From f9df95c727dd1dec4387ce291dc269e46fb72151 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 17 Oct 2019 11:37:41 +0200 Subject: [PATCH] MOBILE-2995 qr: Improve handling of custom URL scheme links --- src/core/course/providers/helper.ts | 11 +++-- src/core/login/pages/site/site.ts | 13 ++++-- src/core/mainmenu/pages/more/more.ts | 26 ++++++----- src/directives/format-text.ts | 6 ++- src/directives/link.ts | 25 ++++++----- src/providers/urlschemes.ts | 64 +++++++++++++++++++++------- 6 files changed, 99 insertions(+), 46 deletions(-) diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index b93d3191b..c94e9752e 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -1269,14 +1269,17 @@ export class CoreCourseHelperProvider { // Get the module. return this.courseProvider.getModule(moduleId, courseId, sectionId, false, false, siteId, modName); }).then((module) => { - module.handlerData = this.moduleDelegate.getModuleDataFor(module.modname, module, courseId, sectionId, false); - if (navCtrl && module.handlerData && module.handlerData.action) { + if (navCtrl && this.sitesProvider.isLoggedIn() && this.sitesProvider.getCurrentSiteId() == site.getId()) { // If the link handler for this module passed through navCtrl, we can use the module's handler to navigate cleanly. // Otherwise, we will redirect below. - modal.dismiss(); + module.handlerData = this.moduleDelegate.getModuleDataFor(module.modname, module, courseId, sectionId, false); - return module.handlerData.action(new Event('click'), navCtrl, module, courseId, undefined, modParams); + if (module.handlerData && module.handlerData.action) { + modal.dismiss(); + + return module.handlerData.action(new Event('click'), navCtrl, module, courseId, undefined, modParams); + } } this.logger.warn('navCtrl was not passed to navigateToModule by the link handler for ' + module.modname); diff --git a/src/core/login/pages/site/site.ts b/src/core/login/pages/site/site.ts index 4546cf012..ddf2dcc7c 100644 --- a/src/core/login/pages/site/site.ts +++ b/src/core/login/pages/site/site.ts @@ -17,6 +17,7 @@ import { IonicPage, NavController, ModalController, AlertController, NavParams } import { CoreAppProvider } from '@providers/app'; import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider, CoreSiteCheckResponse, CoreLoginSiteInfo } from '@providers/sites'; +import { CoreCustomURLSchemesProvider } from '@providers/urlschemes'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreUrlUtilsProvider } from '@providers/utils/url'; @@ -72,7 +73,8 @@ export class CoreLoginSitePage { protected domUtils: CoreDomUtilsProvider, protected eventsProvider: CoreEventsProvider, protected translate: TranslateService, - protected utils: CoreUtilsProvider) { + protected utils: CoreUtilsProvider, + private urlSchemesProvider: CoreCustomURLSchemesProvider) { this.showKeyboard = !!navParams.get('showKeyboard'); this.showScanQR = this.utils.canScanQR(); @@ -365,9 +367,14 @@ export class CoreLoginSitePage { // Scan for a QR code. this.utils.scanQR().then((text) => { if (text) { - this.siteForm.controls.siteUrl.setValue(text); + if (this.urlSchemesProvider.isCustomURL(text)) { + this.urlSchemesProvider.handleCustomURL(text); + } else { + // Not a custom URL scheme, put the text in the field. + this.siteForm.controls.siteUrl.setValue(text); - this.connect(new Event('click'), text); + this.connect(new Event('click'), text); + } } }); } diff --git a/src/core/mainmenu/pages/more/more.ts b/src/core/mainmenu/pages/more/more.ts index ebcab3684..bf2fb13af 100644 --- a/src/core/mainmenu/pages/more/more.ts +++ b/src/core/mainmenu/pages/more/more.ts @@ -16,6 +16,7 @@ import { Component, OnDestroy } from '@angular/core'; import { IonicPage, NavController } from 'ionic-angular'; import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider } from '@providers/sites'; +import { CoreCustomURLSchemesProvider } from '@providers/urlschemes'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreMainMenuDelegate, CoreMainMenuHandlerData } from '../../providers/delegate'; @@ -50,16 +51,17 @@ export class CoreMainMenuMorePage implements OnDestroy { protected langObserver; protected updateSiteObserver; - constructor(private menuDelegate: CoreMainMenuDelegate, - private sitesProvider: CoreSitesProvider, - private navCtrl: NavController, - private mainMenuProvider: CoreMainMenuProvider, + constructor(protected menuDelegate: CoreMainMenuDelegate, + protected sitesProvider: CoreSitesProvider, + protected navCtrl: NavController, + protected mainMenuProvider: CoreMainMenuProvider, eventsProvider: CoreEventsProvider, - private loginHelper: CoreLoginHelperProvider, - private utils: CoreUtilsProvider, - private linkHelper: CoreContentLinksHelperProvider, - private textUtils: CoreTextUtilsProvider, - private translate: TranslateService) { + protected loginHelper: CoreLoginHelperProvider, + protected utils: CoreUtilsProvider, + protected linkHelper: CoreContentLinksHelperProvider, + protected textUtils: CoreTextUtilsProvider, + protected urlSchemesProvider: CoreCustomURLSchemesProvider, + protected translate: TranslateService) { this.langObserver = eventsProvider.on(CoreEventsProvider.LANGUAGE_CHANGED, this.loadSiteInfo.bind(this)); this.updateSiteObserver = eventsProvider.on(CoreEventsProvider.SITE_UPDATED, this.loadSiteInfo.bind(this), @@ -175,8 +177,10 @@ export class CoreMainMenuMorePage implements OnDestroy { // Scan for a QR code. this.utils.scanQR().then((text) => { if (text) { - // Check if it's a URL. We basically check it has a protocol and doesn't include any space. - if (/^[^:]{2,}:\/\/[^ ]+$/i.test(text)) { + if (this.urlSchemesProvider.isCustomURL(text)) { + // Is a custom URL scheme, handle it. + this.urlSchemesProvider.handleCustomURL(text); + } else if (/^[^:]{2,}:\/\/[^ ]+$/i.test(text)) { // Check if it's a URL. // Check if the app can handle the URL. this.linkHelper.handleLink(text, undefined, this.navCtrl, true, true).then((treated) => { if (!treated) { diff --git a/src/directives/format-text.ts b/src/directives/format-text.ts index 9dfe8a746..fdec21043 100644 --- a/src/directives/format-text.ts +++ b/src/directives/format-text.ts @@ -35,6 +35,7 @@ import { CoreSplitViewComponent } from '@components/split-view/split-view'; import { CoreFilterProvider, CoreFilterFilter, CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { CoreCustomURLSchemesProvider } from '@providers/urlschemes'; /** * Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective @@ -87,13 +88,14 @@ export class CoreFormatTextDirective implements OnChanges { protected contentLinksHelper: CoreContentLinksHelperProvider, @Optional() protected navCtrl: NavController, @Optional() protected content: Content, @Optional() - protected svComponent: CoreSplitViewComponent, + @Optional() protected svComponent: CoreSplitViewComponent, protected iframeUtils: CoreIframeUtilsProvider, protected eventsProvider: CoreEventsProvider, protected filterProvider: CoreFilterProvider, protected filterHelper: CoreFilterHelperProvider, protected filterDelegate: CoreFilterDelegate, protected viewContainerRef: ViewContainerRef, + protected urlSchemesProvider: CoreCustomURLSchemesProvider ) { this.element = element.nativeElement; @@ -467,7 +469,7 @@ export class CoreFormatTextDirective implements OnChanges { anchors.forEach((anchor) => { // Angular 2 doesn't let adding directives dynamically. Create the CoreLinkDirective manually. const linkDir = new CoreLinkDirective(anchor, this.domUtils, this.utils, this.sitesProvider, this.urlUtils, - this.contentLinksHelper, this.navCtrl, this.content, this.svComponent, this.textUtils); + this.contentLinksHelper, this.navCtrl, this.content, this.svComponent, this.textUtils, this.urlSchemesProvider); linkDir.capture = true; linkDir.ngOnInit(); diff --git a/src/directives/link.ts b/src/directives/link.ts index 5d85e2199..9643f08ac 100644 --- a/src/directives/link.ts +++ b/src/directives/link.ts @@ -19,9 +19,9 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreUrlUtilsProvider } from '@providers/utils/url'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; -import { CoreConfigConstants } from '../configconstants'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { CoreCustomURLSchemesProvider } from '@providers/urlschemes'; /** * Directive to open a link in external browser. @@ -39,11 +39,17 @@ export class CoreLinkDirective implements OnInit { protected element: HTMLElement; - constructor(element: ElementRef, private domUtils: CoreDomUtilsProvider, private utils: CoreUtilsProvider, - private sitesProvider: CoreSitesProvider, private urlUtils: CoreUrlUtilsProvider, - private contentLinksHelper: CoreContentLinksHelperProvider, @Optional() private navCtrl: NavController, - @Optional() private content: Content, @Optional() private svComponent: CoreSplitViewComponent, - private textUtils: CoreTextUtilsProvider) { + constructor(element: ElementRef, + protected domUtils: CoreDomUtilsProvider, + protected utils: CoreUtilsProvider, + protected sitesProvider: CoreSitesProvider, + protected urlUtils: CoreUrlUtilsProvider, + protected contentLinksHelper: CoreContentLinksHelperProvider, + @Optional() protected navCtrl: NavController, + @Optional() protected content: Content, + @Optional() protected svComponent: CoreSplitViewComponent, + protected textUtils: CoreTextUtilsProvider, + protected urlSchemesProvider: CoreCustomURLSchemesProvider) { // This directive can be added dynamically. In that case, the first param is the anchor HTMLElement. this.element = element.nativeElement || element; } @@ -90,7 +96,6 @@ export class CoreLinkDirective implements OnInit { * @param href HREF to be opened. */ protected navigate(href: string): void { - const contentLinksScheme = CoreConfigConstants.customurlscheme + '://link='; if (this.urlUtils.isLocalFileUrl(href)) { // We have a local file. @@ -107,10 +112,8 @@ export class CoreLinkDirective implements OnInit { // Look for id or name. this.domUtils.scrollToElementBySelector(this.content, '#' + href + ', [name=\'' + href + '\']'); } - } else if (href.indexOf(contentLinksScheme) === 0) { - // Link should be treated by Custom URL Scheme. Encode the right part, otherwise ':' is removed in iOS. - href = contentLinksScheme + encodeURIComponent(href.replace(contentLinksScheme, '')); - this.utils.openInBrowser(href); + } else if (this.urlSchemesProvider.isCustomURL(href)) { + this.urlSchemesProvider.handleCustomURL(href); } else { // It's an external link, we will open with browser. Check if we need to auto-login. diff --git a/src/providers/urlschemes.ts b/src/providers/urlschemes.ts index 5c22508d2..d2a2f9526 100644 --- a/src/providers/urlschemes.ts +++ b/src/providers/urlschemes.ts @@ -54,12 +54,19 @@ export class CoreCustomURLSchemesProvider { protected logger; protected lastUrls = {}; - 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) { + constructor(logger: CoreLoggerProvider, + protected appProvider: CoreAppProvider, + protected utils: CoreUtilsProvider, + protected loginHelper: CoreLoginHelperProvider, + protected linksHelper: CoreContentLinksHelperProvider, + protected initDelegate: CoreInitDelegate, + protected domUtils: CoreDomUtilsProvider, + protected urlUtils: CoreUrlUtilsProvider, + protected sitesProvider: CoreSitesProvider, + protected textUtils: CoreTextUtilsProvider, + protected linksDelegate: CoreContentLinksDelegate, + protected translate: TranslateService, + protected sitePluginsProvider: CoreSitePluginsProvider) { this.logger = logger.getInstance('CoreCustomURLSchemesProvider'); } @@ -282,8 +289,7 @@ export class CoreCustomURLSchemesProvider { * @return Promise resolved with the data. */ protected getCustomURLData(url: string): Promise { - const urlScheme = CoreConfigConstants.customurlscheme + '://'; - if (url.indexOf(urlScheme) == -1) { + if (!this.isCustomURL(url)) { return Promise.reject(null); } @@ -291,7 +297,7 @@ export class CoreCustomURLSchemesProvider { this.logger.debug('Treating custom URL scheme: ' + url); // Delete the sso scheme from the URL. - url = url.replace(urlScheme, ''); + url = this.removeCustomURLScheme(url); // Detect if there's a user specified. const username = this.urlUtils.getUsernameFromUrl(url); @@ -344,8 +350,7 @@ export class CoreCustomURLSchemesProvider { * @return Promise resolved with the data. */ protected getCustomURLLinkData(url: string): Promise { - const contentLinksScheme = CoreConfigConstants.customurlscheme + '://link='; - if (url.indexOf(contentLinksScheme) == -1) { + if (!this.isCustomURLLink(url)) { return Promise.reject(null); } @@ -353,7 +358,7 @@ export class CoreCustomURLSchemesProvider { this.logger.debug('Treating custom URL scheme with link param: ' + url); // Delete the sso scheme from the URL. - url = url.replace(contentLinksScheme, ''); + url = this.removeCustomURLLinkScheme(url); // Detect if there's a user specified. const username = this.urlUtils.getUsernameFromUrl(url); @@ -408,8 +413,7 @@ export class CoreCustomURLSchemesProvider { * @return Promise resolved with the data. */ protected getCustomURLTokenData(url: string): Promise { - const ssoScheme = CoreConfigConstants.customurlscheme + '://token='; - if (url.indexOf(ssoScheme) == -1) { + if (!this.isCustomURLToken(url)) { return Promise.reject(null); } @@ -428,7 +432,7 @@ export class CoreCustomURLSchemesProvider { this.logger.debug('App launched by URL with an SSO'); // Delete the sso scheme from the URL. - url = url.replace(ssoScheme, ''); + url = this.removeCustomURLTokenScheme(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. @@ -488,6 +492,36 @@ export class CoreCustomURLSchemesProvider { return url.indexOf(CoreConfigConstants.customurlscheme + '://token=') != -1; } + + /** + * Remove the scheme from a custom URL. + * + * @param url URL to treat. + * @return URL without scheme. + */ + removeCustomURLScheme(url: string): string { + return url.replace(CoreConfigConstants.customurlscheme + '://', ''); + } + + /** + * Remove the scheme and the "link=" prefix from a link custom URL. + * + * @param url URL to treat. + * @return URL without scheme and prefix. + */ + removeCustomURLLinkScheme(url: string): string { + return url.replace(CoreConfigConstants.customurlscheme + '://link=', ''); + } + + /** + * Remove the scheme and the "token=" prefix from a token custom URL. + * + * @param url URL to treat. + * @return URL without scheme and prefix. + */ + removeCustomURLTokenScheme(url: string): string { + return url.replace(CoreConfigConstants.customurlscheme + '://token=', ''); + } } export class CoreCustomURLSchemes extends makeSingleton(CoreCustomURLSchemesProvider) {}