MOBILE-2995 qr: Improve handling of custom URL scheme links

main
Dani Palou 2019-10-17 11:37:41 +02:00
parent c3f39abafa
commit f9df95c727
6 changed files with 99 additions and 46 deletions

View File

@ -1269,14 +1269,17 @@ export class CoreCourseHelperProvider {
// Get the module. // Get the module.
return this.courseProvider.getModule(moduleId, courseId, sectionId, false, false, siteId, modName); return this.courseProvider.getModule(moduleId, courseId, sectionId, false, false, siteId, modName);
}).then((module) => { }).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. // 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. // 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); this.logger.warn('navCtrl was not passed to navigateToModule by the link handler for ' + module.modname);

View File

@ -17,6 +17,7 @@ import { IonicPage, NavController, ModalController, AlertController, NavParams }
import { CoreAppProvider } from '@providers/app'; import { CoreAppProvider } from '@providers/app';
import { CoreEventsProvider } from '@providers/events'; import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider, CoreSiteCheckResponse, CoreLoginSiteInfo } from '@providers/sites'; import { CoreSitesProvider, CoreSiteCheckResponse, CoreLoginSiteInfo } from '@providers/sites';
import { CoreCustomURLSchemesProvider } from '@providers/urlschemes';
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 { CoreUrlUtilsProvider } from '@providers/utils/url'; import { CoreUrlUtilsProvider } from '@providers/utils/url';
@ -72,7 +73,8 @@ export class CoreLoginSitePage {
protected domUtils: CoreDomUtilsProvider, protected domUtils: CoreDomUtilsProvider,
protected eventsProvider: CoreEventsProvider, protected eventsProvider: CoreEventsProvider,
protected translate: TranslateService, protected translate: TranslateService,
protected utils: CoreUtilsProvider) { protected utils: CoreUtilsProvider,
private urlSchemesProvider: CoreCustomURLSchemesProvider) {
this.showKeyboard = !!navParams.get('showKeyboard'); this.showKeyboard = !!navParams.get('showKeyboard');
this.showScanQR = this.utils.canScanQR(); this.showScanQR = this.utils.canScanQR();
@ -365,9 +367,14 @@ export class CoreLoginSitePage {
// Scan for a QR code. // Scan for a QR code.
this.utils.scanQR().then((text) => { this.utils.scanQR().then((text) => {
if (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);
}
} }
}); });
} }

View File

@ -16,6 +16,7 @@ import { Component, OnDestroy } from '@angular/core';
import { IonicPage, NavController } from 'ionic-angular'; import { IonicPage, NavController } from 'ionic-angular';
import { CoreEventsProvider } from '@providers/events'; import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites'; import { CoreSitesProvider } from '@providers/sites';
import { CoreCustomURLSchemesProvider } from '@providers/urlschemes';
import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreMainMenuDelegate, CoreMainMenuHandlerData } from '../../providers/delegate'; import { CoreMainMenuDelegate, CoreMainMenuHandlerData } from '../../providers/delegate';
@ -50,16 +51,17 @@ export class CoreMainMenuMorePage implements OnDestroy {
protected langObserver; protected langObserver;
protected updateSiteObserver; protected updateSiteObserver;
constructor(private menuDelegate: CoreMainMenuDelegate, constructor(protected menuDelegate: CoreMainMenuDelegate,
private sitesProvider: CoreSitesProvider, protected sitesProvider: CoreSitesProvider,
private navCtrl: NavController, protected navCtrl: NavController,
private mainMenuProvider: CoreMainMenuProvider, protected mainMenuProvider: CoreMainMenuProvider,
eventsProvider: CoreEventsProvider, eventsProvider: CoreEventsProvider,
private loginHelper: CoreLoginHelperProvider, protected loginHelper: CoreLoginHelperProvider,
private utils: CoreUtilsProvider, protected utils: CoreUtilsProvider,
private linkHelper: CoreContentLinksHelperProvider, protected linkHelper: CoreContentLinksHelperProvider,
private textUtils: CoreTextUtilsProvider, protected textUtils: CoreTextUtilsProvider,
private translate: TranslateService) { protected urlSchemesProvider: CoreCustomURLSchemesProvider,
protected translate: TranslateService) {
this.langObserver = eventsProvider.on(CoreEventsProvider.LANGUAGE_CHANGED, this.loadSiteInfo.bind(this)); this.langObserver = eventsProvider.on(CoreEventsProvider.LANGUAGE_CHANGED, this.loadSiteInfo.bind(this));
this.updateSiteObserver = eventsProvider.on(CoreEventsProvider.SITE_UPDATED, 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. // Scan for a QR code.
this.utils.scanQR().then((text) => { this.utils.scanQR().then((text) => {
if (text) { if (text) {
// Check if it's a URL. We basically check it has a protocol and doesn't include any space. if (this.urlSchemesProvider.isCustomURL(text)) {
if (/^[^:]{2,}:\/\/[^ ]+$/i.test(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. // Check if the app can handle the URL.
this.linkHelper.handleLink(text, undefined, this.navCtrl, true, true).then((treated) => { this.linkHelper.handleLink(text, undefined, this.navCtrl, true, true).then((treated) => {
if (!treated) { if (!treated) {

View File

@ -35,6 +35,7 @@ import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { CoreFilterProvider, CoreFilterFilter, CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; import { CoreFilterProvider, CoreFilterFilter, CoreFilterFormatTextOptions } from '@core/filter/providers/filter';
import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper';
import { CoreFilterDelegate } from '@core/filter/providers/delegate'; 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 * 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, protected contentLinksHelper: CoreContentLinksHelperProvider,
@Optional() protected navCtrl: NavController, @Optional() protected navCtrl: NavController,
@Optional() protected content: Content, @Optional() @Optional() protected content: Content, @Optional()
protected svComponent: CoreSplitViewComponent, @Optional() protected svComponent: CoreSplitViewComponent,
protected iframeUtils: CoreIframeUtilsProvider, protected iframeUtils: CoreIframeUtilsProvider,
protected eventsProvider: CoreEventsProvider, protected eventsProvider: CoreEventsProvider,
protected filterProvider: CoreFilterProvider, protected filterProvider: CoreFilterProvider,
protected filterHelper: CoreFilterHelperProvider, protected filterHelper: CoreFilterHelperProvider,
protected filterDelegate: CoreFilterDelegate, protected filterDelegate: CoreFilterDelegate,
protected viewContainerRef: ViewContainerRef, protected viewContainerRef: ViewContainerRef,
protected urlSchemesProvider: CoreCustomURLSchemesProvider
) { ) {
this.element = element.nativeElement; this.element = element.nativeElement;
@ -467,7 +469,7 @@ export class CoreFormatTextDirective implements OnChanges {
anchors.forEach((anchor) => { anchors.forEach((anchor) => {
// Angular 2 doesn't let adding directives dynamically. Create the CoreLinkDirective manually. // 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, 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.capture = true;
linkDir.ngOnInit(); linkDir.ngOnInit();

View File

@ -19,9 +19,9 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreUrlUtilsProvider } from '@providers/utils/url'; import { CoreUrlUtilsProvider } from '@providers/utils/url';
import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
import { CoreConfigConstants } from '../configconstants';
import { CoreSplitViewComponent } from '@components/split-view/split-view'; import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreCustomURLSchemesProvider } from '@providers/urlschemes';
/** /**
* Directive to open a link in external browser. * Directive to open a link in external browser.
@ -39,11 +39,17 @@ export class CoreLinkDirective implements OnInit {
protected element: HTMLElement; protected element: HTMLElement;
constructor(element: ElementRef, private domUtils: CoreDomUtilsProvider, private utils: CoreUtilsProvider, constructor(element: ElementRef,
private sitesProvider: CoreSitesProvider, private urlUtils: CoreUrlUtilsProvider, protected domUtils: CoreDomUtilsProvider,
private contentLinksHelper: CoreContentLinksHelperProvider, @Optional() private navCtrl: NavController, protected utils: CoreUtilsProvider,
@Optional() private content: Content, @Optional() private svComponent: CoreSplitViewComponent, protected sitesProvider: CoreSitesProvider,
private textUtils: CoreTextUtilsProvider) { 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 directive can be added dynamically. In that case, the first param is the anchor HTMLElement.
this.element = element.nativeElement || element; this.element = element.nativeElement || element;
} }
@ -90,7 +96,6 @@ export class CoreLinkDirective implements OnInit {
* @param href HREF to be opened. * @param href HREF to be opened.
*/ */
protected navigate(href: string): void { protected navigate(href: string): void {
const contentLinksScheme = CoreConfigConstants.customurlscheme + '://link=';
if (this.urlUtils.isLocalFileUrl(href)) { if (this.urlUtils.isLocalFileUrl(href)) {
// We have a local file. // We have a local file.
@ -107,10 +112,8 @@ export class CoreLinkDirective implements OnInit {
// Look for id or name. // Look for id or name.
this.domUtils.scrollToElementBySelector(this.content, '#' + href + ', [name=\'' + href + '\']'); this.domUtils.scrollToElementBySelector(this.content, '#' + href + ', [name=\'' + href + '\']');
} }
} else if (href.indexOf(contentLinksScheme) === 0) { } else if (this.urlSchemesProvider.isCustomURL(href)) {
// Link should be treated by Custom URL Scheme. Encode the right part, otherwise ':' is removed in iOS. this.urlSchemesProvider.handleCustomURL(href);
href = contentLinksScheme + encodeURIComponent(href.replace(contentLinksScheme, ''));
this.utils.openInBrowser(href);
} else { } else {
// It's an external link, we will open with browser. Check if we need to auto-login. // It's an external link, we will open with browser. Check if we need to auto-login.

View File

@ -54,12 +54,19 @@ export class CoreCustomURLSchemesProvider {
protected logger; protected logger;
protected lastUrls = {}; protected lastUrls = {};
constructor(logger: CoreLoggerProvider, private appProvider: CoreAppProvider, private utils: CoreUtilsProvider, constructor(logger: CoreLoggerProvider,
private loginHelper: CoreLoginHelperProvider, private linksHelper: CoreContentLinksHelperProvider, protected appProvider: CoreAppProvider,
private initDelegate: CoreInitDelegate, private domUtils: CoreDomUtilsProvider, private urlUtils: CoreUrlUtilsProvider, protected utils: CoreUtilsProvider,
private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider, protected loginHelper: CoreLoginHelperProvider,
private linksDelegate: CoreContentLinksDelegate, private translate: TranslateService, protected linksHelper: CoreContentLinksHelperProvider,
private sitePluginsProvider: CoreSitePluginsProvider) { 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'); this.logger = logger.getInstance('CoreCustomURLSchemesProvider');
} }
@ -282,8 +289,7 @@ export class CoreCustomURLSchemesProvider {
* @return Promise resolved with the data. * @return Promise resolved with the data.
*/ */
protected getCustomURLData(url: string): Promise<CoreCustomURLSchemesParams> { protected getCustomURLData(url: string): Promise<CoreCustomURLSchemesParams> {
const urlScheme = CoreConfigConstants.customurlscheme + '://'; if (!this.isCustomURL(url)) {
if (url.indexOf(urlScheme) == -1) {
return Promise.reject(null); return Promise.reject(null);
} }
@ -291,7 +297,7 @@ export class CoreCustomURLSchemesProvider {
this.logger.debug('Treating custom URL scheme: ' + url); this.logger.debug('Treating custom URL scheme: ' + url);
// Delete the sso scheme from the URL. // Delete the sso scheme from the URL.
url = url.replace(urlScheme, ''); url = this.removeCustomURLScheme(url);
// Detect if there's a user specified. // Detect if there's a user specified.
const username = this.urlUtils.getUsernameFromUrl(url); const username = this.urlUtils.getUsernameFromUrl(url);
@ -344,8 +350,7 @@ export class CoreCustomURLSchemesProvider {
* @return Promise resolved with the data. * @return Promise resolved with the data.
*/ */
protected getCustomURLLinkData(url: string): Promise<CoreCustomURLSchemesParams> { protected getCustomURLLinkData(url: string): Promise<CoreCustomURLSchemesParams> {
const contentLinksScheme = CoreConfigConstants.customurlscheme + '://link='; if (!this.isCustomURLLink(url)) {
if (url.indexOf(contentLinksScheme) == -1) {
return Promise.reject(null); return Promise.reject(null);
} }
@ -353,7 +358,7 @@ export class CoreCustomURLSchemesProvider {
this.logger.debug('Treating custom URL scheme with link param: ' + url); this.logger.debug('Treating custom URL scheme with link param: ' + url);
// Delete the sso scheme from the URL. // Delete the sso scheme from the URL.
url = url.replace(contentLinksScheme, ''); url = this.removeCustomURLLinkScheme(url);
// Detect if there's a user specified. // Detect if there's a user specified.
const username = this.urlUtils.getUsernameFromUrl(url); const username = this.urlUtils.getUsernameFromUrl(url);
@ -408,8 +413,7 @@ export class CoreCustomURLSchemesProvider {
* @return Promise resolved with the data. * @return Promise resolved with the data.
*/ */
protected getCustomURLTokenData(url: string): Promise<CoreCustomURLSchemesParams> { protected getCustomURLTokenData(url: string): Promise<CoreCustomURLSchemesParams> {
const ssoScheme = CoreConfigConstants.customurlscheme + '://token='; if (!this.isCustomURLToken(url)) {
if (url.indexOf(ssoScheme) == -1) {
return Promise.reject(null); return Promise.reject(null);
} }
@ -428,7 +432,7 @@ export class CoreCustomURLSchemesProvider {
this.logger.debug('App launched by URL with an SSO'); this.logger.debug('App launched by URL with an SSO');
// Delete the sso scheme from the URL. // 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 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. // 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; 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) {} export class CoreCustomURLSchemes extends makeSingleton(CoreCustomURLSchemesProvider) {}