forked from CIT/Vmeda.Online
		
	
						commit
						bdd787cd07
					
				@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1018,9 +1018,9 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
 | 
			
		||||
        const text = await CoreUtils.scanQR();
 | 
			
		||||
 | 
			
		||||
        if (text) {
 | 
			
		||||
            this.editorElement?.focus(); // Make sure the editor is focused.
 | 
			
		||||
            document.execCommand('insertText', false, text);
 | 
			
		||||
        }
 | 
			
		||||
        // this.content.resize(); // Resize content, otherwise the content height becomes 1 for some reason.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -18,8 +18,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
 | 
			
		||||
import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreLoginHelper, CoreLoginHelperProvider } from '@features/login/services/login-helper';
 | 
			
		||||
import { CoreLoginHelper } from '@features/login/services/login-helper';
 | 
			
		||||
import { CoreConstants } from '@/core/constants';
 | 
			
		||||
import { Translate } from '@singletons';
 | 
			
		||||
import { CoreSiteIdentityProvider, CoreSitePublicConfigResponse } from '@classes/site';
 | 
			
		||||
@ -50,7 +49,7 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
 | 
			
		||||
    isBrowserSSO = false;
 | 
			
		||||
    isFixedUrlSet = false;
 | 
			
		||||
    showForgottenPassword = true;
 | 
			
		||||
    showScanQR: boolean;
 | 
			
		||||
    showScanQR = false;
 | 
			
		||||
 | 
			
		||||
    protected siteConfig?: CoreSitePublicConfigResponse;
 | 
			
		||||
    protected eventThrown = false;
 | 
			
		||||
@ -60,19 +59,7 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        protected fb: FormBuilder,
 | 
			
		||||
    ) {
 | 
			
		||||
 | 
			
		||||
        const canScanQR = CoreUtils.canScanQR();
 | 
			
		||||
        if (canScanQR) {
 | 
			
		||||
            if (typeof CoreConstants.CONFIG.displayqroncredentialscreen == 'undefined') {
 | 
			
		||||
                this.showScanQR = CoreLoginHelper.isFixedUrlSet();
 | 
			
		||||
            } else {
 | 
			
		||||
                this.showScanQR = !!CoreConstants.CONFIG.displayqroncredentialscreen;
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            this.showScanQR = false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initialize the component.
 | 
			
		||||
@ -91,6 +78,7 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
 | 
			
		||||
        this.logoUrl = !CoreConstants.CONFIG.forceLoginLogo && CoreNavigator.getRouteParam('logoUrl') || undefined;
 | 
			
		||||
        this.siteConfig = CoreNavigator.getRouteParam('siteConfig');
 | 
			
		||||
        this.urlToOpen = CoreNavigator.getRouteParam('urlToOpen');
 | 
			
		||||
        this.showScanQR = CoreLoginHelper.displayQRInCredentialsScreen();
 | 
			
		||||
 | 
			
		||||
        this.credForm = this.fb.group({
 | 
			
		||||
            username: [CoreNavigator.getRouteParam<string>('username') || '', Validators.required],
 | 
			
		||||
@ -285,37 +273,17 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Show instructions and scan QR code.
 | 
			
		||||
     */
 | 
			
		||||
    showInstructionsAndScanQR(): void {
 | 
			
		||||
        // Show some instructions first.
 | 
			
		||||
        CoreDomUtils.showAlertWithOptions({
 | 
			
		||||
            header: Translate.instant('core.login.faqwhereisqrcode'),
 | 
			
		||||
            message: Translate.instant(
 | 
			
		||||
                'core.login.faqwhereisqrcodeanswer',
 | 
			
		||||
                { $image: CoreLoginHelperProvider.FAQ_QRCODE_IMAGE_HTML },
 | 
			
		||||
            ),
 | 
			
		||||
            buttons: [
 | 
			
		||||
                {
 | 
			
		||||
                    text: Translate.instant('core.cancel'),
 | 
			
		||||
                    role: 'cancel',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    text: Translate.instant('core.next'),
 | 
			
		||||
                    handler: (): void => {
 | 
			
		||||
                        this.scanQR();
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Scan a QR code and put its text in the URL input.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async scanQR(): Promise<void> {
 | 
			
		||||
        // @todo Scan for a QR code.
 | 
			
		||||
    async showInstructionsAndScanQR(): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            await CoreLoginHelper.showScanQRInstructions();
 | 
			
		||||
 | 
			
		||||
            await CoreLoginHelper.scanQR();
 | 
			
		||||
        } catch {
 | 
			
		||||
            // Ignore errors.
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -61,6 +61,14 @@
 | 
			
		||||
                </ion-col>
 | 
			
		||||
            </ion-row>
 | 
			
		||||
        </ion-grid>
 | 
			
		||||
 | 
			
		||||
        <ng-container *ngIf="showScanQR">
 | 
			
		||||
            <div class="ion-text-center ion-padding">{{ 'core.login.or' | translate }}</div>
 | 
			
		||||
            <ion-button expand="block" color="light" class="ion-margin" lines="none" (click)="showInstructionsAndScanQR()">
 | 
			
		||||
                <ion-icon slot="start" name="fas-qrcode" aria-hidden="true"></ion-icon>
 | 
			
		||||
                <ion-label>{{ 'core.scanqr' | translate }}</ion-label>
 | 
			
		||||
            </ion-button>
 | 
			
		||||
        </ng-container>
 | 
			
		||||
    </form>
 | 
			
		||||
 | 
			
		||||
    <!-- Forgotten password option. -->
 | 
			
		||||
 | 
			
		||||
@ -51,6 +51,7 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
 | 
			
		||||
    isOAuth = false;
 | 
			
		||||
    isLoggedOut: boolean;
 | 
			
		||||
    siteId!: string;
 | 
			
		||||
    showScanQR = false;
 | 
			
		||||
 | 
			
		||||
    protected page?: string;
 | 
			
		||||
    protected pageParams?: Params;
 | 
			
		||||
@ -82,6 +83,7 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
 | 
			
		||||
        this.siteUrl = siteId;
 | 
			
		||||
        this.page = CoreNavigator.getRouteParam('pageName');
 | 
			
		||||
        this.pageParams = CoreNavigator.getRouteParam('pageParams');
 | 
			
		||||
        this.showScanQR = CoreLoginHelper.displayQRInSiteScreen() || CoreLoginHelper.displayQRInCredentialsScreen();
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            const site = await CoreSites.getSite(this.siteId);
 | 
			
		||||
@ -246,4 +248,19 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Show instructions and scan QR code.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async showInstructionsAndScanQR(): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            await CoreLoginHelper.showScanQRInstructions();
 | 
			
		||||
 | 
			
		||||
            await CoreLoginHelper.scanQR();
 | 
			
		||||
        } catch {
 | 
			
		||||
            // Ignore errors.
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -30,6 +30,8 @@ import { CoreUrlUtils } from '@services/utils/url';
 | 
			
		||||
import { CoreLoginSiteHelpComponent } from '@features/login/components/site-help/site-help';
 | 
			
		||||
import { CoreLoginSiteOnboardingComponent } from '@features/login/components/site-onboarding/site-onboarding';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreCustomURLSchemes, CoreCustomURLSchemesHandleError } from '@services/urlschemes';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Page that displays a "splash screen" while the app is being initialized.
 | 
			
		||||
@ -82,8 +84,7 @@ export class CoreLoginSitePage implements OnInit {
 | 
			
		||||
            this.initOnboarding();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.showScanQR = CoreUtils.canScanQR() && (typeof CoreConstants.CONFIG.displayqronsitescreen == 'undefined' ||
 | 
			
		||||
            !!CoreConstants.CONFIG.displayqronsitescreen);
 | 
			
		||||
        this.showScanQR = CoreLoginHelper.displayQRInSiteScreen();
 | 
			
		||||
 | 
			
		||||
        this.siteForm = this.formBuilder.group({
 | 
			
		||||
            siteUrl: [url, this.moodleUrlValidator()],
 | 
			
		||||
@ -464,28 +465,17 @@ export class CoreLoginSitePage implements OnInit {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Show instructions and scan QR code.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    showInstructionsAndScanQR(): void {
 | 
			
		||||
        // Show some instructions first.
 | 
			
		||||
        CoreDomUtils.showAlertWithOptions({
 | 
			
		||||
            header: Translate.instant('core.login.faqwhereisqrcode'),
 | 
			
		||||
            message: Translate.instant(
 | 
			
		||||
                'core.login.faqwhereisqrcodeanswer',
 | 
			
		||||
                { $image: CoreLoginHelperProvider.FAQ_QRCODE_IMAGE_HTML },
 | 
			
		||||
            ),
 | 
			
		||||
            buttons: [
 | 
			
		||||
                {
 | 
			
		||||
                    text: Translate.instant('core.cancel'),
 | 
			
		||||
                    role: 'cancel',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    text: Translate.instant('core.next'),
 | 
			
		||||
                    handler: (): void => {
 | 
			
		||||
                        this.scanQR();
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        });
 | 
			
		||||
    async showInstructionsAndScanQR(): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            await CoreLoginHelper.showScanQRInstructions();
 | 
			
		||||
 | 
			
		||||
            await this.scanQR();
 | 
			
		||||
        } catch {
 | 
			
		||||
            // Ignore errors.
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -497,9 +487,83 @@ export class CoreLoginSitePage implements OnInit {
 | 
			
		||||
        // Scan for a QR code.
 | 
			
		||||
        const text = await CoreUtils.scanQR();
 | 
			
		||||
 | 
			
		||||
        if (text) {
 | 
			
		||||
            // @todo
 | 
			
		||||
        if (!text) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (CoreCustomURLSchemes.isCustomURL(text)) {
 | 
			
		||||
            try {
 | 
			
		||||
                await CoreCustomURLSchemes.handleCustomURL(text);
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                if (error && error.data && error.data.isAuthenticationURL && error.data.siteUrl) {
 | 
			
		||||
                    // An error ocurred, but it's an authentication URL and we have the site URL.
 | 
			
		||||
                    this.treatErrorInAuthenticationCustomURL(text, error);
 | 
			
		||||
                } else {
 | 
			
		||||
                    CoreCustomURLSchemes.treatHandleCustomURLError(error);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Not a custom URL scheme, check if it's a URL scheme to another app.
 | 
			
		||||
        const scheme = CoreUrlUtils.getUrlProtocol(text);
 | 
			
		||||
 | 
			
		||||
        if (scheme && scheme != 'http' && scheme != 'https') {
 | 
			
		||||
            CoreDomUtils.showErrorModal(Translate.instant('core.errorurlschemeinvalidscheme', { $a: text }));
 | 
			
		||||
        } else if (CoreLoginHelper.isSiteUrlAllowed(text)) {
 | 
			
		||||
            // Put the text in the field (if present).
 | 
			
		||||
            this.siteForm.controls.siteUrl.setValue(text);
 | 
			
		||||
 | 
			
		||||
            this.connect(new Event('click'), text);
 | 
			
		||||
        } else {
 | 
			
		||||
            CoreDomUtils.showErrorModal('core.errorurlschemeinvalidsite', true);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Treat an error while handling a custom URL meant to perform an authentication.
 | 
			
		||||
     * If the site doesn't use SSO, the user will be sent to the credentials screen.
 | 
			
		||||
     *
 | 
			
		||||
     * @param customURL Custom URL handled.
 | 
			
		||||
     * @param error Error data.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async treatErrorInAuthenticationCustomURL(customURL: string, error: CoreCustomURLSchemesHandleError): Promise<void> {
 | 
			
		||||
        const siteUrl = error.data?.siteUrl || '';
 | 
			
		||||
        const modal = await CoreDomUtils.showModalLoading();
 | 
			
		||||
 | 
			
		||||
        // Set the site URL in the input.
 | 
			
		||||
        this.siteForm.controls.siteUrl.setValue(siteUrl);
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            // Check if site uses SSO.
 | 
			
		||||
            const response = await CoreSites.checkSite(siteUrl);
 | 
			
		||||
 | 
			
		||||
            await CoreSites.checkApplication(response);
 | 
			
		||||
 | 
			
		||||
            if (!CoreLoginHelper.isSSOLoginNeeded(response.code)) {
 | 
			
		||||
                // No SSO, go to credentials page.
 | 
			
		||||
                await CoreNavigator.navigate('/login/credentials', {
 | 
			
		||||
                    params: {
 | 
			
		||||
                        siteUrl: response.siteUrl,
 | 
			
		||||
                        siteConfig: response.config,
 | 
			
		||||
                    },
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            // Ignore errors.
 | 
			
		||||
        } finally {
 | 
			
		||||
            modal.dismiss();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Now display the error.
 | 
			
		||||
        error.error = CoreTextUtils.addTextToError(
 | 
			
		||||
            error.error,
 | 
			
		||||
            '<br><br>' + Translate.instant('core.login.youcanstillconnectwithcredentials'),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        CoreCustomURLSchemes.treatHandleCustomURLError(error);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -33,6 +33,8 @@ import { makeSingleton, Translate } from '@singletons';
 | 
			
		||||
import { CoreLogger } from '@singletons/logger';
 | 
			
		||||
import { CoreUrl } from '@singletons/url';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreCanceledError } from '@classes/errors/cancelederror';
 | 
			
		||||
import { CoreCustomURLSchemes } from '@services/urlschemes';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Helper provider that provides some common features regarding authentication.
 | 
			
		||||
@ -53,7 +55,7 @@ export class CoreLoginHelperProvider {
 | 
			
		||||
    protected isSSOConfirmShown = false;
 | 
			
		||||
    protected isOpenEditAlertShown = false;
 | 
			
		||||
    protected isOpeningReconnect = false;
 | 
			
		||||
    waitingForBrowser = false;
 | 
			
		||||
    protected waitingForBrowser = false;
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this.logger = CoreLogger.getInstance('CoreLoginHelper');
 | 
			
		||||
@ -319,7 +321,7 @@ export class CoreLoginHelperProvider {
 | 
			
		||||
     * @param params Params.
 | 
			
		||||
     * @return OAuth ID.
 | 
			
		||||
     */
 | 
			
		||||
    getOAuthIdFromParams(params: CoreUrlParams): number | undefined {
 | 
			
		||||
    getOAuthIdFromParams(params?: CoreUrlParams): number | undefined {
 | 
			
		||||
        return params && typeof params.oauthsso != 'undefined' ? Number(params.oauthsso) : undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1221,6 +1223,110 @@ export class CoreLoginHelperProvider {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return whether the app is waiting for browser.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Whether the app is waiting for browser.
 | 
			
		||||
     */
 | 
			
		||||
    isWaitingForBrowser(): boolean {
 | 
			
		||||
        return this.waitingForBrowser;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set whether the app is waiting for browser.
 | 
			
		||||
     *
 | 
			
		||||
     * @param value New value.
 | 
			
		||||
     */
 | 
			
		||||
    setWaitingForBrowser(value: boolean): void {
 | 
			
		||||
        this.waitingForBrowser = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check whether the QR reader should be displayed in site screen.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Whether the QR reader should be displayed in site screen.
 | 
			
		||||
     */
 | 
			
		||||
    displayQRInSiteScreen(): boolean {
 | 
			
		||||
        return CoreUtils.canScanQR() && (typeof CoreConstants.CONFIG.displayqronsitescreen == 'undefined' ||
 | 
			
		||||
            !!CoreConstants.CONFIG.displayqronsitescreen);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check whether the QR reader should be displayed in credentials screen.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Whether the QR reader should be displayed in credentials screen.
 | 
			
		||||
     */
 | 
			
		||||
    displayQRInCredentialsScreen(): boolean {
 | 
			
		||||
        if (!CoreUtils.canScanQR()) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return (CoreConstants.CONFIG.displayqroncredentialscreen === undefined && this.isFixedUrlSet()) ||
 | 
			
		||||
            (CoreConstants.CONFIG.displayqroncredentialscreen !== undefined && !!CoreConstants.CONFIG.displayqroncredentialscreen);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Show instructions to scan QR code.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved if the user accepts to scan QR.
 | 
			
		||||
     */
 | 
			
		||||
    showScanQRInstructions(): Promise<void> {
 | 
			
		||||
        const deferred = CoreUtils.promiseDefer<void>();
 | 
			
		||||
 | 
			
		||||
        // Show some instructions first.
 | 
			
		||||
        CoreDomUtils.showAlertWithOptions({
 | 
			
		||||
            header: Translate.instant('core.login.faqwhereisqrcode'),
 | 
			
		||||
            message: Translate.instant(
 | 
			
		||||
                'core.login.faqwhereisqrcodeanswer',
 | 
			
		||||
                { $image: CoreLoginHelperProvider.FAQ_QRCODE_IMAGE_HTML },
 | 
			
		||||
            ),
 | 
			
		||||
            buttons: [
 | 
			
		||||
                {
 | 
			
		||||
                    text: Translate.instant('core.cancel'),
 | 
			
		||||
                    role: 'cancel',
 | 
			
		||||
                    handler: (): void => {
 | 
			
		||||
                        deferred.reject(new CoreCanceledError());
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    text: Translate.instant('core.next'),
 | 
			
		||||
                    handler: (): void => {
 | 
			
		||||
                        deferred.resolve();
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return deferred.promise;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Scan a QR code and tries to authenticate the user using custom URL scheme.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async scanQR(): Promise<void> {
 | 
			
		||||
        // Scan for a QR code.
 | 
			
		||||
        const text = await CoreUtils.scanQR();
 | 
			
		||||
 | 
			
		||||
        if (text && CoreCustomURLSchemes.isCustomURL(text)) {
 | 
			
		||||
            try {
 | 
			
		||||
                await CoreCustomURLSchemes.handleCustomURL(text);
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                CoreCustomURLSchemes.treatHandleCustomURLError(error);
 | 
			
		||||
            }
 | 
			
		||||
        } else if (text) {
 | 
			
		||||
            // Not a custom URL scheme, check if it's a URL scheme to another app.
 | 
			
		||||
            const scheme = CoreUrlUtils.getUrlProtocol(text);
 | 
			
		||||
 | 
			
		||||
            if (scheme && scheme != 'http' && scheme != 'https') {
 | 
			
		||||
                CoreDomUtils.showErrorModal(Translate.instant('core.errorurlschemeinvalidscheme', { $a: text }));
 | 
			
		||||
            } else {
 | 
			
		||||
                CoreDomUtils.showErrorModal('core.login.errorqrnoscheme', true);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const CoreLoginHelper = makeSingleton(CoreLoginHelperProvider);
 | 
			
		||||
 | 
			
		||||
@ -23,6 +23,10 @@ import { CoreMainMenuDelegate, CoreMainMenuHandlerData } from '../../services/ma
 | 
			
		||||
import { CoreMainMenu, CoreMainMenuCustomItem } from '../../services/mainmenu';
 | 
			
		||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreCustomURLSchemes } from '@services/urlschemes';
 | 
			
		||||
import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { Translate } from '@singletons';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Page that displays the main menu of the app.
 | 
			
		||||
@ -154,10 +158,31 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy {
 | 
			
		||||
     */
 | 
			
		||||
    async scanQR(): Promise<void> {
 | 
			
		||||
        // Scan for a QR code.
 | 
			
		||||
        // @todo
 | 
			
		||||
        // eslint-disable-next-line no-console
 | 
			
		||||
        console.error('scanQR not implemented');
 | 
			
		||||
        const text = await CoreUtils.scanQR();
 | 
			
		||||
 | 
			
		||||
        if (!text) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (CoreCustomURLSchemes.isCustomURL(text)) {
 | 
			
		||||
            // Is a custom URL scheme, handle it.
 | 
			
		||||
            CoreCustomURLSchemes.handleCustomURL(text).catch((error) => {
 | 
			
		||||
                CoreCustomURLSchemes.treatHandleCustomURLError(error);
 | 
			
		||||
            });
 | 
			
		||||
        } else if (/^[^:]{2,}:\/\/[^ ]+$/i.test(text)) { // Check if it's a URL.
 | 
			
		||||
            // Check if the app can handle the URL.
 | 
			
		||||
            const treated = await CoreContentLinksHelper.handleLink(text, undefined, true, true);
 | 
			
		||||
 | 
			
		||||
            if (!treated) {
 | 
			
		||||
                // Can't handle it, open it in browser.
 | 
			
		||||
                CoreSites.getCurrentSite()?.openInBrowserWithAutoLoginIfSameSite(text);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // It's not a URL, open it in a modal so the user can see it and copy it.
 | 
			
		||||
            CoreTextUtils.viewText(Translate.instant('core.qrscanner'), text, {
 | 
			
		||||
                displayCopyButton: true,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -16,11 +16,13 @@ import { NgModule } from '@angular/core';
 | 
			
		||||
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { CoreViewerImageComponent } from './image/image';
 | 
			
		||||
import { CoreViewerQRScannerComponent } from './qr-scanner/qr-scanner';
 | 
			
		||||
import { CoreViewerTextComponent } from './text/text';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    declarations: [
 | 
			
		||||
        CoreViewerImageComponent,
 | 
			
		||||
        CoreViewerQRScannerComponent,
 | 
			
		||||
        CoreViewerTextComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    imports: [
 | 
			
		||||
@ -28,6 +30,7 @@ import { CoreViewerTextComponent } from './text/text';
 | 
			
		||||
    ],
 | 
			
		||||
    exports: [
 | 
			
		||||
        CoreViewerImageComponent,
 | 
			
		||||
        CoreViewerQRScannerComponent,
 | 
			
		||||
        CoreViewerTextComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,12 @@
 | 
			
		||||
<ion-header>
 | 
			
		||||
    <ion-toolbar>
 | 
			
		||||
        <ion-title>{{ title }}</ion-title>
 | 
			
		||||
        <ion-buttons slot="end">
 | 
			
		||||
            <ion-button (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
 | 
			
		||||
                <ion-icon name="fas-times" slot="icon-only"></ion-icon>
 | 
			
		||||
            </ion-button>
 | 
			
		||||
        </ion-buttons>
 | 
			
		||||
    </ion-toolbar>
 | 
			
		||||
</ion-header>
 | 
			
		||||
<ion-content>
 | 
			
		||||
</ion-content>
 | 
			
		||||
							
								
								
									
										80
									
								
								src/core/features/viewer/components/qr-scanner/qr-scanner.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/core/features/viewer/components/qr-scanner/qr-scanner.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,80 @@
 | 
			
		||||
// (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 { Component, Input, OnDestroy, OnInit } from '@angular/core';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { ModalController, Translate } from '@singletons';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Page to scan a QR code.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'core-viewer-qr-scanner',
 | 
			
		||||
    templateUrl: 'qr-scanner.html',
 | 
			
		||||
})
 | 
			
		||||
export class CoreViewerQRScannerComponent implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
    @Input() title?: string; // Page title.
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async ngOnInit(): Promise<void> {
 | 
			
		||||
        this.title = this.title || Translate.instant('core.scanqr');
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
 | 
			
		||||
            let text = await CoreUtils.startScanQR();
 | 
			
		||||
 | 
			
		||||
            // Text captured, return it.
 | 
			
		||||
            text = typeof text == 'string' ? text.trim() : '';
 | 
			
		||||
 | 
			
		||||
            this.closeModal(text);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            if (!CoreDomUtils.isCanceledError(error)) {
 | 
			
		||||
                // Show error and stop scanning.
 | 
			
		||||
                CoreDomUtils.showErrorModalDefault(error, 'An error occurred.');
 | 
			
		||||
                CoreUtils.stopScanQR();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.closeModal();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Cancel scanning.
 | 
			
		||||
     */
 | 
			
		||||
    cancel(): void {
 | 
			
		||||
        CoreUtils.stopScanQR();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Close modal.
 | 
			
		||||
     *
 | 
			
		||||
     * @param text The text to return (if any).
 | 
			
		||||
     */
 | 
			
		||||
    closeModal(text?: string): void {
 | 
			
		||||
        ModalController.dismiss(text);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    ngOnDestroy(): void {
 | 
			
		||||
        // If this code is reached and scan hasn't been stopped yet it means the user clicked the back button, cancel.
 | 
			
		||||
        CoreUtils.stopScanQR();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -498,6 +498,9 @@ export class CoreSitesProvider {
 | 
			
		||||
                this.currentSite = candidateSite;
 | 
			
		||||
                // Store session.
 | 
			
		||||
                this.login(siteId);
 | 
			
		||||
            } else if (this.currentSite && this.currentSite.getId() == siteId) {
 | 
			
		||||
                // Current site has just been updated, trigger the event.
 | 
			
		||||
                CoreEvents.trigger(CoreEvents.SITE_UPDATED, info, siteId);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            CoreEvents.trigger(CoreEvents.SITE_ADDED, info, siteId);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										564
									
								
								src/core/services/urlschemes.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										564
									
								
								src/core/services/urlschemes.ts
									
									
									
									
									
										Normal file
									
								
							@ -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;
 | 
			
		||||
}
 | 
			
		||||
@ -118,21 +118,29 @@ export class CoreTextUtilsProvider {
 | 
			
		||||
     * @param text Text to add.
 | 
			
		||||
     * @return Modified error.
 | 
			
		||||
     */
 | 
			
		||||
    addTextToError(error: string | CoreTextErrorObject, text: string): string | CoreTextErrorObject {
 | 
			
		||||
    addTextToError(error: string | CoreError | CoreTextErrorObject | undefined | null, text: string): string | CoreTextErrorObject {
 | 
			
		||||
        if (typeof error == 'string') {
 | 
			
		||||
            return error + text;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (error) {
 | 
			
		||||
            if (typeof error.message == 'string') {
 | 
			
		||||
                error.message += text;
 | 
			
		||||
            } else if (typeof error.error == 'string') {
 | 
			
		||||
                error.error += text;
 | 
			
		||||
            } else if (typeof error.content == 'string') {
 | 
			
		||||
                error.content += text;
 | 
			
		||||
            } else if (typeof error.body == 'string') {
 | 
			
		||||
                error.body += text;
 | 
			
		||||
            }
 | 
			
		||||
        if (error instanceof CoreError) {
 | 
			
		||||
            error.message += text;
 | 
			
		||||
 | 
			
		||||
            return error;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!error) {
 | 
			
		||||
            return text;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (typeof error.message == 'string') {
 | 
			
		||||
            error.message += text;
 | 
			
		||||
        } else if (typeof error.error == 'string') {
 | 
			
		||||
            error.error += text;
 | 
			
		||||
        } else if (typeof error.content == 'string') {
 | 
			
		||||
            error.content += text;
 | 
			
		||||
        } else if (typeof error.body == 'string') {
 | 
			
		||||
            error.body += text;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return error;
 | 
			
		||||
@ -522,10 +530,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 +542,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(/^[^?@/]*:\/\//, '');
 | 
			
		||||
 | 
			
		||||
@ -26,9 +26,11 @@ import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CoreMimetypeUtils } from '@services/utils/mimetype';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { CoreWSError } from '@classes/errors/wserror';
 | 
			
		||||
import { makeSingleton, Clipboard, InAppBrowser, FileOpener, WebIntent, QRScanner, Translate } from '@singletons';
 | 
			
		||||
import { makeSingleton, Clipboard, InAppBrowser, FileOpener, WebIntent, QRScanner, Translate, ModalController } from '@singletons';
 | 
			
		||||
import { CoreLogger } from '@singletons/logger';
 | 
			
		||||
import { CoreFileSizeSum } from '@services/plugin-file-delegate';
 | 
			
		||||
import { CoreViewerQRScannerComponent } from '@features/viewer/components/qr-scanner/qr-scanner';
 | 
			
		||||
import { CoreCanceledError } from '@classes/errors/cancelederror';
 | 
			
		||||
 | 
			
		||||
type TreeNode<T> = T & { children: TreeNode<T>[] };
 | 
			
		||||
 | 
			
		||||
@ -1489,12 +1491,20 @@ export class CoreUtilsProvider {
 | 
			
		||||
     * @param title Title of the modal. Defaults to "QR reader".
 | 
			
		||||
     * @return Promise resolved with the captured text or undefined if cancelled or error.
 | 
			
		||||
     */
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
    scanQR(title?: string): Promise<string> {
 | 
			
		||||
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
        return new Promise((resolve, reject): void => {
 | 
			
		||||
            // @todo
 | 
			
		||||
    async scanQR(title?: string): Promise<string> {
 | 
			
		||||
        const modal = await ModalController.create({
 | 
			
		||||
            component: CoreViewerQRScannerComponent,
 | 
			
		||||
            cssClass: 'core-modal-fullscreen',
 | 
			
		||||
            componentProps: {
 | 
			
		||||
                title,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        await modal.present();
 | 
			
		||||
 | 
			
		||||
        const result = await modal.onWillDismiss();
 | 
			
		||||
 | 
			
		||||
        return result.data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -1503,12 +1513,6 @@ export class CoreUtilsProvider {
 | 
			
		||||
     * @return Promise resolved with the QR string, rejected if error or cancelled.
 | 
			
		||||
     */
 | 
			
		||||
    async startScanQR(): Promise<string | undefined> {
 | 
			
		||||
        try {
 | 
			
		||||
            return this.startScanQR();
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            // do nothing
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!CoreApp.isMobile()) {
 | 
			
		||||
            return Promise.reject('QRScanner isn\'t available in browser.');
 | 
			
		||||
        }
 | 
			
		||||
@ -1580,7 +1584,7 @@ export class CoreUtilsProvider {
 | 
			
		||||
        } else if (typeof data != 'undefined') {
 | 
			
		||||
            this.qrScanData.deferred.resolve(data as string);
 | 
			
		||||
        } else {
 | 
			
		||||
            this.qrScanData.deferred.reject(CoreDomUtils.createCanceledError());
 | 
			
		||||
            this.qrScanData.deferred.reject(new CoreCanceledError());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        delete this.qrScanData;
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
@ -454,3 +454,14 @@ ion-button.core-button-select {
 | 
			
		||||
.core-iframe-offline-disabled {
 | 
			
		||||
    display: none !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.core-scanning-qr {
 | 
			
		||||
    .ion-page, .modal-wrapper {
 | 
			
		||||
        background-color: transparent !important;
 | 
			
		||||
        --background: transparent;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ion-content, ion-backdrop, ion-modal:not(.core-modal-fullscreen), ion-tabs {
 | 
			
		||||
        display: none !important;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user