MOBILE-3712 core: Implement and use URL schemes service
parent
b5b8e566ac
commit
e4f719957e
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
|
||||
import { AppComponent } from '@/app/app.component';
|
||||
import { CoreApp } from '@services/app';
|
||||
|
@ -31,7 +31,7 @@ describe('AppComponent', () => {
|
|||
beforeEach(() => {
|
||||
mockSingleton(CoreApp, { setStatusBarColor: jest.fn() });
|
||||
mockSingleton(Network, { onChange: () => new Observable() });
|
||||
mockSingleton(Platform, { ready: () => Promise.resolve() });
|
||||
mockSingleton(Platform, { ready: () => Promise.resolve(), resume: new Subject<void>() });
|
||||
mockSingleton(NgZone, { run: jest.fn() });
|
||||
|
||||
navigator = mockSingleton(CoreNavigator, ['navigate']);
|
||||
|
|
|
@ -29,6 +29,10 @@ import { CoreApp } from '@services/app';
|
|||
import { CoreSites } from '@services/sites';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreSubscriptions } from '@singletons/subscriptions';
|
||||
import { CoreWindow } from '@singletons/window';
|
||||
import { CoreCustomURLSchemes } from '@services/urlschemes';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreUrlUtils } from '@services/utils/url';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
|
@ -39,6 +43,9 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||
|
||||
@ViewChild(IonRouterOutlet) outlet?: IonRouterOutlet;
|
||||
|
||||
protected lastUrls: Record<string, number> = {};
|
||||
protected lastInAppUrl?: string;
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*
|
||||
|
@ -51,6 +58,9 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||
* - Note: HideKeyboardFormAccessoryBar has been moved to config.xml.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const win = <any> window;
|
||||
|
||||
CoreEvents.on(CoreEvents.LOGOUT, () => {
|
||||
// Go to sites page when user is logged out.
|
||||
CoreNavigator.navigate('/login/sites', { reset: true });
|
||||
|
@ -80,6 +90,83 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||
CoreLoginHelper.sitePolicyNotAgreed(data.siteId);
|
||||
});
|
||||
|
||||
// Check URLs loaded in any InAppBrowser.
|
||||
CoreEvents.on(CoreEvents.IAB_LOAD_START, (event) => {
|
||||
// URLs with a custom scheme can be prefixed with "http://" or "https://", we need to remove this.
|
||||
const url = event.url.replace(/^https?:\/\//, '');
|
||||
|
||||
if (CoreCustomURLSchemes.isCustomURL(url)) {
|
||||
// Close the browser if it's a valid SSO URL.
|
||||
CoreCustomURLSchemes.handleCustomURL(url).catch((error) => {
|
||||
CoreCustomURLSchemes.treatHandleCustomURLError(error);
|
||||
});
|
||||
CoreUtils.closeInAppBrowser();
|
||||
|
||||
} else if (CoreApp.instance.isAndroid()) {
|
||||
// Check if the URL has a custom URL scheme. In Android they need to be opened manually.
|
||||
const urlScheme = CoreUrlUtils.getUrlProtocol(url);
|
||||
if (urlScheme && urlScheme !== 'file' && urlScheme !== 'cdvfile') {
|
||||
// Open in browser should launch the right app if found and do nothing if not found.
|
||||
CoreUtils.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) {
|
||||
CoreUtils.openInApp(this.lastInAppUrl);
|
||||
} else {
|
||||
// No last URL loaded, close the InAppBrowser.
|
||||
CoreUtils.closeInAppBrowser();
|
||||
}
|
||||
} else {
|
||||
this.lastInAppUrl = url;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Check InAppBrowser closed.
|
||||
CoreEvents.on(CoreEvents.IAB_EXIT, () => {
|
||||
CoreLoginHelper.setWaitingForBrowser(false);
|
||||
this.lastInAppUrl = '';
|
||||
CoreLoginHelper.checkLogout();
|
||||
});
|
||||
|
||||
Platform.resume.subscribe(() => {
|
||||
// Wait a second before setting it to false since in iOS there could be some frozen WS calls.
|
||||
setTimeout(() => {
|
||||
CoreLoginHelper.setWaitingForBrowser(false);
|
||||
CoreLoginHelper.checkLogout();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// Handle app launched with a certain URL (custom URL scheme).
|
||||
win.handleOpenURL = (url: string): void => {
|
||||
// Execute the callback in the Angular zone, so change detection doesn't stop working.
|
||||
NgZone.run(() => {
|
||||
// First check that the URL hasn't been treated a few seconds ago. Sometimes this function is called more than once.
|
||||
if (this.lastUrls[url] && Date.now() - this.lastUrls[url] < 3000) {
|
||||
// Function called more than once, stop.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CoreCustomURLSchemes.isCustomURL(url)) {
|
||||
// Not a custom URL, ignore.
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastUrls[url] = Date.now();
|
||||
|
||||
CoreEvents.trigger(CoreEvents.APP_LAUNCHED_URL, url);
|
||||
CoreCustomURLSchemes.handleCustomURL(url).catch((error) => {
|
||||
CoreCustomURLSchemes.treatHandleCustomURLError(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// "Expose" CoreWindow.open.
|
||||
win.openWindowSafely = (url: string, name?: string): void => {
|
||||
CoreWindow.open(url, name);
|
||||
};
|
||||
|
||||
CoreEvents.on(CoreEvents.LOGIN, async (data: CoreEventSiteData) => {
|
||||
if (data.siteId) {
|
||||
const site = await CoreSites.getSite(data.siteId);
|
||||
|
|
|
@ -47,12 +47,13 @@ import { CoreFileHelperProvider } from '@services/file-helper';
|
|||
import { CoreGeolocationProvider } from '@services/geolocation';
|
||||
import { CoreNavigatorService } from '@services/navigator';
|
||||
import { CoreScreenService } from '@services/screen';
|
||||
import { CoreCustomURLSchemesProvider } from '@services/urlschemes';
|
||||
|
||||
export const CORE_SERVICES: Type<unknown>[] = [
|
||||
CoreAppProvider,
|
||||
CoreConfigProvider,
|
||||
CoreCronDelegateService,
|
||||
// @todo CoreCustomURLSchemesProvider,
|
||||
CoreCustomURLSchemesProvider,
|
||||
CoreDbProvider,
|
||||
CoreFileHelperProvider,
|
||||
CoreFileSessionProvider,
|
||||
|
|
|
@ -23,6 +23,7 @@ import { CoreUtils } from '@services/utils/utils';
|
|||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreConstants } from '@/core/constants';
|
||||
import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper';
|
||||
import { CoreCustomURLSchemes } from '@services/urlschemes';
|
||||
|
||||
/**
|
||||
* Directive to open a link in external browser or in the app.
|
||||
|
@ -111,7 +112,15 @@ export class CoreLinkDirective implements OnInit {
|
|||
return;
|
||||
}
|
||||
|
||||
// @todo: Custom URL schemes.
|
||||
if (CoreCustomURLSchemes.isCustomURL(href)) {
|
||||
try {
|
||||
await CoreCustomURLSchemes.handleCustomURL(href);
|
||||
} catch (error) {
|
||||
CoreCustomURLSchemes.treatHandleCustomURLError(error);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return this.openExternalLink(href, openIn);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,564 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// 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 { CoreError } from '@classes/errors/error';
|
||||
import { CoreWSError } from '@classes/errors/wserror';
|
||||
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
|
||||
import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper';
|
||||
import { CoreLoginHelper, CoreLoginSSOData } from '@features/login/services/login-helper';
|
||||
import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins';
|
||||
import { ApplicationInit, makeSingleton, Translate } from '@singletons';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import { CoreConstants } from '../constants';
|
||||
import { CoreApp } from './app';
|
||||
import { CoreNavigator } from './navigator';
|
||||
import { CoreSiteCheckResponse, CoreSites } from './sites';
|
||||
import { CoreDomUtils } from './utils/dom';
|
||||
import { CoreTextErrorObject, CoreTextUtils } from './utils/text';
|
||||
import { CoreUrlUtils } from './utils/url';
|
||||
import { CoreUtils } from './utils/utils';
|
||||
|
||||
/*
|
||||
* Provider to handle custom URL schemes.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CoreCustomURLSchemesProvider {
|
||||
|
||||
protected logger: CoreLogger;
|
||||
protected lastUrls: Record<string, number> = {};
|
||||
|
||||
constructor() {
|
||||
this.logger = CoreLogger.getInstance('CoreCustomURLSchemesProvider');
|
||||
}
|
||||
|
||||
/**
|
||||
* Given some data of a custom URL with a token, create a site if it needs to be created.
|
||||
*
|
||||
* @param data URL data.
|
||||
* @return Promise resolved with the site ID if created or already exists.
|
||||
*/
|
||||
protected async createSiteIfNeeded(data: CoreCustomURLSchemesParams): Promise<string | undefined> {
|
||||
if (!data.token) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentSite = CoreSites.getCurrentSite();
|
||||
|
||||
if (!currentSite || currentSite.getToken() != data.token) {
|
||||
// Token belongs to a different site, create it. It doesn't matter if it already exists.
|
||||
|
||||
if (!data.siteUrl.match(/^https?:\/\//)) {
|
||||
// URL doesn't have a protocol and it's required to be able to create the site. Check which one to use.
|
||||
const result = await CoreSites.checkSite(data.siteUrl);
|
||||
|
||||
data.siteUrl = result.siteUrl;
|
||||
|
||||
await CoreSites.checkApplication(result);
|
||||
}
|
||||
|
||||
return CoreSites.newSite(
|
||||
data.siteUrl,
|
||||
data.token,
|
||||
data.privateToken,
|
||||
!!data.isSSOToken,
|
||||
CoreLoginHelper.getOAuthIdFromParams(data.ssoUrlParams),
|
||||
);
|
||||
} else {
|
||||
// Token belongs to current site, no need to create it.
|
||||
return CoreSites.getCurrentSiteId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an URL received by custom URL scheme.
|
||||
*
|
||||
* @param url URL to treat.
|
||||
* @return Promise resolved when done. If rejected, the parameter is of type CoreCustomURLSchemesHandleError.
|
||||
*/
|
||||
async handleCustomURL(url: string): Promise<void> {
|
||||
if (!this.isCustomURL(url)) {
|
||||
throw new CoreCustomURLSchemesHandleError(null);
|
||||
}
|
||||
|
||||
/* First check that this URL hasn't been treated a few seconds ago. The function that handles custom URL schemes already
|
||||
does this, but this function is called from other places so we need to handle it in here too. */
|
||||
if (this.lastUrls[url] && Date.now() - this.lastUrls[url] < 3000) {
|
||||
// Function called more than once, stop.
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastUrls[url] = Date.now();
|
||||
url = CoreTextUtils.decodeURIComponent(url);
|
||||
|
||||
// Wait for app to be ready.
|
||||
await ApplicationInit.donePromise;
|
||||
|
||||
// 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(/\/?#?\/?$/, '');
|
||||
|
||||
const modal = await CoreDomUtils.showModalLoading();
|
||||
let data: CoreCustomURLSchemesParams;
|
||||
|
||||
// Get the data from the URL.
|
||||
try {
|
||||
if (this.isCustomURLToken(url)) {
|
||||
data = await this.getCustomURLTokenData(url);
|
||||
} else if (this.isCustomURLLink(url)) {
|
||||
// In iOS, the protocol after the scheme doesn't have ":". Add it.
|
||||
url = url.replace(/\/\/link=(https?)\/\//, '//link=$1://');
|
||||
|
||||
data = await this.getCustomURLLinkData(url);
|
||||
} else {
|
||||
// In iOS, the protocol after the scheme doesn't have ":". Add it.
|
||||
url = url.replace(/\/\/(https?)\/\//, '//$1://');
|
||||
|
||||
data = await this.getCustomURLData(url);
|
||||
}
|
||||
} catch (error) {
|
||||
modal.dismiss();
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
try {
|
||||
const isValid = await CoreLoginHelper.isSiteUrlAllowed(data.siteUrl);
|
||||
|
||||
if (!isValid) {
|
||||
throw Translate.instant('core.errorurlschemeinvalidsite');
|
||||
}
|
||||
|
||||
if (data.redirect && data.redirect.match(/^https?:\/\//) && data.redirect.indexOf(data.siteUrl) == -1) {
|
||||
// Redirect URL must belong to the same site. Reject.
|
||||
throw Translate.instant('core.contentlinks.errorredirectothersite');
|
||||
}
|
||||
|
||||
// First of all, create the site if needed.
|
||||
const siteId = await this.createSiteIfNeeded(data);
|
||||
|
||||
if (data.isSSOToken || (data.isAuthenticationURL && siteId && CoreSites.getCurrentSiteId() == siteId)) {
|
||||
// Site created and authenticated, open the page to go.
|
||||
if (data.pageName) {
|
||||
// Page defined, go to that page instead of site initial page.
|
||||
CoreNavigator.navigateToSitePath(data.pageName, {
|
||||
params: data.pageParams,
|
||||
});
|
||||
} else {
|
||||
CoreNavigator.navigateToSiteHome();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.redirect && !data.redirect.match(/^https?:\/\//)) {
|
||||
// Redirect is a relative URL. Append the site URL.
|
||||
data.redirect = CoreTextUtils.concatenatePaths(data.siteUrl, data.redirect);
|
||||
}
|
||||
|
||||
let siteIds = [siteId];
|
||||
|
||||
if (!siteId) {
|
||||
// No site created, check if the site is stored (to know which one to use).
|
||||
siteIds = await CoreSites.getSiteIdsFromUrl(data.siteUrl, true, data.username);
|
||||
}
|
||||
|
||||
if (siteIds.length > 1) {
|
||||
// More than one site to treat the URL, let the user choose.
|
||||
CoreContentLinksHelper.goToChooseSite(data.redirect || data.siteUrl);
|
||||
|
||||
} else if (siteIds.length == 1) {
|
||||
// Only one site, handle the link.
|
||||
const site = await CoreSites.getSite(siteIds[0]);
|
||||
|
||||
if (!data.redirect) {
|
||||
// No redirect, go to the root URL if needed.
|
||||
await CoreContentLinksHelper.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;
|
||||
|
||||
const treated = await CoreContentLinksHelper.handleLink(data.redirect, username);
|
||||
|
||||
if (!treated) {
|
||||
CoreDomUtils.showErrorModal('core.contentlinks.errornoactions', true);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Site not stored. Try to add the site.
|
||||
const result = await CoreSites.checkSite(data.siteUrl);
|
||||
|
||||
// Site exists. We'll allow to add it.
|
||||
modal.dismiss(); // Dismiss modal so it doesn't collide with confirms.
|
||||
|
||||
await this.goToAddSite(data, result);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
throw new CoreCustomURLSchemesHandleError(error, data);
|
||||
} finally {
|
||||
modal.dismiss();
|
||||
|
||||
if (data.isSSOToken) {
|
||||
CoreApp.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 url URL to treat.
|
||||
* @return Promise resolved with the data.
|
||||
*/
|
||||
protected async getCustomURLData(url: string): Promise<CoreCustomURLSchemesParams> {
|
||||
if (!this.isCustomURL(url)) {
|
||||
throw new CoreCustomURLSchemesHandleError(null);
|
||||
}
|
||||
|
||||
// App opened using custom URL scheme.
|
||||
this.logger.debug('Treating custom URL scheme: ' + url);
|
||||
|
||||
// Delete the sso scheme from the URL.
|
||||
url = this.removeCustomURLScheme(url);
|
||||
|
||||
// Detect if there's a user specified.
|
||||
const username = CoreUrlUtils.getUsernameFromUrl(url);
|
||||
if (username) {
|
||||
url = url.replace(username + '@', ''); // Remove the username from the URL.
|
||||
}
|
||||
|
||||
// Get the params of the URL.
|
||||
const params = CoreUrlUtils.extractUrlParams(url);
|
||||
|
||||
// Remove the params to get the site URL.
|
||||
if (url.indexOf('?') != -1) {
|
||||
url = url.substr(0, url.indexOf('?'));
|
||||
}
|
||||
|
||||
if (!url.match(/https?:\/\//)) {
|
||||
// Url doesn't have a protocol. Check if the site is stored in the app to be able to determine the protocol.
|
||||
const siteIds = await CoreSites.getSiteIdsFromUrl(url, true, username);
|
||||
|
||||
if (siteIds.length) {
|
||||
// There is at least 1 site with this URL. Use it to know the full URL.
|
||||
const site = await CoreSites.getSite(siteIds[0]);
|
||||
|
||||
url = site.getURL();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
siteUrl: url,
|
||||
username: username,
|
||||
token: params.token,
|
||||
privateToken: params.privateToken,
|
||||
redirect: params.redirect,
|
||||
isAuthenticationURL: !!params.token,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data from a "link" custom URL scheme. This kind of URL is deprecated.
|
||||
*
|
||||
* @param url URL to treat.
|
||||
* @return Promise resolved with the data.
|
||||
*/
|
||||
protected async getCustomURLLinkData(url: string): Promise<CoreCustomURLSchemesParams> {
|
||||
if (!this.isCustomURLLink(url)) {
|
||||
throw new CoreCustomURLSchemesHandleError(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 = this.removeCustomURLLinkScheme(url);
|
||||
|
||||
// Detect if there's a user specified.
|
||||
const username = CoreUrlUtils.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.
|
||||
const data = await CoreSites.isStoredRootURL(url, username);
|
||||
|
||||
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.
|
||||
const site = await CoreSites.getSite(data.siteIds[0]);
|
||||
|
||||
return {
|
||||
siteUrl: site.getURL(),
|
||||
username: username,
|
||||
redirect: url,
|
||||
};
|
||||
|
||||
} else {
|
||||
// Get the site URL.
|
||||
let siteUrl = CoreContentLinksDelegate.getSiteUrl(url);
|
||||
let redirect: string | undefined = 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 url URL to treat.
|
||||
* @return Promise resolved with the data.
|
||||
*/
|
||||
protected async getCustomURLTokenData(url: string): Promise<CoreCustomURLSchemesParams> {
|
||||
if (!this.isCustomURLToken(url)) {
|
||||
throw new CoreCustomURLSchemesHandleError(null);
|
||||
}
|
||||
|
||||
if (CoreApp.isSSOAuthenticationOngoing()) {
|
||||
// Authentication ongoing, probably duplicated request.
|
||||
throw new CoreCustomURLSchemesHandleError('Duplicated');
|
||||
}
|
||||
|
||||
// App opened using custom URL scheme. Probably an SSO authentication.
|
||||
CoreApp.startSSOAuthentication();
|
||||
this.logger.debug('App launched by URL with an SSO');
|
||||
|
||||
// Delete the sso scheme from the URL.
|
||||
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.
|
||||
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');
|
||||
|
||||
throw new CoreCustomURLSchemesHandleError(null);
|
||||
}
|
||||
|
||||
const data: CoreCustomURLSchemesParams = await CoreLoginHelper.validateBrowserSSOLogin(url);
|
||||
|
||||
data.isSSOToken = true;
|
||||
data.isAuthenticationURL = true;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Go to page to add a site, or open a browser if SSO.
|
||||
*
|
||||
* @param data URL data.
|
||||
* @param checkResponse Result of checkSite.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async goToAddSite(data: CoreCustomURLSchemesParams, checkResponse: CoreSiteCheckResponse): Promise<void> {
|
||||
const ssoNeeded = CoreLoginHelper.isSSOLoginNeeded(checkResponse.code);
|
||||
const pageParams = {
|
||||
siteUrl: checkResponse.siteUrl,
|
||||
username: data.username,
|
||||
urlToOpen: data.redirect,
|
||||
siteConfig: checkResponse.config,
|
||||
};
|
||||
let hasSitePluginsLoaded = false;
|
||||
|
||||
if (CoreSites.isLoggedIn()) {
|
||||
// Ask the user before changing site.
|
||||
await CoreDomUtils.showConfirm(Translate.instant('core.contentlinks.confirmurlothersite'));
|
||||
|
||||
if (!ssoNeeded) {
|
||||
hasSitePluginsLoaded = CoreSitePlugins.hasSitePluginsLoaded;
|
||||
if (hasSitePluginsLoaded) {
|
||||
// Store the redirect since logout will restart the app.
|
||||
CoreApp.storeRedirect(CoreConstants.NO_SITE_ID, '/login/credentials', pageParams);
|
||||
}
|
||||
|
||||
await CoreSites.logout();
|
||||
}
|
||||
}
|
||||
|
||||
if (ssoNeeded) {
|
||||
CoreLoginHelper.confirmAndOpenBrowserForSSOLogin(
|
||||
checkResponse.siteUrl,
|
||||
checkResponse.code,
|
||||
checkResponse.service,
|
||||
checkResponse.config?.launchurl,
|
||||
);
|
||||
} else if (!hasSitePluginsLoaded) {
|
||||
await CoreNavigator.navigateToLoginCredentials(pageParams);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a URL is a custom URL scheme.
|
||||
*
|
||||
* @param url URL to check.
|
||||
* @return Whether it's a custom URL scheme.
|
||||
*/
|
||||
isCustomURL(url: string): boolean {
|
||||
if (!url) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return url.indexOf(CoreConstants.CONFIG.customurlscheme + '://') != -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a URL is a custom URL scheme with the "link" param (deprecated).
|
||||
*
|
||||
* @param url URL to check.
|
||||
* @return Whether it's a custom URL scheme.
|
||||
*/
|
||||
isCustomURLLink(url: string): boolean {
|
||||
if (!url) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return url.indexOf(CoreConstants.CONFIG.customurlscheme + '://link=') != -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a URL is a custom URL scheme with a "token" param (deprecated).
|
||||
*
|
||||
* @param url URL to check.
|
||||
* @return Whether it's a custom URL scheme.
|
||||
*/
|
||||
isCustomURLToken(url: string): boolean {
|
||||
if (!url) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return url.indexOf(CoreConstants.CONFIG.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(CoreConstants.CONFIG.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(CoreConstants.CONFIG.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(CoreConstants.CONFIG.customurlscheme + '://token=', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Treat error returned by handleCustomURL.
|
||||
*
|
||||
* @param error Error data.
|
||||
*/
|
||||
treatHandleCustomURLError(error: CoreCustomURLSchemesHandleError): void {
|
||||
if (error.error == 'Duplicated') {
|
||||
// Duplicated request
|
||||
} else if (CoreUtils.isWebServiceError(error.error) && error.data && error.data.isSSOToken) {
|
||||
// An error occurred, display the error and logout the user.
|
||||
CoreLoginHelper.treatUserTokenError(error.data.siteUrl, <CoreWSError> error.error);
|
||||
CoreSites.logout();
|
||||
} else {
|
||||
CoreDomUtils.showErrorModalDefault(error.error, Translate.instant('core.login.invalidsite'));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Error returned by handleCustomURL.
|
||||
*/
|
||||
export class CoreCustomURLSchemesHandleError extends CoreError {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param error The error message or object.
|
||||
* @param data Data obtained from the URL (if any).
|
||||
*/
|
||||
constructor(public error: string | CoreError | CoreTextErrorObject | null, public data?: CoreCustomURLSchemesParams) {
|
||||
super(CoreTextUtils.getErrorMessageFromError(error));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const CoreCustomURLSchemes = makeSingleton(CoreCustomURLSchemesProvider);
|
||||
|
||||
/**
|
||||
* All params that can be in a custom URL scheme.
|
||||
*/
|
||||
export interface CoreCustomURLSchemesParams extends CoreLoginSSOData {
|
||||
|
||||
/**
|
||||
* Username.
|
||||
*/
|
||||
username?: string;
|
||||
|
||||
/**
|
||||
* URL to open once authenticated.
|
||||
*/
|
||||
redirect?: string;
|
||||
|
||||
/**
|
||||
* Whether it's an SSO token URL.
|
||||
*/
|
||||
isSSOToken?: boolean;
|
||||
|
||||
/**
|
||||
* Whether the URL is meant to perform an authentication.
|
||||
*/
|
||||
isAuthenticationURL?: boolean;
|
||||
}
|
|
@ -522,10 +522,10 @@ export class CoreTextUtilsProvider {
|
|||
/**
|
||||
* Get the error message from an error object.
|
||||
*
|
||||
* @param error Error object.
|
||||
* @param error Error.
|
||||
* @return Error message, undefined if not found.
|
||||
*/
|
||||
getErrorMessageFromError(error?: string | CoreError | CoreTextErrorObject): string | undefined {
|
||||
getErrorMessageFromError(error?: string | CoreError | CoreTextErrorObject | null): string | undefined {
|
||||
if (typeof error == 'string') {
|
||||
return error;
|
||||
}
|
||||
|
@ -534,7 +534,11 @@ export class CoreTextUtilsProvider {
|
|||
return error.message;
|
||||
}
|
||||
|
||||
return error && (error.message || error.error || error.content || error.body);
|
||||
if (!error) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return error.message || error.error || error.content || error.body;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -362,7 +362,7 @@ export class CoreUrlUtilsProvider {
|
|||
* @param url URL to treat.
|
||||
* @return Username. Undefined if no username found.
|
||||
*/
|
||||
getUsernameFromUrl(url: string): string | void {
|
||||
getUsernameFromUrl(url: string): string | undefined {
|
||||
if (url.indexOf('@') > -1) {
|
||||
// Get URL without protocol.
|
||||
const withoutProtocol = url.replace(/^[^?@/]*:\/\//, '');
|
||||
|
|
|
@ -45,6 +45,7 @@ export interface CoreEventsData {
|
|||
[CoreEvents.COMPLETION_MODULE_VIEWED]: CoreEventCompletionModuleViewedData;
|
||||
[CoreEvents.SECTION_STATUS_CHANGED]: CoreEventSectionStatusChangedData;
|
||||
[CoreEvents.ACTIVITY_DATA_SENT]: CoreEventActivityDataSentData;
|
||||
[CoreEvents.IAB_LOAD_START]: InAppBrowserEvent;
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
Loading…
Reference in New Issue