From fe0d6a0979f22f0f04455697f3aa8179c0dcc61c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 15 Nov 2024 12:42:15 +0100 Subject: [PATCH] MOBILE-4653 utils: Move IAB related utils functions --- src/addons/mod/lti/services/lti.ts | 3 +- .../services/handlers/push-click.ts | 3 +- src/app/app.component.ts | 4 +- src/core/classes/sites/site.ts | 3 +- src/core/components/recaptcha/recaptcha.ts | 4 +- src/core/directives/link.ts | 5 +- .../pages/change-password/change-password.ts | 8 +- .../features/login/services/login-helper.ts | 9 +- .../complete-profile/complete-profile.ts | 4 +- src/core/features/user/services/support.ts | 4 +- .../initializers/prepare-inapp-browser.ts | 9 +- src/core/services/utils/utils.ts | 140 ++------------ src/core/singletons/iab.ts | 182 ++++++++++++++++++ 13 files changed, 229 insertions(+), 149 deletions(-) create mode 100644 src/core/singletons/iab.ts diff --git a/src/addons/mod/lti/services/lti.ts b/src/addons/mod/lti/services/lti.ts index c4c4e70b8..0a4f7bb52 100644 --- a/src/addons/mod/lti/services/lti.ts +++ b/src/addons/mod/lti/services/lti.ts @@ -28,6 +28,7 @@ import { CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws'; import { makeSingleton, Translate } from '@singletons'; import { ADDON_MOD_LTI_COMPONENT } from '../constants'; import { CoreCacheUpdateFrequency } from '@/core/constants'; +import { CoreInAppBrowser } from '@singletons/iab'; /** * Service that provides some features for LTI. @@ -253,7 +254,7 @@ export class AddonModLtiProvider { const launcherUrl = await this.generateLauncher(url, params); if (CorePlatform.isMobile()) { - CoreUtils.openInApp(launcherUrl); + CoreInAppBrowser.open(launcherUrl); } else { // In desktop open in browser, we found some cases where inapp caused JS issues. CoreUtils.openInBrowser(launcherUrl); diff --git a/src/addons/notifications/services/handlers/push-click.ts b/src/addons/notifications/services/handlers/push-click.ts index 9866bd5f4..322c029a3 100644 --- a/src/addons/notifications/services/handlers/push-click.ts +++ b/src/addons/notifications/services/handlers/push-click.ts @@ -24,6 +24,7 @@ import { AddonNotifications } from '../notifications'; import { AddonNotificationsMainMenuHandlerService } from './mainmenu'; import { AddonNotificationsHelper } from '../notifications-helper'; import { CoreViewer } from '@features/viewer/services/viewer'; +import { CoreInAppBrowser } from '@singletons/iab'; /** * Handler for non-messaging push notifications clicks. @@ -89,7 +90,7 @@ export class AddonNotificationsPushClickHandlerService implements CorePushNotifi switch (notification.customdata.appurlopenin) { case 'inapp': - CoreUtils.openInApp(url); + CoreInAppBrowser.open(url); return; diff --git a/src/app/app.component.ts b/src/app/app.component.ts index e821baaec..f8907f867 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -22,7 +22,7 @@ import { CoreApp } from '@services/app'; import { CoreNavigator } from '@services/navigator'; import { CoreSubscriptions } from '@singletons/subscriptions'; import { CoreWindow } from '@singletons/window'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreInAppBrowser } from '@singletons/iab'; import { CorePlatform } from '@services/platform'; import { CoreLogger } from '@singletons/logger'; import { CorePromisedValue } from '@classes/promised-value'; @@ -51,7 +51,7 @@ export class AppComponent implements OnInit, AfterViewInit { CorePlatform.resume.subscribe(() => { // Wait a second before setting it to false since in iOS there could be some frozen WS calls. setTimeout(() => { - if (CoreLoginHelper.isWaitingForBrowser() && !CoreUtils.isInAppBrowserOpen()) { + if (CoreLoginHelper.isWaitingForBrowser() && !CoreInAppBrowser.isInAppBrowserOpen()) { CoreLoginHelper.stopWaitingForBrowser(); CoreLoginHelper.checkLogout(); } diff --git a/src/core/classes/sites/site.ts b/src/core/classes/sites/site.ts index 13863cc41..135d4beed 100644 --- a/src/core/classes/sites/site.ts +++ b/src/core/classes/sites/site.ts @@ -55,6 +55,7 @@ import { CoreAuthenticatedSite, CoreAuthenticatedSiteOptionalData, CoreSiteWSPre import { firstValueFrom } from 'rxjs'; import { CorePlatform } from '@services/platform'; import { CoreLoadings } from '@services/loadings'; +import { CoreInAppBrowser } from '@singletons/iab'; /** * Class that represents a site (combination of site + user). @@ -535,7 +536,7 @@ export class CoreSite extends CoreAuthenticatedSite { options.clearsessioncache = 'yes'; } - return CoreUtils.openInApp(autoLoginUrl, options); + return CoreInAppBrowser.open(autoLoginUrl, options); } else { return CoreUtils.openInBrowser(autoLoginUrl, options); } diff --git a/src/core/components/recaptcha/recaptcha.ts b/src/core/components/recaptcha/recaptcha.ts index f37662f3c..beb129e37 100644 --- a/src/core/components/recaptcha/recaptcha.ts +++ b/src/core/components/recaptcha/recaptcha.ts @@ -17,7 +17,7 @@ import { Component, Input, OnInit } from '@angular/core'; import { CoreLang, CoreLangFormat } from '@services/lang'; import { CoreSites } from '@services/sites'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreInAppBrowser } from '@singletons/iab'; import { CorePath } from '@singletons/path'; /** @@ -66,7 +66,7 @@ export class CoreRecaptchaComponent implements OnInit { // The app cannot render the recaptcha directly because it has problems with the local protocols and domains. const src = CorePath.concatenatePaths(this.siteUrl, 'webservice/recaptcha.php?lang=' + this.lang); - const inAppBrowserWindow = CoreUtils.openInApp(src); + const inAppBrowserWindow = CoreInAppBrowser.open(src); if (!inAppBrowserWindow) { return; } diff --git a/src/core/directives/link.ts b/src/core/directives/link.ts index 3bb1f30d3..8ba268e87 100644 --- a/src/core/directives/link.ts +++ b/src/core/directives/link.ts @@ -28,6 +28,7 @@ import { CoreFilepool } from '@services/filepool'; import { CoreDom } from '@singletons/dom'; import { toBoolean } from '../transforms/boolean'; import { CoreLoadings } from '@services/loadings'; +import { CoreInAppBrowser } from '@singletons/iab'; /** * Directive to open a link in external browser or in the app. @@ -186,7 +187,7 @@ export class CoreLinkDirective implements OnInit { if (!CoreSites.isLoggedIn()) { // Not logged in, cannot auto-login. if (openInApp) { - CoreUtils.openInApp(href); + CoreInAppBrowser.open(href); } else { CoreUtils.openInBrowser(href, { showBrowserWarning: this.showBrowserWarning }); } @@ -227,7 +228,7 @@ export class CoreLinkDirective implements OnInit { } } else { if (openInApp) { - CoreUtils.openInApp(href); + CoreInAppBrowser.open(href); } else { CoreUtils.openInBrowser(href, { showBrowserWarning: this.showBrowserWarning }); } diff --git a/src/core/features/login/pages/change-password/change-password.ts b/src/core/features/login/pages/change-password/change-password.ts index f2c063d82..c033584c3 100644 --- a/src/core/features/login/pages/change-password/change-password.ts +++ b/src/core/features/login/pages/change-password/change-password.ts @@ -19,7 +19,7 @@ import { CoreLoginHelper } from '@features/login/services/login-helper'; import { Translate } from '@singletons'; import { CoreNavigator } from '@services/navigator'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreInAppBrowser } from '@singletons/iab'; import { CoreUserSupport } from '@features/user/services/support'; /** @@ -94,7 +94,7 @@ export class CoreLoginChangePasswordPage implements OnDestroy { this.urlLoadedObserver = CoreEvents.on(CoreEvents.IAB_LOAD_STOP, (event) => { if (event.url.match(/\/login\/change_password\.php.*return=1/)) { // Password has changed, close the IAB now. - CoreUtils.closeInAppBrowser(); + CoreInAppBrowser.closeInAppBrowser(); this.login(); return; @@ -105,7 +105,7 @@ export class CoreLoginChangePasswordPage implements OnDestroy { } // Use a script to check if the user changed the password, in some platforms we cannot tell using the URL. - CoreUtils.getInAppBrowserInstance()?.executeScript({ + CoreInAppBrowser.getInAppBrowserInstance()?.executeScript({ code: ` if ( document.querySelector('input[type="password"]') === null && @@ -119,7 +119,7 @@ export class CoreLoginChangePasswordPage implements OnDestroy { this.messageObserver = CoreEvents.on(CoreEvents.IAB_MESSAGE, (data) => { if (data.passwordChanged) { - CoreUtils.closeInAppBrowser(); + CoreInAppBrowser.closeInAppBrowser(); this.login(); } }); diff --git a/src/core/features/login/services/login-helper.ts b/src/core/features/login/services/login-helper.ts index 8e7bda707..da13e6556 100644 --- a/src/core/features/login/services/login-helper.ts +++ b/src/core/features/login/services/login-helper.ts @@ -62,6 +62,7 @@ import { CoreQRScan } from '@services/qrscan'; import { CoreLoadings } from '@services/loadings'; import { CoreErrorHelper } from '@services/error-helper'; import { CoreSSO } from '@singletons/sso'; +import { CoreInAppBrowser } from '@singletons/iab'; /** * Helper provider that provides some common features regarding authentication. @@ -172,7 +173,7 @@ export class CoreLoginHelperProvider { async forgottenPasswordClicked(siteUrl: string, username: string, siteConfig?: CoreSitePublicConfigResponse): Promise { if (siteConfig && siteConfig.forgottenpasswordurl) { // URL set, open it. - CoreUtils.openInApp(siteConfig.forgottenpasswordurl); + CoreInAppBrowser.open(siteConfig.forgottenpasswordurl); return; } @@ -663,7 +664,7 @@ export class CoreLoginHelperProvider { this.logger.debug('openBrowserForSSOLogin loginUrl:', loginUrl); if (this.isSSOEmbeddedBrowser(typeOfLogin)) { - CoreUtils.openInApp(loginUrl, { + CoreInAppBrowser.open(loginUrl, { clearsessioncache: 'yes', // Clear the session cache to allow for multiple logins. closebuttoncaption: Translate.instant('core.login.cancel'), }); @@ -690,7 +691,7 @@ export class CoreLoginHelperProvider { await alert.onDidDismiss(); - CoreUtils.openInApp(siteUrl + '/login/change_password.php'); + CoreInAppBrowser.open(siteUrl + '/login/change_password.php'); } /** @@ -699,7 +700,7 @@ export class CoreLoginHelperProvider { * @param siteUrl URL of the site. */ openForgottenPassword(siteUrl: string): void { - CoreUtils.openInApp(siteUrl + '/login/forgot_password.php'); + CoreInAppBrowser.open(siteUrl + '/login/forgot_password.php'); } /** diff --git a/src/core/features/user/pages/complete-profile/complete-profile.ts b/src/core/features/user/pages/complete-profile/complete-profile.ts index 5e3ad563f..53046d6af 100644 --- a/src/core/features/user/pages/complete-profile/complete-profile.ts +++ b/src/core/features/user/pages/complete-profile/complete-profile.ts @@ -19,7 +19,7 @@ import { CoreLoginHelper } from '@features/login/services/login-helper'; import { Translate } from '@singletons'; import { CoreNavigator } from '@services/navigator'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreInAppBrowser } from '@singletons/iab'; import { CoreUserSupport } from '@features/user/services/support'; /** @@ -93,7 +93,7 @@ export class CoreUserCompleteProfilePage implements OnDestroy { this.urlLoadedObserver = CoreEvents.on(CoreEvents.IAB_LOAD_START, (event) => { if (event.url.match(/\/user\/preferences.php/)) { // Profile should be complete now. - CoreUtils.closeInAppBrowser(); + CoreInAppBrowser.closeInAppBrowser(); this.login(); } }); diff --git a/src/core/features/user/services/support.ts b/src/core/features/user/services/support.ts index 1e85f5848..f61e5688e 100644 --- a/src/core/features/user/services/support.ts +++ b/src/core/features/user/services/support.ts @@ -18,7 +18,7 @@ import { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/suppo import { InAppBrowserObject } from '@awesome-cordova-plugins/in-app-browser'; import { CorePlatform } from '@services/platform'; import { CoreSites } from '@services/sites'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreInAppBrowser } from '@singletons/iab'; import { makeSingleton, Translate } from '@singletons'; import { CoreEvents } from '@singletons/events'; import { CoreSubscriptions } from '@singletons/subscriptions'; @@ -42,7 +42,7 @@ export class CoreUserSupportService { const supportConfig = options.supportConfig ?? CoreUserAuthenticatedSupportConfig.forCurrentSite(); const supportPageUrl = supportConfig.getSupportPageUrl(); const autoLoginUrl = await CoreSites.getCurrentSite()?.getAutoLoginUrl(supportPageUrl, false); - const browser = CoreUtils.openInApp(autoLoginUrl ?? supportPageUrl); + const browser = CoreInAppBrowser.open(autoLoginUrl ?? supportPageUrl); if (supportPageUrl.endsWith('/user/contactsitesupport.php')) { this.populateSupportForm(browser, options.subject, options.message); diff --git a/src/core/initializers/prepare-inapp-browser.ts b/src/core/initializers/prepare-inapp-browser.ts index f35e1eb2d..c22754cce 100644 --- a/src/core/initializers/prepare-inapp-browser.ts +++ b/src/core/initializers/prepare-inapp-browser.ts @@ -23,6 +23,7 @@ import { CoreUrl } from '@singletons/url'; import { CoreUtils } from '@services/utils/utils'; import { Translate } from '@singletons'; import { CoreEvents } from '@singletons/events'; +import { CoreInAppBrowser } from '@singletons/iab'; let lastInAppUrl: string | null = null; @@ -43,14 +44,14 @@ export default function(): void { CoreCustomURLSchemes.handleCustomURL(url).catch((error) => { CoreCustomURLSchemes.treatHandleCustomURLError(error); }); - CoreUtils.closeInAppBrowser(); + CoreInAppBrowser.closeInAppBrowser(); return; } if (isExternalApp && url.includes('://token=')) { // It's an SSO token for another app. Close the IAB and show an error. - CoreUtils.closeInAppBrowser(); + CoreInAppBrowser.closeInAppBrowser(); CoreDomUtils.showErrorModal(new CoreSiteError({ supportConfig: CoreSites.getCurrentSite() ? CoreUserAuthenticatedSupportConfig.forCurrentSite() @@ -73,10 +74,10 @@ export default function(): void { // At this point, URL schemes will stop working in IAB, and in Android the IAB is showing a "Webpage not available" error. // Re-loading the page inside the existing IAB doesn't fix it, we need to re-load the whole IAB. if (lastInAppUrl) { - CoreUtils.openInApp(lastInAppUrl); + CoreInAppBrowser.open(lastInAppUrl); } else { // No last URL loaded, close the InAppBrowser. - CoreUtils.closeInAppBrowser(); + CoreInAppBrowser.closeInAppBrowser(); } }); diff --git a/src/core/services/utils/utils.ts b/src/core/services/utils/utils.ts index 05fe91cec..80bd52df8 100644 --- a/src/core/services/utils/utils.ts +++ b/src/core/services/utils/utils.ts @@ -13,19 +13,17 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { InAppBrowserObject, InAppBrowserOptions } from '@awesome-cordova-plugins/in-app-browser'; +import { InAppBrowserObject } from '@awesome-cordova-plugins/in-app-browser'; import { FileEntry } from '@awesome-cordova-plugins/file/ngx'; -import { CoreEvents } from '@singletons/events'; import { CoreFile } from '@services/file'; import { CoreLang, CoreLangFormat } from '@services/lang'; import { CoreWS } from '@services/ws'; import { CoreMimetypeUtils } from '@services/utils/mimetype'; -import { makeSingleton, InAppBrowser, FileOpener, WebIntent, Translate, NgZone } from '@singletons'; +import { makeSingleton, FileOpener, WebIntent, Translate } from '@singletons'; import { CoreLogger } from '@singletons/logger'; import { CoreFileEntry } from '@services/file-helper'; import { CoreConstants } from '@/core/constants'; import { CoreWindow } from '@singletons/window'; -import { CoreColors } from '@singletons/colors'; import { CorePlatform } from '@services/platform'; import { CoreErrorWithOptions } from '@classes/errors/errorwithoptions'; import { CoreFilepool } from '@services/filepool'; @@ -38,6 +36,7 @@ import { CoreText } from '@singletons/text'; import { CoreWait, CoreWaitOptions } from '@singletons/wait'; import { CoreQRScan } from '@services/qrscan'; import { CoreErrorHelper } from '@services/error-helper'; +import { CoreInAppBrowser, CoreInAppBrowserOpenOptions } from '@singletons/iab'; export type TreeNode = T & { children: TreeNode[] }; @@ -50,7 +49,6 @@ export class CoreUtilsProvider { protected readonly DONT_CLONE = ['[object FileEntry]', '[object DirectoryEntry]', '[object DOMFileSystem]']; protected logger: CoreLogger; - protected iabInstance?: InAppBrowserObject; protected uniqueIds: {[name: string]: number} = {}; constructor() { @@ -269,29 +267,31 @@ export class CoreUtilsProvider { /** * Close the InAppBrowser window. + * + * @deprecated since 5.0. Use CoreInAppBrowser.closeInAppBrowser instead. */ closeInAppBrowser(): void { - if (this.iabInstance) { - this.iabInstance.close(); - } + CoreInAppBrowser.closeInAppBrowser(); } /** * Get inapp browser instance (if any). * * @returns IAB instance, undefined if not open. + * @deprecated since 5.0. Use CoreInAppBrowser.getInAppBrowserInstance instead. */ getInAppBrowserInstance(): InAppBrowserObject | undefined { - return this.iabInstance; + return CoreInAppBrowser.getInAppBrowserInstance(); } /** * Check if inapp browser is open. * * @returns Whether it's open. + * @deprecated since 5.0. Use CoreInAppBrowser.isInAppBrowserOpen instead. */ isInAppBrowserOpen(): boolean { - return !!this.iabInstance; + return CoreInAppBrowser.isInAppBrowserOpen(); } /** @@ -968,7 +968,7 @@ export class CoreUtilsProvider { if (mimetype == 'text/html' && CorePlatform.isAndroid()) { // Open HTML local files in InAppBrowser, in system browser some embedded files aren't loaded. - this.openInApp(path); + CoreInAppBrowser.open(path); return; } else if (extension === 'apk' && CorePlatform.isAndroid()) { @@ -1042,117 +1042,16 @@ export class CoreUtilsProvider { /** * Open a URL using InAppBrowser. - * Do not use for files, refer to {@link CoreUtilsProvider.openFile}. + * Do not use for files, refer to {@link CoreUtils.openFile}. * * @param url The URL to open. * @param options Override default options passed to InAppBrowser. * @returns The opened window. - */ - openInApp(url: string, options?: CoreUtilsOpenInAppOptions): InAppBrowserObject { - options = options || {}; - options.usewkwebview = 'yes'; // Force WKWebView in iOS. - options.enableViewPortScale = options.enableViewPortScale ?? 'yes'; // Enable zoom on iOS by default. - options.allowInlineMediaPlayback = options.allowInlineMediaPlayback ?? 'yes'; // Allow playing inline videos in iOS. - - if (!options.location && CorePlatform.isIOS() && url.indexOf('file://') === 0) { - // The URL uses file protocol, don't show it on iOS. - // In Android we keep it because otherwise we lose the whole toolbar. - options.location = 'no'; - } - - this.setInAppBrowserToolbarColors(options); - - this.iabInstance = InAppBrowser.create(url, '_blank', options); - - if (CorePlatform.isMobile()) { - const loadStartUrls: string[] = []; - - const loadStartSubscription = this.iabInstance.on('loadstart').subscribe((event) => { - NgZone.run(() => { - // Store the last loaded URLs (max 10). - loadStartUrls.push(event.url); - if (loadStartUrls.length > 10) { - loadStartUrls.shift(); - } - - CoreEvents.trigger(CoreEvents.IAB_LOAD_START, event); - }); - }); - - const loadStopSubscription = this.iabInstance.on('loadstop').subscribe((event) => { - NgZone.run(() => { - CoreEvents.trigger(CoreEvents.IAB_LOAD_STOP, event); - }); - }); - - const messageSubscription = this.iabInstance.on('message').subscribe((event) => { - NgZone.run(() => { - CoreEvents.trigger(CoreEvents.IAB_MESSAGE, event.data); - }); - }); - - const exitSubscription = this.iabInstance.on('exit').subscribe((event) => { - NgZone.run(() => { - loadStartSubscription.unsubscribe(); - loadStopSubscription.unsubscribe(); - messageSubscription.unsubscribe(); - exitSubscription.unsubscribe(); - - this.iabInstance = undefined; - CoreEvents.trigger(CoreEvents.IAB_EXIT, event); - }); - }); - } - - CoreAnalytics.logEvent({ - type: CoreAnalyticsEventType.OPEN_LINK, - link: CoreUrl.unfixPluginfileURL(options.originalUrl ?? url), - }); - - return this.iabInstance; - } - - /** - * Given some IAB options, set the toolbar colors properties to the right values. * - * @param options Options to change. - * @returns Changed options. + * @deprecated since 5.0. Use CoreInAppBrowser.openInApp instead. */ - protected setInAppBrowserToolbarColors(options: InAppBrowserOptions): InAppBrowserOptions { - if (options.toolbarcolor) { - // Color already set. - return options; - } - - // Color not set. Check if it needs to be changed automatically. - let bgColor: string | undefined; - let textColor: string | undefined; - - if (CoreConstants.CONFIG.iabToolbarColors === 'auto') { - bgColor = CoreColors.getToolbarBackgroundColor(); - } else if (CoreConstants.CONFIG.iabToolbarColors && typeof CoreConstants.CONFIG.iabToolbarColors === 'object') { - bgColor = CoreConstants.CONFIG.iabToolbarColors.background; - textColor = CoreConstants.CONFIG.iabToolbarColors.text; - } - - if (!bgColor) { - // Use default color. In iOS, use black background color since the default is transparent and doesn't look good. - options.locationcolor = '#000000'; - - return options; - } - - if (!textColor) { - textColor = CoreColors.isWhiteContrastingBetter(bgColor) ? '#ffffff' : '#000000'; - } - - options.toolbarcolor = bgColor; - options.closebuttoncolor = textColor; - options.navigationbuttoncolor = textColor; - options.locationcolor = bgColor; - options.locationtextcolor = textColor; - - return options; + openInApp(url: string, options?: CoreInAppBrowserOpenOptions): InAppBrowserObject { + return CoreInAppBrowser.open(url, options); } /** @@ -1224,7 +1123,7 @@ export class CoreUtilsProvider { } // In the rest of platforms we need to open them in InAppBrowser. - this.openInApp(url); + CoreInAppBrowser.open(url); } /** @@ -1810,13 +1709,6 @@ export type CoreUtilsOpenInBrowserOptions = { browserWarningUrl?: string; }; -/** - * Options for opening in InAppBrowser. - */ -export type CoreUtilsOpenInAppOptions = InAppBrowserOptions & { - originalUrl?: string; // Original URL to open (in case the URL was treated, e.g. to add a token or an auto-login). -}; - /** * Options for waiting. * diff --git a/src/core/singletons/iab.ts b/src/core/singletons/iab.ts new file mode 100644 index 000000000..5c9aac8a8 --- /dev/null +++ b/src/core/singletons/iab.ts @@ -0,0 +1,182 @@ +// (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 { InAppBrowserObject, InAppBrowserOptions } from '@awesome-cordova-plugins/in-app-browser'; +import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; +import { CorePlatform } from '@services/platform'; +import { InAppBrowser, NgZone } from '@singletons'; +import { CoreEvents } from '@singletons/events'; +import { CoreUrl } from './url'; +import { CoreConstants } from '../constants'; +import { CoreColors } from './colors'; + +/** + * Singleton with helper functions for InAppBrowser. + */ +export class CoreInAppBrowser { + + private static iabInstance?: InAppBrowserObject; + + /** + * Close the InAppBrowser window. + */ + static closeInAppBrowser(): void { + if (!CoreInAppBrowser.iabInstance) { + return; + } + + CoreInAppBrowser.iabInstance.close(); + } + + /** + * Get inapp browser instance (if any). + * + * @returns IAB instance, undefined if not open. + */ + static getInAppBrowserInstance(): InAppBrowserObject | undefined { + return CoreInAppBrowser.iabInstance; + } + + /** + * Check if inapp browser is open. + * + * @returns Whether it's open. + */ + static isInAppBrowserOpen(): boolean { + return !!CoreInAppBrowser.iabInstance; + } + + /** + * Open a URL using InAppBrowser. + * Do not use for files, refer to {@link CoreUtilsProvider.openFile}. + * + * @param url The URL to open. + * @param options Override default options passed to InAppBrowser. + * @returns The opened window. + */ + static open(url: string, options?: CoreInAppBrowserOpenOptions): InAppBrowserObject { + options = options || {}; + options.usewkwebview = 'yes'; // Force WKWebView in iOS. + options.enableViewPortScale = options.enableViewPortScale ?? 'yes'; // Enable zoom on iOS by default. + options.allowInlineMediaPlayback = options.allowInlineMediaPlayback ?? 'yes'; // Allow playing inline videos in iOS. + + if (!options.location && CorePlatform.isIOS() && url.indexOf('file://') === 0) { + // The URL uses file protocol, don't show it on iOS. + // In Android we keep it because otherwise we lose the whole toolbar. + options.location = 'no'; + } + + CoreInAppBrowser.setInAppBrowserToolbarColors(options); + + CoreInAppBrowser.iabInstance = InAppBrowser.create(url, '_blank', options); + + if (CorePlatform.isMobile()) { + const loadStartUrls: string[] = []; + + const loadStartSubscription = CoreInAppBrowser.iabInstance.on('loadstart').subscribe((event) => { + NgZone.run(() => { + // Store the last loaded URLs (max 10). + loadStartUrls.push(event.url); + if (loadStartUrls.length > 10) { + loadStartUrls.shift(); + } + + CoreEvents.trigger(CoreEvents.IAB_LOAD_START, event); + }); + }); + + const loadStopSubscription = CoreInAppBrowser.iabInstance.on('loadstop').subscribe((event) => { + NgZone.run(() => { + CoreEvents.trigger(CoreEvents.IAB_LOAD_STOP, event); + }); + }); + + const messageSubscription = CoreInAppBrowser.iabInstance.on('message').subscribe((event) => { + NgZone.run(() => { + CoreEvents.trigger(CoreEvents.IAB_MESSAGE, event.data); + }); + }); + + const exitSubscription = CoreInAppBrowser.iabInstance.on('exit').subscribe((event) => { + NgZone.run(() => { + loadStartSubscription.unsubscribe(); + loadStopSubscription.unsubscribe(); + messageSubscription.unsubscribe(); + exitSubscription.unsubscribe(); + + CoreInAppBrowser.iabInstance = undefined; + CoreEvents.trigger(CoreEvents.IAB_EXIT, event); + }); + }); + } + + CoreAnalytics.logEvent({ + type: CoreAnalyticsEventType.OPEN_LINK, + link: CoreUrl.unfixPluginfileURL(options.originalUrl ?? url), + }); + + return CoreInAppBrowser.iabInstance; + } + + /** + * Given some IAB options, set the toolbar colors properties to the right values. + * + * @param options Options to change. + * @returns Changed options. + */ + protected static setInAppBrowserToolbarColors(options: InAppBrowserOptions): InAppBrowserOptions { + if (options.toolbarcolor) { + // Color already set. + return options; + } + + // Color not set. Check if it needs to be changed automatically. + let bgColor: string | undefined; + let textColor: string | undefined; + + if (CoreConstants.CONFIG.iabToolbarColors === 'auto') { + bgColor = CoreColors.getToolbarBackgroundColor(); + } else if (CoreConstants.CONFIG.iabToolbarColors && typeof CoreConstants.CONFIG.iabToolbarColors === 'object') { + bgColor = CoreConstants.CONFIG.iabToolbarColors.background; + textColor = CoreConstants.CONFIG.iabToolbarColors.text; + } + + if (!bgColor) { + // Use default color. In iOS, use black background color since the default is transparent and doesn't look good. + options.locationcolor = '#000000'; + + return options; + } + + if (!textColor) { + textColor = CoreColors.isWhiteContrastingBetter(bgColor) ? '#ffffff' : '#000000'; + } + + options.toolbarcolor = bgColor; + options.closebuttoncolor = textColor; + options.navigationbuttoncolor = textColor; + options.locationcolor = bgColor; + options.locationtextcolor = textColor; + + return options; + } + +} + +/** + * Options for opening in InAppBrowser. + */ +export type CoreInAppBrowserOpenOptions = InAppBrowserOptions & { + originalUrl?: string; // Original URL to open (in case the URL was treated, e.g. to add a token or an auto-login). +};