MOBILE-3013 core: Support new kind of URL schemes
This commit is contained in:
		
							parent
							
								
									cab44018a6
								
							
						
					
					
						commit
						99f79b9ff9
					
				| @ -1264,6 +1264,7 @@ | |||||||
|   "core.contentlinks.confirmurlothersite": "local_moodlemobileapp", |   "core.contentlinks.confirmurlothersite": "local_moodlemobileapp", | ||||||
|   "core.contentlinks.errornoactions": "local_moodlemobileapp", |   "core.contentlinks.errornoactions": "local_moodlemobileapp", | ||||||
|   "core.contentlinks.errornosites": "local_moodlemobileapp", |   "core.contentlinks.errornosites": "local_moodlemobileapp", | ||||||
|  |   "core.contentlinks.errorredirectothersite": "local_moodlemobileapp", | ||||||
|   "core.continue": "moodle", |   "core.continue": "moodle", | ||||||
|   "core.copiedtoclipboard": "local_moodlemobileapp", |   "core.copiedtoclipboard": "local_moodlemobileapp", | ||||||
|   "core.course": "moodle", |   "core.course": "moodle", | ||||||
|  | |||||||
| @ -19,6 +19,9 @@ import { CoreEventsProvider } from '@providers/events'; | |||||||
| import { CoreLangProvider } from '@providers/lang'; | import { CoreLangProvider } from '@providers/lang'; | ||||||
| import { CoreLoggerProvider } from '@providers/logger'; | import { CoreLoggerProvider } from '@providers/logger'; | ||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
|  | import { CoreUrlUtilsProvider } from '@providers/utils/url'; | ||||||
|  | import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||||
|  | import { CoreCustomURLSchemesProvider } from '@providers/urlschemes'; | ||||||
| import { CoreLoginHelperProvider } from '@core/login/providers/helper'; | import { CoreLoginHelperProvider } from '@core/login/providers/helper'; | ||||||
| import { Keyboard } from '@ionic-native/keyboard'; | import { Keyboard } from '@ionic-native/keyboard'; | ||||||
| import { ScreenOrientation } from '@ionic-native/screen-orientation'; | import { ScreenOrientation } from '@ionic-native/screen-orientation'; | ||||||
| @ -32,11 +35,13 @@ export class MoodleMobileApp implements OnInit { | |||||||
|     rootPage: any = 'CoreLoginInitPage'; |     rootPage: any = 'CoreLoginInitPage'; | ||||||
|     protected logger; |     protected logger; | ||||||
|     protected lastUrls = {}; |     protected lastUrls = {}; | ||||||
|  |     protected lastInAppUrl: string; | ||||||
| 
 | 
 | ||||||
|     constructor(private platform: Platform, logger: CoreLoggerProvider, keyboard: Keyboard, |     constructor(private platform: Platform, logger: CoreLoggerProvider, keyboard: Keyboard, | ||||||
|             private eventsProvider: CoreEventsProvider, private loginHelper: CoreLoginHelperProvider, private zone: NgZone, |             private eventsProvider: CoreEventsProvider, private loginHelper: CoreLoginHelperProvider, private zone: NgZone, | ||||||
|             private appProvider: CoreAppProvider, private langProvider: CoreLangProvider, private sitesProvider: CoreSitesProvider, |             private appProvider: CoreAppProvider, private langProvider: CoreLangProvider, private sitesProvider: CoreSitesProvider, | ||||||
|             private screenOrientation: ScreenOrientation, app: IonicApp) { |             private screenOrientation: ScreenOrientation, app: IonicApp, private urlSchemesProvider: CoreCustomURLSchemesProvider, | ||||||
|  |             private utils: CoreUtilsProvider, private urlUtils: CoreUrlUtilsProvider) { | ||||||
|         this.logger = logger.getInstance('AppComponent'); |         this.logger = logger.getInstance('AppComponent'); | ||||||
| 
 | 
 | ||||||
|         platform.ready().then(() => { |         platform.ready().then(() => { | ||||||
| @ -98,13 +103,39 @@ export class MoodleMobileApp implements OnInit { | |||||||
| 
 | 
 | ||||||
|         // Check URLs loaded in any InAppBrowser.
 |         // Check URLs loaded in any InAppBrowser.
 | ||||||
|         this.eventsProvider.on(CoreEventsProvider.IAB_LOAD_START, (event) => { |         this.eventsProvider.on(CoreEventsProvider.IAB_LOAD_START, (event) => { | ||||||
|             this.loginHelper.inAppBrowserLoadStart(event.url); |             // URLs with a custom scheme can be prefixed with "http://" or "https://", we need to remove this.
 | ||||||
|  |             const url = event.url.replace(/^https?:\/\//, ''); | ||||||
|  | 
 | ||||||
|  |             if (this.urlSchemesProvider.isCustomURL(url)) { | ||||||
|  |                 // Close the browser if it's a valid SSO URL.
 | ||||||
|  |                 this.urlSchemesProvider.handleCustomURL(url); | ||||||
|  |                 this.utils.closeInAppBrowser(false); | ||||||
|  | 
 | ||||||
|  |             } else if (this.platform.is('android')) { | ||||||
|  |                 // Check if the URL has a custom URL scheme. In Android they need to be opened manually.
 | ||||||
|  |                 const urlScheme = this.urlUtils.getUrlProtocol(url); | ||||||
|  |                 if (urlScheme && urlScheme !== 'file' && urlScheme !== 'cdvfile') { | ||||||
|  |                     // Open in browser should launch the right app if found and do nothing if not found.
 | ||||||
|  |                     this.utils.openInBrowser(url); | ||||||
|  | 
 | ||||||
|  |                     // At this point the InAppBrowser is showing a "Webpage not available" error message.
 | ||||||
|  |                     // Try to navigate to last loaded URL so this error message isn't found.
 | ||||||
|  |                     if (this.lastInAppUrl) { | ||||||
|  |                         this.utils.openInApp(this.lastInAppUrl); | ||||||
|  |                     } else { | ||||||
|  |                         // No last URL loaded, close the InAppBrowser.
 | ||||||
|  |                         this.utils.closeInAppBrowser(false); | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     this.lastInAppUrl = url; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         // Check InAppBrowser closed.
 |         // Check InAppBrowser closed.
 | ||||||
|         this.eventsProvider.on(CoreEventsProvider.IAB_EXIT, () => { |         this.eventsProvider.on(CoreEventsProvider.IAB_EXIT, () => { | ||||||
|             this.loginHelper.waitingForBrowser = false; |             this.loginHelper.waitingForBrowser = false; | ||||||
|             this.loginHelper.lastInAppUrl = ''; |             this.lastInAppUrl = ''; | ||||||
|             this.loginHelper.checkLogout(); |             this.loginHelper.checkLogout(); | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
| @ -131,14 +162,10 @@ export class MoodleMobileApp implements OnInit { | |||||||
|                 this.lastUrls[url] = Date.now(); |                 this.lastUrls[url] = Date.now(); | ||||||
| 
 | 
 | ||||||
|                 this.eventsProvider.trigger(CoreEventsProvider.APP_LAUNCHED_URL, url); |                 this.eventsProvider.trigger(CoreEventsProvider.APP_LAUNCHED_URL, url); | ||||||
|  |                 this.urlSchemesProvider.handleCustomURL(url); | ||||||
|             }); |             }); | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         // Listen for app launched URLs. If we receive one, check if it's a SSO authentication.
 |  | ||||||
|         this.eventsProvider.on(CoreEventsProvider.APP_LAUNCHED_URL, (url) => { |  | ||||||
|             this.loginHelper.appLaunchedByURL(url); |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         // Load custom lang strings. This cannot be done inside the lang provider because it causes circular dependencies.
 |         // Load custom lang strings. This cannot be done inside the lang provider because it causes circular dependencies.
 | ||||||
|         const loadCustomStrings = (): void => { |         const loadCustomStrings = (): void => { | ||||||
|             const currentSite = this.sitesProvider.getCurrentSite(), |             const currentSite = this.sitesProvider.getCurrentSite(), | ||||||
|  | |||||||
| @ -59,6 +59,7 @@ import { CoreUpdateManagerProvider } from '@providers/update-manager'; | |||||||
| import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; | import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; | ||||||
| import { CoreSyncProvider } from '@providers/sync'; | import { CoreSyncProvider } from '@providers/sync'; | ||||||
| import { CoreFileHelperProvider } from '@providers/file-helper'; | import { CoreFileHelperProvider } from '@providers/file-helper'; | ||||||
|  | import { CoreCustomURLSchemesProvider } from '@providers/urlschemes'; | ||||||
| 
 | 
 | ||||||
| // Core modules.
 | // Core modules.
 | ||||||
| import { CoreComponentsModule } from '@components/components.module'; | import { CoreComponentsModule } from '@components/components.module'; | ||||||
| @ -161,7 +162,8 @@ export const CORE_PROVIDERS: any[] = [ | |||||||
|     CoreUpdateManagerProvider, |     CoreUpdateManagerProvider, | ||||||
|     CorePluginFileDelegate, |     CorePluginFileDelegate, | ||||||
|     CoreSyncProvider, |     CoreSyncProvider, | ||||||
|     CoreFileHelperProvider |     CoreFileHelperProvider, | ||||||
|  |     CoreCustomURLSchemesProvider | ||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
| @NgModule({ | @NgModule({ | ||||||
| @ -280,6 +282,7 @@ export const CORE_PROVIDERS: any[] = [ | |||||||
|         CorePluginFileDelegate, |         CorePluginFileDelegate, | ||||||
|         CoreSyncProvider, |         CoreSyncProvider, | ||||||
|         CoreFileHelperProvider, |         CoreFileHelperProvider, | ||||||
|  |         CoreCustomURLSchemesProvider, | ||||||
|         { |         { | ||||||
|             provide: HTTP_INTERCEPTORS, |             provide: HTTP_INTERCEPTORS, | ||||||
|             useClass: CoreInterceptor, |             useClass: CoreInterceptor, | ||||||
|  | |||||||
| @ -1264,6 +1264,7 @@ | |||||||
|     "core.contentlinks.confirmurlothersite": "This link belongs to another site. Do you want to open it?", |     "core.contentlinks.confirmurlothersite": "This link belongs to another site. Do you want to open it?", | ||||||
|     "core.contentlinks.errornoactions": "Couldn't find an action to perform with this link.", |     "core.contentlinks.errornoactions": "Couldn't find an action to perform with this link.", | ||||||
|     "core.contentlinks.errornosites": "Couldn't find any site to handle this link.", |     "core.contentlinks.errornosites": "Couldn't find any site to handle this link.", | ||||||
|  |     "core.contentlinks.errorredirectothersite": "The redirect URL cannot point to a different site.", | ||||||
|     "core.continue": "Continue", |     "core.continue": "Continue", | ||||||
|     "core.copiedtoclipboard": "Text copied to clipboard", |     "core.copiedtoclipboard": "Text copied to clipboard", | ||||||
|     "core.course": "Course", |     "core.course": "Course", | ||||||
|  | |||||||
| @ -3,5 +3,6 @@ | |||||||
|     "chooseaccounttoopenlink": "Choose an account to open the link with.", |     "chooseaccounttoopenlink": "Choose an account to open the link with.", | ||||||
|     "confirmurlothersite": "This link belongs to another site. Do you want to open it?", |     "confirmurlothersite": "This link belongs to another site. Do you want to open it?", | ||||||
|     "errornoactions": "Couldn't find an action to perform with this link.", |     "errornoactions": "Couldn't find an action to perform with this link.", | ||||||
|     "errornosites": "Couldn't find any site to handle this link." |     "errornosites": "Couldn't find any site to handle this link.", | ||||||
|  |     "errorredirectothersite": "The redirect URL cannot point to a different site." | ||||||
| } | } | ||||||
| @ -14,10 +14,12 @@ | |||||||
| 
 | 
 | ||||||
| import { Component, OnInit } from '@angular/core'; | import { Component, OnInit } from '@angular/core'; | ||||||
| import { IonicPage, NavController, NavParams } from 'ionic-angular'; | import { IonicPage, NavController, NavParams } from 'ionic-angular'; | ||||||
|  | import { TranslateService } from '@ngx-translate/core'; | ||||||
| import { CoreSitesProvider } from '@providers/sites'; | import { CoreSitesProvider } from '@providers/sites'; | ||||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| import { CoreContentLinksDelegate, CoreContentLinksAction } from '../../providers/delegate'; | import { CoreContentLinksDelegate, CoreContentLinksAction } from '../../providers/delegate'; | ||||||
| import { CoreContentLinksHelperProvider } from '../../providers/helper'; | import { CoreContentLinksHelperProvider } from '../../providers/helper'; | ||||||
|  | import { CoreLoginHelperProvider } from '@core/login/providers/helper'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Page to display the list of sites to choose one to perform a content link action. |  * Page to display the list of sites to choose one to perform a content link action. | ||||||
| @ -33,10 +35,11 @@ export class CoreContentLinksChooseSitePage implements OnInit { | |||||||
|     sites: any[]; |     sites: any[]; | ||||||
|     loaded: boolean; |     loaded: boolean; | ||||||
|     protected action: CoreContentLinksAction; |     protected action: CoreContentLinksAction; | ||||||
|  |     protected isRootURL: boolean; | ||||||
| 
 | 
 | ||||||
|     constructor(private navCtrl: NavController, navParams: NavParams, private contentLinksDelegate: CoreContentLinksDelegate, |     constructor(private navCtrl: NavController, navParams: NavParams, private contentLinksDelegate: CoreContentLinksDelegate, | ||||||
|             private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider, |             private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider, private translate: TranslateService, | ||||||
|             private contentLinksHelper: CoreContentLinksHelperProvider) { |             private contentLinksHelper: CoreContentLinksHelperProvider, private loginHelper: CoreLoginHelperProvider) { | ||||||
|         this.url = navParams.get('url'); |         this.url = navParams.get('url'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -48,19 +51,35 @@ export class CoreContentLinksChooseSitePage implements OnInit { | |||||||
|             return this.leaveView(); |             return this.leaveView(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Get the action to perform.
 |         // Check if it's the root URL.
 | ||||||
|         this.contentLinksDelegate.getActionsFor(this.url).then((actions) => { |         this.sitesProvider.isStoredRootURL(this.url).then((data): any => { | ||||||
|             this.action = this.contentLinksHelper.getFirstValidAction(actions); |             if (data.site) { | ||||||
|             if (!this.action) { |                 // It's the root URL.
 | ||||||
|                 return Promise.reject(null); |                 this.isRootURL = true; | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|  |                 return data.siteIds; | ||||||
|  |             } else if (data.siteIds.length) { | ||||||
|  |                 // Not root URL, but the URL belongs to at least 1 site. Check if there is any action to treat the link.
 | ||||||
|  |                 return this.contentLinksDelegate.getActionsFor(this.url).then((actions): any => { | ||||||
|  |                     this.action = this.contentLinksHelper.getFirstValidAction(actions); | ||||||
|  |                     if (!this.action) { | ||||||
|  |                         return Promise.reject(this.translate.instant('core.contentlinks.errornoactions')); | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     return this.action.sites; | ||||||
|  |                 }); | ||||||
|  |             } else { | ||||||
|  |                 // No sites to treat the URL.
 | ||||||
|  |                 return Promise.reject(this.translate.instant('core.contentlinks.errornosites')); | ||||||
|  |             } | ||||||
|  |         }).then((siteIds) => { | ||||||
|             // Get the sites that can perform the action.
 |             // Get the sites that can perform the action.
 | ||||||
|             return this.sitesProvider.getSites(this.action.sites).then((sites) => { |             return this.sitesProvider.getSites(siteIds); | ||||||
|                 this.sites = sites; |         }).then((sites) => { | ||||||
|             }); |             this.sites = sites; | ||||||
|         }).catch(() => { | 
 | ||||||
|             this.domUtils.showErrorModal('core.contentlinks.errornosites', true); |         }).catch((error) => { | ||||||
|  |             this.domUtils.showErrorModalDefault(error, 'core.contentlinks.errornosites', true); | ||||||
|             this.leaveView(); |             this.leaveView(); | ||||||
|         }).finally(() => { |         }).finally(() => { | ||||||
|             this.loaded = true; |             this.loaded = true; | ||||||
| @ -80,7 +99,11 @@ export class CoreContentLinksChooseSitePage implements OnInit { | |||||||
|      * @param {string} siteId Site ID. |      * @param {string} siteId Site ID. | ||||||
|      */ |      */ | ||||||
|     siteClicked(siteId: string): void { |     siteClicked(siteId: string): void { | ||||||
|         this.action.action(siteId, this.navCtrl); |         if (this.isRootURL) { | ||||||
|  |             this.loginHelper.redirect('', {}, siteId); | ||||||
|  |         } else { | ||||||
|  |             this.action.action(siteId, this.navCtrl); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -44,9 +44,6 @@ export class CoreContentLinksHelperProvider { | |||||||
|             private initDelegate: CoreInitDelegate, eventsProvider: CoreEventsProvider, private textUtils: CoreTextUtilsProvider, |             private initDelegate: CoreInitDelegate, eventsProvider: CoreEventsProvider, private textUtils: CoreTextUtilsProvider, | ||||||
|             private sitePluginsProvider: CoreSitePluginsProvider, private zone: NgZone, private utils: CoreUtilsProvider) { |             private sitePluginsProvider: CoreSitePluginsProvider, private zone: NgZone, private utils: CoreUtilsProvider) { | ||||||
|         this.logger = logger.getInstance('CoreContentLinksHelperProvider'); |         this.logger = logger.getInstance('CoreContentLinksHelperProvider'); | ||||||
| 
 |  | ||||||
|         // Listen for app launched URLs. If we receive one, check if it's a content link.
 |  | ||||||
|         eventsProvider.on(CoreEventsProvider.APP_LAUNCHED_URL, this.handleCustomUrl.bind(this)); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -62,7 +59,7 @@ export class CoreContentLinksHelperProvider { | |||||||
|         let promise; |         let promise; | ||||||
| 
 | 
 | ||||||
|         if (checkRoot) { |         if (checkRoot) { | ||||||
|             promise = this.isStoredRootURL(url, username); |             promise = this.sitesProvider.isStoredRootURL(url, username); | ||||||
|         } else { |         } else { | ||||||
|             promise = Promise.resolve({}); |             promise = Promise.resolve({}); | ||||||
|         } |         } | ||||||
| @ -139,6 +136,7 @@ export class CoreContentLinksHelperProvider { | |||||||
|      * |      * | ||||||
|      * @param {string} url URL to handle. |      * @param {string} url URL to handle. | ||||||
|      * @return {boolean} True if the URL should be handled by this component, false otherwise. |      * @return {boolean} True if the URL should be handled by this component, false otherwise. | ||||||
|  |      * @deprecated Please use CoreCustomURLSchemesProvider.handleCustomURL instead. | ||||||
|      */ |      */ | ||||||
|     handleCustomUrl(url: string): boolean { |     handleCustomUrl(url: string): boolean { | ||||||
|         const contentLinksScheme = CoreConfigConstants.customurlscheme + '://link'; |         const contentLinksScheme = CoreConfigConstants.customurlscheme + '://link'; | ||||||
| @ -166,7 +164,7 @@ export class CoreContentLinksHelperProvider { | |||||||
|         // Wait for the app to be ready.
 |         // Wait for the app to be ready.
 | ||||||
|         this.initDelegate.ready().then(() => { |         this.initDelegate.ready().then(() => { | ||||||
|             // Check if it's the root URL.
 |             // Check if it's the root URL.
 | ||||||
|             return this.isStoredRootURL(url, username); |             return this.sitesProvider.isStoredRootURL(url, username); | ||||||
|         }).then((data) => { |         }).then((data) => { | ||||||
| 
 | 
 | ||||||
|             if (data.site) { |             if (data.site) { | ||||||
| @ -174,7 +172,7 @@ export class CoreContentLinksHelperProvider { | |||||||
|                 modal.dismiss(); |                 modal.dismiss(); | ||||||
| 
 | 
 | ||||||
|                 return this.handleRootURL(data.site, false); |                 return this.handleRootURL(data.site, false); | ||||||
|             } else if (data.hasSites) { |             } else if (data.siteIds.length > 0) { | ||||||
|                 modal.dismiss(); // Dismiss modal so it doesn't collide with confirms.
 |                 modal.dismiss(); // Dismiss modal so it doesn't collide with confirms.
 | ||||||
| 
 | 
 | ||||||
|                 return this.handleLink(url, username).then((treated) => { |                 return this.handleLink(url, username).then((treated) => { | ||||||
| @ -266,7 +264,7 @@ export class CoreContentLinksHelperProvider { | |||||||
|         let promise; |         let promise; | ||||||
| 
 | 
 | ||||||
|         if (checkRoot) { |         if (checkRoot) { | ||||||
|             promise = this.isStoredRootURL(url, username); |             promise = this.sitesProvider.isStoredRootURL(url, username); | ||||||
|         } else { |         } else { | ||||||
|             promise = Promise.resolve({}); |             promise = Promise.resolve({}); | ||||||
|         } |         } | ||||||
| @ -321,12 +319,14 @@ export class CoreContentLinksHelperProvider { | |||||||
|      * |      * | ||||||
|      * @param {CoreSite} site Site to handle. |      * @param {CoreSite} site Site to handle. | ||||||
|      * @param {boolean} [openBrowserRoot] Whether to open in browser if it's root URL and it belongs to current site. |      * @param {boolean} [openBrowserRoot] Whether to open in browser if it's root URL and it belongs to current site. | ||||||
|  |      * @param {boolean} [checkToken] Whether to check that token is the same to verify it's current site. If false or not defined, | ||||||
|  |      *                               only the URL will be checked. | ||||||
|      * @return {Promise<any>} Promise resolved when done. |      * @return {Promise<any>} Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     handleRootURL(site: CoreSite, openBrowserRoot?: boolean): Promise<any> { |     handleRootURL(site: CoreSite, openBrowserRoot?: boolean, checkToken?: boolean): Promise<any> { | ||||||
|         const currentSite = this.sitesProvider.getCurrentSite(); |         const currentSite = this.sitesProvider.getCurrentSite(); | ||||||
| 
 | 
 | ||||||
|         if (currentSite && currentSite.getURL() == site.getURL()) { |         if (currentSite && currentSite.getURL() == site.getURL() && (!checkToken || currentSite.getToken() == site.getToken())) { | ||||||
|             // Already logged in.
 |             // Already logged in.
 | ||||||
|             if (openBrowserRoot) { |             if (openBrowserRoot) { | ||||||
|                 return site.openInBrowserWithAutoLogin(site.getURL()); |                 return site.openInBrowserWithAutoLogin(site.getURL()); | ||||||
| @ -338,38 +338,4 @@ export class CoreContentLinksHelperProvider { | |||||||
|             return this.loginHelper.redirect('', {}, site.getId()); |             return this.loginHelper.redirect('', {}, site.getId()); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Check if a URL is the root URL of any of the stored sites. If so, return the site ID. |  | ||||||
|      * |  | ||||||
|      * @param {string} url URL to check. |  | ||||||
|      * @param {string} username Username to check. |  | ||||||
|      * @return {Promise<{site: CoreSite, hasSites: boolean}>} Promise resolved with site and whether there is any site to treat |  | ||||||
|      *                                   the URL. Site will be undefined if it isn't the root URL of any stored site. |  | ||||||
|      */ |  | ||||||
|     isStoredRootURL(url: string, username: string): Promise<{site: CoreSite, hasSites: boolean}> { |  | ||||||
|         // Check if the site is stored.
 |  | ||||||
|         return this.sitesProvider.getSiteIdsFromUrl(url, true, username).then((siteIds) => { |  | ||||||
|             const result = { |  | ||||||
|                 hasSites: siteIds.length > 0, |  | ||||||
|                 site: undefined |  | ||||||
|             }; |  | ||||||
| 
 |  | ||||||
|             if (result.hasSites) { |  | ||||||
|                 // If more than one site is returned it usually means there are different users stored. Use any of them.
 |  | ||||||
|                 return this.sitesProvider.getSite(siteIds[0]).then((site) => { |  | ||||||
|                     const siteUrl = this.textUtils.removeEndingSlash(this.urlUtils.removeProtocolAndWWW(site.getURL())), |  | ||||||
|                         treatedUrl = this.textUtils.removeEndingSlash(this.urlUtils.removeProtocolAndWWW(url)); |  | ||||||
| 
 |  | ||||||
|                     if (siteUrl == treatedUrl) { |  | ||||||
|                         result.site = site; |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     return result; |  | ||||||
|                 }); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             return result; |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -21,8 +21,6 @@ import { CoreSitesProvider } from '@providers/sites'; | |||||||
| import { CoreDomUtilsProvider } from '@providers/utils/dom'; | import { CoreDomUtilsProvider } from '@providers/utils/dom'; | ||||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||||
| import { CoreLoginHelperProvider } from '../../providers/helper'; | import { CoreLoginHelperProvider } from '../../providers/helper'; | ||||||
| import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; |  | ||||||
| import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; |  | ||||||
| import { FormBuilder, FormGroup, Validators } from '@angular/forms'; | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; | ||||||
| import { CoreConfigConstants } from '../../../../configconstants'; | import { CoreConfigConstants } from '../../../../configconstants'; | ||||||
| 
 | 
 | ||||||
| @ -56,8 +54,7 @@ export class CoreLoginCredentialsPage { | |||||||
|     constructor(private navCtrl: NavController, navParams: NavParams, fb: FormBuilder, private appProvider: CoreAppProvider, |     constructor(private navCtrl: NavController, navParams: NavParams, fb: FormBuilder, private appProvider: CoreAppProvider, | ||||||
|             private sitesProvider: CoreSitesProvider, private loginHelper: CoreLoginHelperProvider, |             private sitesProvider: CoreSitesProvider, private loginHelper: CoreLoginHelperProvider, | ||||||
|             private domUtils: CoreDomUtilsProvider, private translate: TranslateService, private utils: CoreUtilsProvider, |             private domUtils: CoreDomUtilsProvider, private translate: TranslateService, private utils: CoreUtilsProvider, | ||||||
|             private eventsProvider: CoreEventsProvider, private contentLinksDelegate: CoreContentLinksDelegate, |             private eventsProvider: CoreEventsProvider) { | ||||||
|             private contentLinksHelper: CoreContentLinksHelperProvider) { |  | ||||||
| 
 | 
 | ||||||
|         this.siteUrl = navParams.get('siteUrl'); |         this.siteUrl = navParams.get('siteUrl'); | ||||||
|         this.siteConfig = navParams.get('siteConfig'); |         this.siteConfig = navParams.get('siteConfig'); | ||||||
| @ -220,20 +217,7 @@ export class CoreLoginCredentialsPage { | |||||||
| 
 | 
 | ||||||
|                 this.siteId = id; |                 this.siteId = id; | ||||||
| 
 | 
 | ||||||
|                 if (this.urlToOpen) { |                 return this.loginHelper.goToSiteInitialPage(undefined, undefined, undefined, undefined, this.urlToOpen); | ||||||
|                     // There's a content link to open.
 |  | ||||||
|                     return this.contentLinksDelegate.getActionsFor(this.urlToOpen, undefined, username).then((actions) => { |  | ||||||
|                         const action = this.contentLinksHelper.getFirstValidAction(actions); |  | ||||||
|                         if (action && action.sites.length) { |  | ||||||
|                             // Action should only have 1 site because we're filtering by username.
 |  | ||||||
|                             action.action(action.sites[0]); |  | ||||||
|                         } else { |  | ||||||
|                             return this.loginHelper.goToSiteInitialPage(); |  | ||||||
|                         } |  | ||||||
|                     }); |  | ||||||
|                 } else { |  | ||||||
|                     return this.loginHelper.goToSiteInitialPage(); |  | ||||||
|                 } |  | ||||||
|             }); |             }); | ||||||
|         }).catch((error) => { |         }).catch((error) => { | ||||||
|             this.loginHelper.treatUserTokenError(siteUrl, error, username, password); |             this.loginHelper.treatUserTokenError(siteUrl, error, username, password); | ||||||
|  | |||||||
| @ -41,7 +41,7 @@ export interface CoreLoginSSOData { | |||||||
|      * The site's URL. |      * The site's URL. | ||||||
|      * @type {string} |      * @type {string} | ||||||
|      */ |      */ | ||||||
|     siteUrl?: string; |     siteUrl: string; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * User's token. |      * User's token. | ||||||
| @ -78,7 +78,6 @@ export class CoreLoginHelperProvider { | |||||||
|     protected logger; |     protected logger; | ||||||
|     protected isSSOConfirmShown = false; |     protected isSSOConfirmShown = false; | ||||||
|     protected isOpenEditAlertShown = false; |     protected isOpenEditAlertShown = false; | ||||||
|     lastInAppUrl: string; |  | ||||||
|     waitingForBrowser = false; |     waitingForBrowser = false; | ||||||
| 
 | 
 | ||||||
|     constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider, |     constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider, | ||||||
| @ -126,6 +125,7 @@ export class CoreLoginHelperProvider { | |||||||
|      * |      * | ||||||
|      * @param {string} url URL received. |      * @param {string} url URL received. | ||||||
|      * @return {boolean} True if it's a SSO URL, false otherwise. |      * @return {boolean} True if it's a SSO URL, false otherwise. | ||||||
|  |      * @deprecated Please use CoreCustomURLSchemesProvider.handleCustomURL instead. | ||||||
|      */ |      */ | ||||||
|     appLaunchedByURL(url: string): boolean { |     appLaunchedByURL(url: string): boolean { | ||||||
|         const ssoScheme = CoreConfigConstants.customurlscheme + '://token='; |         const ssoScheme = CoreConfigConstants.customurlscheme + '://token='; | ||||||
| @ -437,13 +437,13 @@ export class CoreLoginHelperProvider { | |||||||
|                     // There are sites stored, open sites page first to be able to go back.
 |                     // There are sites stored, open sites page first to be able to go back.
 | ||||||
|                     navCtrl.setRoot('CoreLoginSitesPage'); |                     navCtrl.setRoot('CoreLoginSitesPage'); | ||||||
| 
 | 
 | ||||||
|                     return navCtrl.push(page, page, {animate: false}); |                     return navCtrl.push(page, params, {animate: false}); | ||||||
|                 } else { |                 } else { | ||||||
|                     if (page != 'CoreLoginSitePage') { |                     if (page != 'CoreLoginSitePage') { | ||||||
|                         // Open the new site page to be able to go back.
 |                         // Open the new site page to be able to go back.
 | ||||||
|                         navCtrl.setRoot('CoreLoginSitePage'); |                         navCtrl.setRoot('CoreLoginSitePage'); | ||||||
| 
 | 
 | ||||||
|                         return navCtrl.push(page, page, {animate: false}); |                         return navCtrl.push(page, params, {animate: false}); | ||||||
|                     } else { |                     } else { | ||||||
|                         // Just open the page as root.
 |                         // Just open the page as root.
 | ||||||
|                         return navCtrl.setRoot(page, params); |                         return navCtrl.setRoot(page, params); | ||||||
| @ -460,10 +460,11 @@ export class CoreLoginHelperProvider { | |||||||
|      * @param {string} [page] Name of the page to load after loading the main page. |      * @param {string} [page] Name of the page to load after loading the main page. | ||||||
|      * @param {any} [params] Params to pass to the page. |      * @param {any} [params] Params to pass to the page. | ||||||
|      * @param {NavOptions} [options] Navigation options. |      * @param {NavOptions} [options] Navigation options. | ||||||
|  |      * @param {string} [url] URL to open once the main menu is loaded. | ||||||
|      * @return {Promise<any>} Promise resolved when done. |      * @return {Promise<any>} Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     goToSiteInitialPage(navCtrl?: NavController, page?: string, params?: any, options?: NavOptions): Promise<any> { |     goToSiteInitialPage(navCtrl?: NavController, page?: string, params?: any, options?: NavOptions, url?: string): Promise<any> { | ||||||
|         return this.openMainMenu(navCtrl, page, params, options); |         return this.openMainMenu(navCtrl, page, params, options, url); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -494,33 +495,10 @@ export class CoreLoginHelperProvider { | |||||||
|      * Function called when a page starts loading in any InAppBrowser window. |      * Function called when a page starts loading in any InAppBrowser window. | ||||||
|      * |      * | ||||||
|      * @param {string} url Loaded url. |      * @param {string} url Loaded url. | ||||||
|  |      * @deprecated | ||||||
|      */ |      */ | ||||||
|     inAppBrowserLoadStart(url: string): void { |     inAppBrowserLoadStart(url: string): void { | ||||||
|         // URLs with a custom scheme can be prefixed with "http://" or "https://", we need to remove this.
 |         // This function is deprecated.
 | ||||||
|         url = url.replace(/^https?:\/\//, ''); |  | ||||||
| 
 |  | ||||||
|         if (this.appLaunchedByURL(url)) { |  | ||||||
|             // Close the browser if it's a valid SSO URL.
 |  | ||||||
|             this.utils.closeInAppBrowser(false); |  | ||||||
|         } else if (this.platform.is('android')) { |  | ||||||
|             // Check if the URL has a custom URL scheme. In Android they need to be opened manually.
 |  | ||||||
|             const urlScheme = this.urlUtils.getUrlProtocol(url); |  | ||||||
|             if (urlScheme && urlScheme !== 'file' && urlScheme !== 'cdvfile') { |  | ||||||
|                 // Open in browser should launch the right app if found and do nothing if not found.
 |  | ||||||
|                 this.utils.openInBrowser(url); |  | ||||||
| 
 |  | ||||||
|                 // At this point the InAppBrowser is showing a "Webpage not available" error message.
 |  | ||||||
|                 // Try to navigate to last loaded URL so this error message isn't found.
 |  | ||||||
|                 if (this.lastInAppUrl) { |  | ||||||
|                     this.utils.openInApp(this.lastInAppUrl); |  | ||||||
|                 } else { |  | ||||||
|                     // No last URL loaded, close the InAppBrowser.
 |  | ||||||
|                     this.utils.closeInAppBrowser(false); |  | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 this.lastInAppUrl = url; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -657,9 +635,10 @@ export class CoreLoginHelperProvider { | |||||||
|      * @param {string} page Name of the page to load. |      * @param {string} page Name of the page to load. | ||||||
|      * @param {any} params Params to pass to the page. |      * @param {any} params Params to pass to the page. | ||||||
|      * @param {NavOptions} [options] Navigation options. |      * @param {NavOptions} [options] Navigation options. | ||||||
|  |      * @param {string} [url] URL to open once the main menu is loaded. | ||||||
|      * @return {Promise<any>} Promise resolved when done. |      * @return {Promise<any>} Promise resolved when done. | ||||||
|      */ |      */ | ||||||
|     protected openMainMenu(navCtrl: NavController, page: string, params: any, options?: NavOptions): Promise<any> { |     protected openMainMenu(navCtrl: NavController, page: string, params: any, options?: NavOptions, url?: string): Promise<any> { | ||||||
|         navCtrl = navCtrl || this.appProvider.getRootNavController(); |         navCtrl = navCtrl || this.appProvider.getRootNavController(); | ||||||
| 
 | 
 | ||||||
|         // Due to DeepLinker, we need to remove the path from the URL before going to main menu.
 |         // Due to DeepLinker, we need to remove the path from the URL before going to main menu.
 | ||||||
| @ -673,7 +652,7 @@ export class CoreLoginHelperProvider { | |||||||
|             }); |             }); | ||||||
|         } else { |         } else { | ||||||
|             // Open the main menu.
 |             // Open the main menu.
 | ||||||
|             return navCtrl.setRoot('CoreMainMenuPage', { redirectPage: page, redirectParams: params }, options); |             return navCtrl.setRoot('CoreMainMenuPage', { redirectPage: page, redirectParams: params, urlToOpen: url }, options); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -19,6 +19,8 @@ import { CoreEventsProvider } from '@providers/events'; | |||||||
| import { CoreIonTabsComponent } from '@components/ion-tabs/ion-tabs'; | import { CoreIonTabsComponent } from '@components/ion-tabs/ion-tabs'; | ||||||
| import { CoreMainMenuProvider } from '../../providers/mainmenu'; | import { CoreMainMenuProvider } from '../../providers/mainmenu'; | ||||||
| import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from '../../providers/delegate'; | import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from '../../providers/delegate'; | ||||||
|  | import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; | ||||||
|  | import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Page that displays the main menu of the app. |  * Page that displays the main menu of the app. | ||||||
| @ -40,12 +42,14 @@ export class CoreMainMenuPage implements OnDestroy { | |||||||
|     protected subscription; |     protected subscription; | ||||||
|     protected redirectObs: any; |     protected redirectObs: any; | ||||||
|     protected pendingRedirect: any; |     protected pendingRedirect: any; | ||||||
|  |     protected urlToOpen: string; | ||||||
| 
 | 
 | ||||||
|     @ViewChild('mainTabs') mainTabs: CoreIonTabsComponent; |     @ViewChild('mainTabs') mainTabs: CoreIonTabsComponent; | ||||||
| 
 | 
 | ||||||
|     constructor(private menuDelegate: CoreMainMenuDelegate, private sitesProvider: CoreSitesProvider, navParams: NavParams, |     constructor(private menuDelegate: CoreMainMenuDelegate, private sitesProvider: CoreSitesProvider, navParams: NavParams, | ||||||
|             private navCtrl: NavController, private eventsProvider: CoreEventsProvider, private cdr: ChangeDetectorRef, |             private navCtrl: NavController, private eventsProvider: CoreEventsProvider, private cdr: ChangeDetectorRef, | ||||||
|             private mainMenuProvider: CoreMainMenuProvider) { |             private mainMenuProvider: CoreMainMenuProvider, private linksDelegate: CoreContentLinksDelegate, | ||||||
|  |             private linksHelper: CoreContentLinksHelperProvider) { | ||||||
| 
 | 
 | ||||||
|         // Check if the menu was loaded with a redirect.
 |         // Check if the menu was loaded with a redirect.
 | ||||||
|         const redirectPage = navParams.get('redirectPage'); |         const redirectPage = navParams.get('redirectPage'); | ||||||
| @ -55,6 +59,8 @@ export class CoreMainMenuPage implements OnDestroy { | |||||||
|                 redirectParams: navParams.get('redirectParams') |                 redirectParams: navParams.get('redirectParams') | ||||||
|             }; |             }; | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         this.urlToOpen = navParams.get('urlToOpen'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -155,6 +161,17 @@ export class CoreMainMenuPage implements OnDestroy { | |||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|             this.loaded = this.menuDelegate.areHandlersLoaded(); |             this.loaded = this.menuDelegate.areHandlersLoaded(); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         if (this.urlToOpen) { | ||||||
|  |             // There's a content link to open.
 | ||||||
|  |             this.linksDelegate.getActionsFor(this.urlToOpen, undefined).then((actions) => { | ||||||
|  |                 const action = this.linksHelper.getFirstValidAction(actions); | ||||||
|  |                 if (action && action.sites.length) { | ||||||
|  |                     // Action should only have 1 site because we're filtering by username.
 | ||||||
|  |                     action.action(action.sites[0]); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -565,9 +565,14 @@ export class CoreSitesProvider { | |||||||
|      * @param {string} siteUrl The site url. |      * @param {string} siteUrl The site url. | ||||||
|      * @param {string} token User's token. |      * @param {string} token User's token. | ||||||
|      * @param {string} [privateToken=''] User's private token. |      * @param {string} [privateToken=''] User's private token. | ||||||
|      * @return {Promise<any>} A promise resolved when the site is added and the user is authenticated. |      * @param {boolean} [login=true] Whether to login the user in the site. Defaults to true. | ||||||
|  |      * @return {Promise<string>} A promise resolved with siteId when the site is added and the user is authenticated. | ||||||
|      */ |      */ | ||||||
|     newSite(siteUrl: string, token: string, privateToken: string = ''): Promise<any> { |     newSite(siteUrl: string, token: string, privateToken: string = '', login: boolean = true): Promise<string> { | ||||||
|  |         if (typeof login != 'boolean') { | ||||||
|  |             login = true; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         // Create a "candidate" site to fetch the site info.
 |         // Create a "candidate" site to fetch the site info.
 | ||||||
|         const candidateSite = this.sitesFactory.makeSite(undefined, siteUrl, token, undefined, privateToken); |         const candidateSite = this.sitesFactory.makeSite(undefined, siteUrl, token, undefined, privateToken); | ||||||
| 
 | 
 | ||||||
| @ -585,13 +590,18 @@ export class CoreSitesProvider { | |||||||
|                     // Try to get the site config.
 |                     // Try to get the site config.
 | ||||||
|                     return this.getSiteConfig(candidateSite).then((config) => { |                     return this.getSiteConfig(candidateSite).then((config) => { | ||||||
|                         candidateSite.setConfig(config); |                         candidateSite.setConfig(config); | ||||||
|  | 
 | ||||||
|                         // Add site to sites list.
 |                         // Add site to sites list.
 | ||||||
|                         this.addSite(siteId, siteUrl, token, info, privateToken, config); |                         this.addSite(siteId, siteUrl, token, info, privateToken, config); | ||||||
|                         // Turn candidate site into current site.
 |  | ||||||
|                         this.currentSite = candidateSite; |  | ||||||
|                         this.sites[siteId] = candidateSite; |                         this.sites[siteId] = candidateSite; | ||||||
|                         // Store session.
 | 
 | ||||||
|                         this.login(siteId); |                         if (login) { | ||||||
|  |                             // Turn candidate site into current site.
 | ||||||
|  |                             this.currentSite = candidateSite; | ||||||
|  |                             // Store session.
 | ||||||
|  |                             this.login(siteId); | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|                         this.eventsProvider.trigger(CoreEventsProvider.SITE_ADDED, info, siteId); |                         this.eventsProvider.trigger(CoreEventsProvider.SITE_ADDED, info, siteId); | ||||||
| 
 | 
 | ||||||
|                         return siteId; |                         return siteId; | ||||||
| @ -1477,4 +1487,38 @@ export class CoreSitesProvider { | |||||||
|             delete this.siteSchemasMigration[site.id]; |             delete this.siteSchemasMigration[site.id]; | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Check if a URL is the root URL of any of the stored sites. | ||||||
|  |      * | ||||||
|  |      * @param {string} url URL to check. | ||||||
|  |      * @param {string} [username] Username to check. | ||||||
|  |      * @return {Promise<{site: CoreSite, siteIds: string[]}>} Promise resolved with site to use and the list of sites that have | ||||||
|  |      *                                   the URL. Site will be undefined if it isn't the root URL of any stored site. | ||||||
|  |      */ | ||||||
|  |     isStoredRootURL(url: string, username?: string): Promise<{site: CoreSite, siteIds: string[]}> { | ||||||
|  |         // Check if the site is stored.
 | ||||||
|  |         return this.getSiteIdsFromUrl(url, true, username).then((siteIds) => { | ||||||
|  |             const result = { | ||||||
|  |                 siteIds: siteIds, | ||||||
|  |                 site: undefined | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             if (siteIds.length > 0) { | ||||||
|  |                 // If more than one site is returned it usually means there are different users stored. Use any of them.
 | ||||||
|  |                 return this.getSite(siteIds[0]).then((site) => { | ||||||
|  |                     const siteUrl = this.textUtils.removeEndingSlash(this.urlUtils.removeProtocolAndWWW(site.getURL())), | ||||||
|  |                         treatedUrl = this.textUtils.removeEndingSlash(this.urlUtils.removeProtocolAndWWW(url)); | ||||||
|  | 
 | ||||||
|  |                     if (siteUrl == treatedUrl) { | ||||||
|  |                         result.site = site; | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     return result; | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return result; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										461
									
								
								src/providers/urlschemes.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										461
									
								
								src/providers/urlschemes.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,461 @@ | |||||||
|  | // (C) Copyright 2015 Martin Dougiamas
 | ||||||
|  | //
 | ||||||
|  | // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||||
|  | // you may not use this file except in compliance with the License.
 | ||||||
|  | // You may obtain a copy of the License at
 | ||||||
|  | //
 | ||||||
|  | //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  | //
 | ||||||
|  | // Unless required by applicable law or agreed to in writing, software
 | ||||||
|  | // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||||
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||||
|  | // See the License for the specific language governing permissions and
 | ||||||
|  | // limitations under the License.
 | ||||||
|  | 
 | ||||||
|  | import { Injectable } from '@angular/core'; | ||||||
|  | import { TranslateService } from '@ngx-translate/core'; | ||||||
|  | import { CoreAppProvider } from './app'; | ||||||
|  | import { CoreInitDelegate } from './init'; | ||||||
|  | import { CoreLoggerProvider } from './logger'; | ||||||
|  | import { CoreSitesProvider } from './sites'; | ||||||
|  | import { CoreDomUtilsProvider } from './utils/dom'; | ||||||
|  | import { CoreTextUtilsProvider } from './utils/text'; | ||||||
|  | import { CoreUrlUtilsProvider } from './utils/url'; | ||||||
|  | import { CoreUtilsProvider } from './utils/utils'; | ||||||
|  | import { CoreLoginHelperProvider } from '@core/login/providers/helper'; | ||||||
|  | import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; | ||||||
|  | import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; | ||||||
|  | import { CoreSitePluginsProvider } from '@core/siteplugins/providers/siteplugins'; | ||||||
|  | import { CoreConfigConstants } from '../configconstants'; | ||||||
|  | import { CoreConstants } from '@core/constants'; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * All params that can be in a custom URL scheme. | ||||||
|  |  */ | ||||||
|  | export interface CoreCustomURLSchemesParams { | ||||||
|  |     /** | ||||||
|  |      * The site's URL. | ||||||
|  |      * @type {string} | ||||||
|  |      */ | ||||||
|  |     siteUrl: string; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * User's token. If set, user will be authenticated. | ||||||
|  |      * @type {string} | ||||||
|  |      */ | ||||||
|  |     token?: string; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * User's private token. | ||||||
|  |      * @type {string} | ||||||
|  |      */ | ||||||
|  |     privateToken?: string; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Username. | ||||||
|  |      * @type {string} | ||||||
|  |      */ | ||||||
|  |     username?: string; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * URL to open once authenticated. | ||||||
|  |      * @type {string} | ||||||
|  |      */ | ||||||
|  |     redirect?: any; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Name of the page to go once authenticated. | ||||||
|  |      * @type {string} | ||||||
|  |      */ | ||||||
|  |     pageName?: string; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Params to pass to the page. | ||||||
|  |      * @type {string} | ||||||
|  |      */ | ||||||
|  |     pageParams?: any; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  |  * Provider to handle custom URL schemes. | ||||||
|  |  */ | ||||||
|  | @Injectable() | ||||||
|  | export class CoreCustomURLSchemesProvider { | ||||||
|  |     protected logger; | ||||||
|  | 
 | ||||||
|  |     constructor(logger: CoreLoggerProvider, private appProvider: CoreAppProvider, private utils: CoreUtilsProvider, | ||||||
|  |             private loginHelper: CoreLoginHelperProvider, private linksHelper: CoreContentLinksHelperProvider, | ||||||
|  |             private initDelegate: CoreInitDelegate, private domUtils: CoreDomUtilsProvider, private urlUtils: CoreUrlUtilsProvider, | ||||||
|  |             private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider, | ||||||
|  |             private linksDelegate: CoreContentLinksDelegate, private translate: TranslateService, | ||||||
|  |             private sitePluginsProvider: CoreSitePluginsProvider) { | ||||||
|  |         this.logger = logger.getInstance('CoreCustomURLSchemesProvider'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Handle an URL received by custom URL scheme. | ||||||
|  |      * | ||||||
|  |      * @param {string} url URL to treat. | ||||||
|  |      * @return {Promise<any>} Promise resolved when done. | ||||||
|  |      */ | ||||||
|  |     handleCustomURL(url: string): Promise<any> { | ||||||
|  |         if (!this.isCustomURL(url)) { | ||||||
|  |             return Promise.reject(null); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let modal, | ||||||
|  |             isSSOToken = false, | ||||||
|  |             data: CoreCustomURLSchemesParams; | ||||||
|  | 
 | ||||||
|  |         // Wait for app to be ready.
 | ||||||
|  |         return this.initDelegate.ready().then(() => { | ||||||
|  |             url = this.textUtils.decodeURIComponent(url); | ||||||
|  | 
 | ||||||
|  |             // Some platforms like Windows add a slash at the end. Remove it.
 | ||||||
|  |             // Some sites add a # at the end of the URL. If it's there, remove it.
 | ||||||
|  |             url = url.replace(/\/?#?\/?$/, ''); | ||||||
|  | 
 | ||||||
|  |             modal = this.domUtils.showModalLoading(); | ||||||
|  | 
 | ||||||
|  |             // Get the data from the URL.
 | ||||||
|  |             if (this.isCustomURLToken(url)) { | ||||||
|  |                 isSSOToken = true; | ||||||
|  | 
 | ||||||
|  |                 return this.getCustomURLTokenData(url); | ||||||
|  |             } else if (this.isCustomURLLink(url)) { | ||||||
|  |                 return this.getCustomURLLinkData(url); | ||||||
|  |             } else { | ||||||
|  |                 return this.getCustomURLData(url); | ||||||
|  |             } | ||||||
|  |         }).then((result) => { | ||||||
|  |             data = result; | ||||||
|  | 
 | ||||||
|  |             if (data.redirect && data.redirect.indexOf(data.siteUrl) == -1) { | ||||||
|  |                 // Redirect URL must belong to the same site. Reject.
 | ||||||
|  |                 return Promise.reject(this.translate.instant('core.contentlinks.errorredirectothersite')); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // First of all, authenticate the user if needed.
 | ||||||
|  |             const currentSite = this.sitesProvider.getCurrentSite(); | ||||||
|  | 
 | ||||||
|  |             if (data.token) { | ||||||
|  |                 if (!currentSite || currentSite.getToken() != data.token) { | ||||||
|  |                     return this.sitesProvider.newSite(data.siteUrl, data.token, data.privateToken, isSSOToken); | ||||||
|  |                 } else { | ||||||
|  |                     return this.sitesProvider.getCurrentSiteId(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }).then((siteId) => { | ||||||
|  |             if (isSSOToken) { | ||||||
|  |                 // Site created and authenticated, open the page to go.
 | ||||||
|  |                 if (data.pageName) { | ||||||
|  |                     // State defined, go to that state instead of site initial page.
 | ||||||
|  |                     this.appProvider.getRootNavController().push(data.pageName, data.pageParams); | ||||||
|  |                 } else { | ||||||
|  |                     this.loginHelper.goToSiteInitialPage(); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             let promise; | ||||||
|  | 
 | ||||||
|  |             if (siteId) { | ||||||
|  |                 // Site created, we know the site to use.
 | ||||||
|  |                 promise = Promise.resolve([siteId]); | ||||||
|  |             } else { | ||||||
|  |                 // Check if the site is stored.
 | ||||||
|  |                 promise = this.sitesProvider.getSiteIdsFromUrl(data.siteUrl, true, data.username); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return promise.then((siteIds) => { | ||||||
|  |                 if (siteIds.length > 1) { | ||||||
|  |                     // More than one site to treat the URL, let the user choose.
 | ||||||
|  |                     this.linksHelper.goToChooseSite(data.redirect || data.siteUrl); | ||||||
|  | 
 | ||||||
|  |                 } else if (siteIds.length == 1) { | ||||||
|  |                     // Only one site, handle the link.
 | ||||||
|  |                     return this.sitesProvider.getSite(siteIds[0]).then((site) => { | ||||||
|  |                         if (!data.redirect) { | ||||||
|  |                             // No redirect, go to the root URL if needed.
 | ||||||
|  | 
 | ||||||
|  |                             return this.linksHelper.handleRootURL(site, false, true); | ||||||
|  |                         } else { | ||||||
|  |                             // Handle the redirect link.
 | ||||||
|  |                             modal.dismiss(); // Dismiss modal so it doesn't collide with confirms.
 | ||||||
|  | 
 | ||||||
|  |                             /* Always use the username from the site in this case. If the link has a username and a token, | ||||||
|  |                                this will make sure that the link is opened with the user the token belongs to. */ | ||||||
|  |                             const username = site.getInfo().username || data.username; | ||||||
|  | 
 | ||||||
|  |                             return this.linksHelper.handleLink(data.redirect, username).then((treated) => { | ||||||
|  |                                 if (!treated) { | ||||||
|  |                                     this.domUtils.showErrorModal('core.contentlinks.errornoactions', true); | ||||||
|  |                                 } | ||||||
|  |                             }); | ||||||
|  |                         } | ||||||
|  |                     }); | ||||||
|  | 
 | ||||||
|  |                 } else { | ||||||
|  |                     // Site not stored. Try to add the site.
 | ||||||
|  |                     return this.sitesProvider.checkSite(data.siteUrl).then((result) => { | ||||||
|  |                         // Site exists. We'll allow to add it.
 | ||||||
|  |                         const ssoNeeded = this.loginHelper.isSSOLoginNeeded(result.code), | ||||||
|  |                             pageName = 'CoreLoginCredentialsPage', | ||||||
|  |                             pageParams = { | ||||||
|  |                                 siteUrl: result.siteUrl, | ||||||
|  |                                 username: data.username, | ||||||
|  |                                 urlToOpen: data.redirect, | ||||||
|  |                                 siteConfig: result.config | ||||||
|  |                             }; | ||||||
|  |                         let promise, | ||||||
|  |                             hasSitePluginsLoaded = false; | ||||||
|  | 
 | ||||||
|  |                         modal.dismiss(); // Dismiss modal so it doesn't collide with confirms.
 | ||||||
|  | 
 | ||||||
|  |                         if (!this.sitesProvider.isLoggedIn()) { | ||||||
|  |                             // Not logged in, no need to confirm. If SSO the confirm will be shown later.
 | ||||||
|  |                             promise = Promise.resolve(); | ||||||
|  |                         } else { | ||||||
|  |                             // Ask the user before changing site.
 | ||||||
|  |                             const confirmMsg = this.translate.instant('core.contentlinks.confirmurlothersite'); | ||||||
|  |                             promise = this.domUtils.showConfirm(confirmMsg).then(() => { | ||||||
|  |                                 if (!ssoNeeded) { | ||||||
|  |                                     hasSitePluginsLoaded = this.sitePluginsProvider.hasSitePluginsLoaded; | ||||||
|  |                                     if (hasSitePluginsLoaded) { | ||||||
|  |                                         // Store the redirect since logout will restart the app.
 | ||||||
|  |                                         this.appProvider.storeRedirect(CoreConstants.NO_SITE_ID, pageName, pageParams); | ||||||
|  |                                     } | ||||||
|  | 
 | ||||||
|  |                                     return this.sitesProvider.logout().catch(() => { | ||||||
|  |                                         // Ignore errors (shouldn't happen).
 | ||||||
|  |                                     }); | ||||||
|  |                                 } | ||||||
|  |                             }); | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         return promise.then(() => { | ||||||
|  |                             if (ssoNeeded) { | ||||||
|  |                                 this.loginHelper.confirmAndOpenBrowserForSSOLogin( | ||||||
|  |                                     result.siteUrl, result.code, result.service, result.config && result.config.launchurl); | ||||||
|  |                             } else if (!hasSitePluginsLoaded) { | ||||||
|  |                                 return this.loginHelper.goToNoSitePage(undefined, pageName, pageParams); | ||||||
|  |                             } | ||||||
|  |                         }); | ||||||
|  | 
 | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |         }).catch((error) => { | ||||||
|  |             if (error && isSSOToken) { | ||||||
|  |                 // An error occurred, display the error and logout the user.
 | ||||||
|  |                 this.loginHelper.treatUserTokenError(data.siteUrl, error); | ||||||
|  |                 this.sitesProvider.logout(); | ||||||
|  |             } else { | ||||||
|  |                 this.domUtils.showErrorModalDefault(error, this.translate.instant('core.login.invalidsite')); | ||||||
|  |             } | ||||||
|  |         }).finally(() => { | ||||||
|  |             modal.dismiss(); | ||||||
|  | 
 | ||||||
|  |             if (isSSOToken) { | ||||||
|  |                 this.appProvider.finishSSOAuthentication(); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the data from a custom URL scheme. The structure of the URL is: | ||||||
|  |      * moodlemobile://username@domain.com?token=TOKEN&privatetoken=PRIVATETOKEN&redirect=http://domain.com/course/view.php?id=2
 | ||||||
|  |      * | ||||||
|  |      * @param {string} url URL to treat. | ||||||
|  |      * @return {Promise<CoreCustomURLSchemesParams>} Promise resolved with the data. | ||||||
|  |      */ | ||||||
|  |     protected getCustomURLData(url: string): Promise<CoreCustomURLSchemesParams> { | ||||||
|  |         const urlScheme = CoreConfigConstants.customurlscheme + '://'; | ||||||
|  |         if (url.indexOf(urlScheme) == -1) { | ||||||
|  |             return Promise.reject(null); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // App opened using custom URL scheme.
 | ||||||
|  |         this.logger.debug('Treating custom URL scheme: ' + url); | ||||||
|  | 
 | ||||||
|  |         // Delete the sso scheme from the URL.
 | ||||||
|  |         url = url.replace(urlScheme, ''); | ||||||
|  | 
 | ||||||
|  |         // Detect if there's a user specified.
 | ||||||
|  |         const username = this.urlUtils.getUsernameFromUrl(url); | ||||||
|  |         if (username) { | ||||||
|  |             url = url.replace(username + '@', ''); // Remove the username from the URL.
 | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Get the params of the URL.
 | ||||||
|  |         const params = this.urlUtils.extractUrlParams(url); | ||||||
|  | 
 | ||||||
|  |         // Remove the params to get the site URL.
 | ||||||
|  |         if (url.indexOf('?') != -1) { | ||||||
|  |             url = url.substr(0, url.indexOf('?')); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return Promise.resolve({ | ||||||
|  |             siteUrl: url, | ||||||
|  |             username: username, | ||||||
|  |             token: params.token, | ||||||
|  |             privateToken: params.privateToken, | ||||||
|  |             redirect: params.redirect | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the data from a "link" custom URL scheme. This kind of URL is deprecated. | ||||||
|  |      * | ||||||
|  |      * @param {string} url URL to treat. | ||||||
|  |      * @return {Promise<CoreCustomURLSchemesParams>} Promise resolved with the data. | ||||||
|  |      */ | ||||||
|  |     protected getCustomURLLinkData(url: string): Promise<CoreCustomURLSchemesParams> { | ||||||
|  |         const contentLinksScheme = CoreConfigConstants.customurlscheme + '://link='; | ||||||
|  |         if (url.indexOf(contentLinksScheme) == -1) { | ||||||
|  |             return Promise.reject(null); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // App opened using custom URL scheme.
 | ||||||
|  |         this.logger.debug('Treating custom URL scheme with link param: ' + url); | ||||||
|  | 
 | ||||||
|  |         // Delete the sso scheme from the URL.
 | ||||||
|  |         url = url.replace(contentLinksScheme, ''); | ||||||
|  | 
 | ||||||
|  |         // Detect if there's a user specified.
 | ||||||
|  |         const username = this.urlUtils.getUsernameFromUrl(url); | ||||||
|  |         if (username) { | ||||||
|  |             url = url.replace(username + '@', ''); // Remove the username from the URL.
 | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // First of all, check if it's the root URL of a site.
 | ||||||
|  |         return this.sitesProvider.isStoredRootURL(url, username).then((data): any => { | ||||||
|  | 
 | ||||||
|  |             if (data.site) { | ||||||
|  |                 // Root URL.
 | ||||||
|  |                 return { | ||||||
|  |                     siteUrl: data.site.getURL(), | ||||||
|  |                     username: username | ||||||
|  |                 }; | ||||||
|  | 
 | ||||||
|  |             } else if (data.siteIds.length > 0) { | ||||||
|  |                 // Not the root URL, but at least 1 site supports the URL. Get the site URL from the list of sites.
 | ||||||
|  |                 return this.sitesProvider.getSite(data.siteIds[0]).then((site) => { | ||||||
|  |                     return { | ||||||
|  |                         siteUrl: site.getURL(), | ||||||
|  |                         username: username, | ||||||
|  |                         redirect: url | ||||||
|  |                     }; | ||||||
|  |                 }); | ||||||
|  | 
 | ||||||
|  |             } else { | ||||||
|  |                 // Get the site URL.
 | ||||||
|  |                 let siteUrl = this.linksDelegate.getSiteUrl(url), | ||||||
|  |                     redirect = url; | ||||||
|  | 
 | ||||||
|  |                 if (!siteUrl) { | ||||||
|  |                     // Site URL not found, use the original URL since it could be the root URL of the site.
 | ||||||
|  |                     siteUrl = url; | ||||||
|  |                     redirect = undefined; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 return { | ||||||
|  |                     siteUrl: siteUrl, | ||||||
|  |                     username: username, | ||||||
|  |                     redirect: redirect | ||||||
|  |                 }; | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the data from a "token" custom URL scheme. This kind of URL is deprecated. | ||||||
|  |      * | ||||||
|  |      * @param {string} url URL to treat. | ||||||
|  |      * @return {Promise<CoreCustomURLSchemesParams>} Promise resolved with the data. | ||||||
|  |      */ | ||||||
|  |     protected getCustomURLTokenData(url: string): Promise<CoreCustomURLSchemesParams> { | ||||||
|  |         const ssoScheme = CoreConfigConstants.customurlscheme + '://token='; | ||||||
|  |         if (url.indexOf(ssoScheme) == -1) { | ||||||
|  |             return Promise.reject(null); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (this.appProvider.isSSOAuthenticationOngoing()) { | ||||||
|  |             // Authentication ongoing, probably duplicated request.
 | ||||||
|  |             return Promise.reject(null); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (this.appProvider.isDesktop()) { | ||||||
|  |             // In desktop, make sure InAppBrowser is closed.
 | ||||||
|  |             this.utils.closeInAppBrowser(true); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // App opened using custom URL scheme. Probably an SSO authentication.
 | ||||||
|  |         this.appProvider.startSSOAuthentication(); | ||||||
|  |         this.logger.debug('App launched by URL with an SSO'); | ||||||
|  | 
 | ||||||
|  |         // Delete the sso scheme from the URL.
 | ||||||
|  |         url = url.replace(ssoScheme, ''); | ||||||
|  | 
 | ||||||
|  |         // Some platforms like Windows add a slash at the end. Remove it.
 | ||||||
|  |         // Some sites add a # at the end of the URL. If it's there, remove it.
 | ||||||
|  |         url = url.replace(/\/?#?\/?$/, ''); | ||||||
|  | 
 | ||||||
|  |         // Decode from base64.
 | ||||||
|  |         try { | ||||||
|  |             url = atob(url); | ||||||
|  |         } catch (err) { | ||||||
|  |             // Error decoding the parameter.
 | ||||||
|  |             this.logger.error('Error decoding parameter received for login SSO'); | ||||||
|  | 
 | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return this.loginHelper.validateBrowserSSOLogin(url); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Check whether a URL is a custom URL scheme. | ||||||
|  |      * | ||||||
|  |      * @param {string} url URL to check. | ||||||
|  |      * @return {boolean} Whether it's a custom URL scheme. | ||||||
|  |      */ | ||||||
|  |     isCustomURL(url: string): boolean { | ||||||
|  |         if (!url) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return url.indexOf(CoreConfigConstants.customurlscheme + '://') != -1; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Check whether a URL is a custom URL scheme with the "link" param (deprecated). | ||||||
|  |      * | ||||||
|  |      * @param {string} url URL to check. | ||||||
|  |      * @return {boolean} Whether it's a custom URL scheme. | ||||||
|  |      */ | ||||||
|  |     isCustomURLLink(url: string): boolean { | ||||||
|  |         if (!url) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return url.indexOf(CoreConfigConstants.customurlscheme + '://link=') != -1; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Check whether a URL is a custom URL scheme with a "token" param (deprecated). | ||||||
|  |      * | ||||||
|  |      * @param {string} url URL to check. | ||||||
|  |      * @return {boolean} Whether it's a custom URL scheme. | ||||||
|  |      */ | ||||||
|  |     isCustomURLToken(url: string): boolean { | ||||||
|  |         if (!url) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return url.indexOf(CoreConfigConstants.customurlscheme + '://token=') != -1; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -52,12 +52,28 @@ export class CoreUrlUtilsProvider { | |||||||
|      */ |      */ | ||||||
|     extractUrlParams(url: string): any { |     extractUrlParams(url: string): any { | ||||||
|         const regex = /[?&]+([^=&]+)=?([^&]*)?/gi, |         const regex = /[?&]+([^=&]+)=?([^&]*)?/gi, | ||||||
|  |             subParamsPlaceholder = '@@@SUBPARAMS@@@', | ||||||
|             params: any = {}, |             params: any = {}, | ||||||
|             urlAndHash = url.split('#'); |             urlAndHash = url.split('#'), | ||||||
|  |             questionMarkSplit = urlAndHash[0].split('?'); | ||||||
|  |         let subParams; | ||||||
|  | 
 | ||||||
|  |         if (questionMarkSplit.length > 2) { | ||||||
|  |             // There is more than one question mark in the URL. This can happen if any of the params is a URL with params.
 | ||||||
|  |             // We only want to treat the first level of params, so we'll remove this second list of params and restore it later.
 | ||||||
|  |             questionMarkSplit.splice(0, 2); | ||||||
|  | 
 | ||||||
|  |             subParams = '?' + questionMarkSplit.join('?'); | ||||||
|  |             urlAndHash[0] = urlAndHash[0].replace(subParams, subParamsPlaceholder); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         urlAndHash[0].replace(regex, (match: string, key: string, value: string): string => { |         urlAndHash[0].replace(regex, (match: string, key: string, value: string): string => { | ||||||
|             params[key] = typeof value != 'undefined' ? value : ''; |             params[key] = typeof value != 'undefined' ? value : ''; | ||||||
| 
 | 
 | ||||||
|  |             if (subParams) { | ||||||
|  |                 params[key] = params[key].replace(subParamsPlaceholder, subParams); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             return match; |             return match; | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user