diff --git a/src/core/classes/sites/authenticated-site.ts b/src/core/classes/sites/authenticated-site.ts index abb1c9789..b12b1f04d 100644 --- a/src/core/classes/sites/authenticated-site.ts +++ b/src/core/classes/sites/authenticated-site.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { CoreApp } from '@services/app'; +import { CoreSSO } from '@singletons/sso'; import { CoreNetwork } from '@services/network'; import { CoreEventData, CoreEvents } from '@singletons/events'; import { @@ -790,9 +790,9 @@ export class CoreAuthenticatedSite extends CoreUnauthenticatedSite { wsPreSets.wsToken = this.token ?? ''; return await this.callOrEnqueueRequest(method, data, preSets, wsPreSets); - } else if (CoreApp.isSSOAuthenticationOngoing()) { + } else if (CoreSSO.isSSOAuthenticationOngoing()) { // There's an SSO authentication ongoing, wait for it to finish and try again. - await CoreApp.waitForSSOAuthentication(); + await CoreSSO.waitForSSOAuthentication(); return await this.callOrEnqueueRequest(method, data, preSets, wsPreSets); } diff --git a/src/core/core.module.ts b/src/core/core.module.ts index 59f174a73..fee9c1c4f 100644 --- a/src/core/core.module.ts +++ b/src/core/core.module.ts @@ -29,6 +29,7 @@ import { getInitializerProviders } from './initializers'; export async function getCoreServices(): Promise[]> { const { CoreAppProvider } = await import('@services/app'); + const { CoreAppDBService } = await import('@services/app-db'); const { CoreConfigProvider } = await import('@services/config'); const { CoreCronDelegateService } = await import('@services/cron'); const { CoreCustomURLSchemesProvider } = await import('@services/urlschemes'); @@ -65,6 +66,7 @@ export async function getCoreServices(): Promise[]> { return [ CoreAppProvider, + CoreAppDBService, CoreConfigProvider, CoreCronDelegateService, CoreCustomURLSchemesProvider, diff --git a/src/core/features/fileuploader/services/handlers/audio.ts b/src/core/features/fileuploader/services/handlers/audio.ts index a5e5dc573..13cb2a8ef 100644 --- a/src/core/features/fileuploader/services/handlers/audio.ts +++ b/src/core/features/fileuploader/services/handlers/audio.ts @@ -14,7 +14,7 @@ import { Injectable } from '@angular/core'; -import { CoreApp } from '@services/app'; +import { CoreMedia } from '@singletons/media'; import { CorePlatform } from '@services/platform'; import { CoreArray } from '@singletons/array'; import { makeSingleton } from '@singletons'; @@ -34,7 +34,7 @@ export class CoreFileUploaderAudioHandlerService implements CoreFileUploaderHand * @inheritdoc */ async isEnabled(): Promise { - return CorePlatform.isMobile() || (CoreApp.canGetUserMedia() && CoreApp.canRecordMedia()); + return CorePlatform.isMobile() || (CoreMedia.canGetUserMedia() && CoreMedia.canRecordMedia()); } /** diff --git a/src/core/features/fileuploader/services/handlers/camera.ts b/src/core/features/fileuploader/services/handlers/camera.ts index 373b338fa..9abf9cd3f 100644 --- a/src/core/features/fileuploader/services/handlers/camera.ts +++ b/src/core/features/fileuploader/services/handlers/camera.ts @@ -14,7 +14,7 @@ import { Injectable } from '@angular/core'; -import { CoreApp } from '@services/app'; +import { CoreMedia } from '@singletons/media'; import { CorePlatform } from '@services/platform'; import { CoreArray } from '@singletons/array'; import { makeSingleton } from '@singletons'; @@ -34,7 +34,7 @@ export class CoreFileUploaderCameraHandlerService implements CoreFileUploaderHan * @inheritdoc */ async isEnabled(): Promise { - return CorePlatform.isMobile() || CoreApp.canGetUserMedia(); + return CorePlatform.isMobile() || CoreMedia.canGetUserMedia(); } /** diff --git a/src/core/features/fileuploader/services/handlers/video.ts b/src/core/features/fileuploader/services/handlers/video.ts index ac655b580..605f92b38 100644 --- a/src/core/features/fileuploader/services/handlers/video.ts +++ b/src/core/features/fileuploader/services/handlers/video.ts @@ -14,7 +14,7 @@ import { Injectable } from '@angular/core'; -import { CoreApp } from '@services/app'; +import { CoreMedia } from '@singletons/media'; import { CorePlatform } from '@services/platform'; import { CoreArray } from '@singletons/array'; import { makeSingleton } from '@singletons'; @@ -34,7 +34,7 @@ export class CoreFileUploaderVideoHandlerService implements CoreFileUploaderHand * @inheritdoc */ async isEnabled(): Promise { - return CorePlatform.isMobile() || (CoreApp.canGetUserMedia() && CoreApp.canRecordMedia()); + return CorePlatform.isMobile() || (CoreMedia.canGetUserMedia() && CoreMedia.canRecordMedia()); } /** diff --git a/src/core/features/login/pages/credentials/credentials.ts b/src/core/features/login/pages/credentials/credentials.ts index 01035f220..b8638cab5 100644 --- a/src/core/features/login/pages/credentials/credentials.ts +++ b/src/core/features/login/pages/credentials/credentials.ts @@ -17,7 +17,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Subscription } from 'rxjs'; import { debounceTime } from 'rxjs/operators'; -import { CoreApp } from '@services/app'; +import { CoreSSO } from '@singletons/sso'; import { CoreNetwork } from '@services/network'; import { CoreSiteCheckResponse, CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; @@ -251,7 +251,7 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy { e?.stopPropagation(); // Check that there's no SSO authentication ongoing and the view hasn't changed. - if (CoreApp.isSSOAuthenticationOngoing() || this.viewLeft || !this.siteCheck) { + if (CoreSSO.isSSOAuthenticationOngoing() || this.viewLeft || !this.siteCheck) { return; } diff --git a/src/core/features/login/services/login-helper.ts b/src/core/features/login/services/login-helper.ts index 17be768a0..8e7bda707 100644 --- a/src/core/features/login/services/login-helper.ts +++ b/src/core/features/login/services/login-helper.ts @@ -61,6 +61,7 @@ import { CoreSiteError, CoreSiteErrorDebug } from '@classes/errors/siteerror'; import { CoreQRScan } from '@services/qrscan'; import { CoreLoadings } from '@services/loadings'; import { CoreErrorHelper } from '@services/error-helper'; +import { CoreSSO } from '@singletons/sso'; /** * Helper provider that provides some common features regarding authentication. @@ -131,7 +132,7 @@ export class CoreLoginHelperProvider { const currentSite = CoreSites.getCurrentSite(); if ( - !CoreApp.isSSOAuthenticationOngoing() && + !CoreSSO.isSSOAuthenticationOngoing() && currentSite?.isLoggedOut() && CoreNavigator.isCurrent('/login/reconnect') ) { diff --git a/src/core/features/mainmenu/guards/auth.ts b/src/core/features/mainmenu/guards/auth.ts index 68923cb9b..b63c4e0f4 100644 --- a/src/core/features/mainmenu/guards/auth.ts +++ b/src/core/features/mainmenu/guards/auth.ts @@ -14,7 +14,7 @@ import { CanActivateFn } from '@angular/router'; import { CoreLoginHelper } from '@features/login/services/login-helper'; -import { CoreApp } from '@services/app'; +import { CoreRedirects } from '@singletons/redirects'; import { CoreSites } from '@services/sites'; import { Router } from '@singletons'; @@ -35,7 +35,7 @@ export const authGuard: CanActivateFn = async () => { const siteId = CoreSites.getCurrentSiteId(); // Pass redirect data (if any and belongs to same site). - let redirect = CoreApp.consumeMemoryRedirect(); + let redirect = CoreRedirects.consumeMemoryRedirect(); if (redirect?.siteId !== siteId) { redirect = null; } diff --git a/src/core/features/pushnotifications/services/database/pushnotifications.ts b/src/core/features/pushnotifications/services/database/pushnotifications.ts index 37d851bba..d95d1249f 100644 --- a/src/core/features/pushnotifications/services/database/pushnotifications.ts +++ b/src/core/features/pushnotifications/services/database/pushnotifications.ts @@ -13,7 +13,7 @@ // limitations under the License. import { SQLiteDB } from '@classes/sqlitedb'; -import { CoreAppSchema } from '@services/app'; +import { CoreAppSchema } from '@services/app-db'; import { CoreSiteSchema } from '@services/sites'; /** diff --git a/src/core/features/pushnotifications/services/pushnotifications.ts b/src/core/features/pushnotifications/services/pushnotifications.ts index 0e131e2dd..b724407c4 100644 --- a/src/core/features/pushnotifications/services/pushnotifications.ts +++ b/src/core/features/pushnotifications/services/pushnotifications.ts @@ -16,7 +16,7 @@ import { Injectable } from '@angular/core'; import { ILocalNotification } from '@awesome-cordova-plugins/local-notifications'; import { NotificationEventResponse, PushOptions, RegistrationEventResponse } from '@awesome-cordova-plugins/push/ngx'; -import { CoreApp } from '@services/app'; +import { CoreAppDB } from '@services/app-db'; import { CoreSites } from '@services/sites'; import { CorePushNotificationsDelegate } from './push-delegate'; import { CoreLocalNotifications } from '@services/local-notifications'; @@ -204,13 +204,9 @@ export class CorePushNotificationsProvider { * @returns Promise resolved when done. */ protected async initializeDatabase(): Promise { - try { - await CoreApp.createTablesFromSchema(APP_SCHEMA); - } catch { - // Ignore errors. - } + await CoreAppDB.createTablesFromSchema(APP_SCHEMA); - const database = CoreApp.getDB(); + const database = CoreAppDB.getDB(); const badgesTable = new CoreDatabaseTableProxy( { cachingStrategy: CoreDatabaseCachingStrategy.Eager }, database, diff --git a/src/core/features/sharedfiles/services/database/sharedfiles.ts b/src/core/features/sharedfiles/services/database/sharedfiles.ts index 346fae39f..6bc106df5 100644 --- a/src/core/features/sharedfiles/services/database/sharedfiles.ts +++ b/src/core/features/sharedfiles/services/database/sharedfiles.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { CoreAppSchema } from '@services/app'; +import { CoreAppSchema } from '@services/app-db'; /** * Database variables for CoreSharedFilesProvider service. diff --git a/src/core/features/sharedfiles/services/sharedfiles.ts b/src/core/features/sharedfiles/services/sharedfiles.ts index 166fc6119..8e97b4186 100644 --- a/src/core/features/sharedfiles/services/sharedfiles.ts +++ b/src/core/features/sharedfiles/services/sharedfiles.ts @@ -17,7 +17,7 @@ import { FileEntry, DirectoryEntry } from '@awesome-cordova-plugins/file/ngx'; import { Md5 } from 'ts-md5/dist/md5'; import { CoreLogger } from '@singletons/logger'; -import { CoreApp } from '@services/app'; +import { CoreAppDB } from '@services/app-db'; import { CoreFile } from '@services/file'; import { CoreUtils } from '@services/utils/utils'; import { CoreMimetypeUtils } from '@services/utils/mimetype'; @@ -51,13 +51,9 @@ export class CoreSharedFilesProvider { * @returns Promise resolved when done. */ async initializeDatabase(): Promise { - try { - await CoreApp.createTablesFromSchema(APP_SCHEMA); - } catch (e) { - // Ignore errors. - } + await CoreAppDB.createTablesFromSchema(APP_SCHEMA); - const database = CoreApp.getDB(); + const database = CoreAppDB.getDB(); const sharedFilesTable = new CoreDatabaseTableProxy( { cachingStrategy: CoreDatabaseCachingStrategy.None }, database, diff --git a/src/core/features/siteplugins/services/siteplugins.ts b/src/core/features/siteplugins/services/siteplugins.ts index bae857ef8..8f9927e2a 100644 --- a/src/core/features/siteplugins/services/siteplugins.ts +++ b/src/core/features/siteplugins/services/siteplugins.ts @@ -18,7 +18,6 @@ import { CoreCacheUpdateFrequency, CoreConstants } from '@/core/constants'; import { CoreSite } from '@classes/sites/site'; import { CoreCourseAnyModuleData } from '@features/course/services/course'; import { CoreCourses } from '@features/courses/services/courses'; -import { CoreApp } from '@services/app'; import { CoreFilepool } from '@services/filepool'; import { CoreLang, CoreLangFormat } from '@services/lang'; import { CoreSites } from '@services/sites'; @@ -101,7 +100,7 @@ export class CoreSitePluginsProvider { appcustomurlscheme: CoreConstants.CONFIG.customurlscheme, appisdesktop: false, appismobile: CorePlatform.isMobile(), - appiswide: CoreApp.isWide(), + appiswide: CorePlatform.isWide(), appplatform: 'browser', }; diff --git a/src/core/features/user/pages/about/about.ts b/src/core/features/user/pages/about/about.ts index 86d4b044f..465e594f0 100644 --- a/src/core/features/user/pages/about/about.ts +++ b/src/core/features/user/pages/about/about.ts @@ -25,7 +25,6 @@ import { USER_PROFILE_REFRESHED, USER_PROFILE_SERVER_TIMEZONE, } from '@features/user/services/user'; -import { CoreUserHelper } from '@features/user/services/user-helper'; import { CoreNavigator } from '@services/navigator'; import { CoreIonLoadingElement } from '@classes/ion-loading'; import { CoreSite } from '@classes/sites/site'; @@ -34,6 +33,7 @@ import { CoreMimetypeUtils } from '@services/utils/mimetype'; import { Translate } from '@singletons'; import { CoreUrl } from '@singletons/url'; import { CoreLoadings } from '@services/loadings'; +import { CoreTime } from '@singletons/time'; /** * Page that displays info about a user. @@ -275,7 +275,7 @@ export class CoreUserAboutPage implements OnInit, OnDestroy { } if (this.user.timezone) { - this.user.timezone = CoreUserHelper.translateLegacyTimezone(this.user.timezone); + this.user.timezone = CoreTime.translateLegacyTimezone(this.user.timezone); } } diff --git a/src/core/features/user/services/user-helper.ts b/src/core/features/user/services/user-helper.ts index 4d2e53367..fd790ec50 100644 --- a/src/core/features/user/services/user-helper.ts +++ b/src/core/features/user/services/user-helper.ts @@ -18,6 +18,7 @@ import { CoreSites } from '@services/sites'; import { makeSingleton, Translate } from '@singletons'; import { CoreUser, CoreUserProfile, CoreUserRole } from './user'; +import { CoreTime } from '@singletons/time'; /** * Service that provides some features regarding users information. @@ -25,63 +26,6 @@ import { CoreUser, CoreUserProfile, CoreUserRole } from './user'; @Injectable({ providedIn: 'root' }) export class CoreUserHelperProvider { - protected static readonly LEGACY_TIMEZONES = { - '-13.0': 'Australia/Perth', - '-12.5': 'Etc/GMT+12', - '-12.0': 'Etc/GMT+12', - '-11.5': 'Etc/GMT+11', - '-11.0': 'Etc/GMT+11', - '-10.5': 'Etc/GMT+10', - '-10.0': 'Etc/GMT+10', - '-9.5': 'Etc/GMT+9', - '-9.0': 'Etc/GMT+9', - '-8.5': 'Etc/GMT+8', - '-8.0': 'Etc/GMT+8', - '-7.5': 'Etc/GMT+7', - '-7.0': 'Etc/GMT+7', - '-6.5': 'Etc/GMT+6', - '-6.0': 'Etc/GMT+6', - '-5.5': 'Etc/GMT+5', - '-5.0': 'Etc/GMT+5', - '-4.5': 'Etc/GMT+4', - '-4.0': 'Etc/GMT+4', - '-3.5': 'Etc/GMT+3', - '-3.0': 'Etc/GMT+3', - '-2.5': 'Etc/GMT+2', - '-2.0': 'Etc/GMT+2', - '-1.5': 'Etc/GMT+1', - '-1.0': 'Etc/GMT+1', - '-0.5': 'Etc/GMT', - '0': 'Etc/GMT', - '0.0': 'Etc/GMT', - '0.5': 'Etc/GMT', - '1.0': 'Etc/GMT-1', - '1.5': 'Etc/GMT-1', - '2.0': 'Etc/GMT-2', - '2.5': 'Etc/GMT-2', - '3.0': 'Etc/GMT-3', - '3.5': 'Etc/GMT-3', - '4.0': 'Etc/GMT-4', - '4.5': 'Asia/Kabul', - '5.0': 'Etc/GMT-5', - '5.5': 'Asia/Kolkata', - '6.0': 'Etc/GMT-6', - '6.5': 'Asia/Rangoon', - '7.0': 'Etc/GMT-7', - '7.5': 'Etc/GMT-7', - '8.0': 'Etc/GMT-8', - '8.5': 'Etc/GMT-8', - '9.0': 'Etc/GMT-9', - '9.5': 'Australia/Darwin', - '10.0': 'Etc/GMT-10', - '10.5': 'Etc/GMT-10', - '11.0': 'Etc/GMT-11', - '11.5': 'Etc/GMT-11', - '12.0': 'Etc/GMT-12', - '12.5': 'Etc/GMT-12', - '13.0': 'Etc/GMT-13', - }; - /** * Formats a user address, concatenating address, city and country. * @@ -192,9 +136,10 @@ export class CoreUserHelperProvider { * * @param tz Timezone name. * @returns Readable timezone name. + * @deprecated since 5.0. Use CoreTime.translateLegacyTimezone instead. */ translateLegacyTimezone(tz: string): string { - return CoreUserHelperProvider.LEGACY_TIMEZONES[tz] ?? tz; + return CoreTime.translateLegacyTimezone(tz); } } diff --git a/src/core/features/usertours/services/database/user-tours.ts b/src/core/features/usertours/services/database/user-tours.ts index c2252df0d..9977a0ae5 100644 --- a/src/core/features/usertours/services/database/user-tours.ts +++ b/src/core/features/usertours/services/database/user-tours.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { CoreAppSchema } from '@services/app'; +import { CoreAppSchema } from '@services/app-db'; /** * Database variables for CoreUserTours service. diff --git a/src/core/features/usertours/services/user-tours.ts b/src/core/features/usertours/services/user-tours.ts index 463f79287..b6537ba9a 100644 --- a/src/core/features/usertours/services/user-tours.ts +++ b/src/core/features/usertours/services/user-tours.ts @@ -18,13 +18,12 @@ import { Injectable } from '@angular/core'; import { CoreCancellablePromise } from '@classes/cancellable-promise'; import { CoreDatabaseTable } from '@classes/database/database-table'; import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy'; -import { CoreApp } from '@services/app'; -import { CoreUtils } from '@services/utils/utils'; +import { CoreAppDB } from '@services/app-db'; import { AngularFrameworkDelegate, makeSingleton } from '@singletons'; import { CoreDirectivesRegistry } from '@singletons/directives-registry'; import { CoreDom } from '@singletons/dom'; import { CoreSubscriptions } from '@singletons/subscriptions'; -import { CoreUserToursUserTourComponent } from '../components/user-tour/user-tour'; +import type { CoreUserToursUserTourComponent } from '../components/user-tour/user-tour'; import { APP_SCHEMA, CoreUserToursDBEntry, USER_TOURS_TABLE_NAME } from './database/user-tours'; import { CorePromisedValue } from '@classes/promised-value'; import { CoreWait } from '@singletons/wait'; @@ -43,12 +42,12 @@ export class CoreUserToursService { * Initialize database. */ async initializeDatabase(): Promise { - await CoreUtils.ignoreErrors(CoreApp.createTablesFromSchema(APP_SCHEMA)); + await CoreAppDB.createTablesFromSchema(APP_SCHEMA); this.table.setLazyConstructor(async () => { const table = new CoreDatabaseTableProxy( { cachingStrategy: CoreDatabaseCachingStrategy.Eager }, - CoreApp.getDB(), + CoreAppDB.getDB(), USER_TOURS_TABLE_NAME, ); @@ -112,6 +111,8 @@ export class CoreUserToursService { protected async show(options: CoreUserToursBasicOptions): Promise; protected async show(options: CoreUserToursFocusedOptions): Promise; protected async show(options: CoreUserToursBasicOptions | CoreUserToursFocusedOptions): Promise { + const { CoreUserToursUserTourComponent } = await import('../components/user-tour/user-tour'); + const { delay, ...componentOptions } = options; await CoreWait.wait(delay ?? 200); diff --git a/src/core/guards/redirect.ts b/src/core/guards/redirect.ts index 55bf4996f..3900183a8 100644 --- a/src/core/guards/redirect.ts +++ b/src/core/guards/redirect.ts @@ -13,7 +13,7 @@ // limitations under the License. import { CanActivateFn } from '@angular/router'; -import { CoreApp } from '@services/app'; +import { CoreRedirects } from '@singletons/redirects'; import { CoreRedirectPayload } from '@services/navigator'; import { CoreSites } from '@services/sites'; import { Router } from '@singletons'; @@ -25,7 +25,7 @@ import { CoreConstants } from '../constants'; * @returns True if there's no redirect, redirection route otherwise. */ export const redirectGuard: CanActivateFn = async () => { - const redirect = CoreApp.consumeMemoryRedirect(); + const redirect = CoreRedirects.consumeMemoryRedirect(); if (!redirect) { return true; } diff --git a/src/core/initializers/app.ts b/src/core/initializers/app.ts index 837caf889..cd9a1d67b 100644 --- a/src/core/initializers/app.ts +++ b/src/core/initializers/app.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { CoreApp } from '@services/app'; import { CoreHTMLClasses } from '@singletons/html-classes'; /** @@ -19,4 +20,5 @@ import { CoreHTMLClasses } from '@singletons/html-classes'; */ export default async function(): Promise { CoreHTMLClasses.initialize(); + CoreApp.initialize(); } diff --git a/src/core/initializers/consume-storage-redirect.ts b/src/core/initializers/consume-storage-redirect.ts index 20b776ae3..48489043e 100644 --- a/src/core/initializers/consume-storage-redirect.ts +++ b/src/core/initializers/consume-storage-redirect.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { CoreApp } from '@services/app'; +import { CoreRedirects } from '@singletons/redirects'; import { CoreUpdateManager } from '@services/update-manager'; /** @@ -21,5 +21,5 @@ import { CoreUpdateManager } from '@services/update-manager'; export default async function(): Promise { await CoreUpdateManager.donePromise; - CoreApp.consumeStorageRedirect(); + CoreRedirects.consumeStorageRedirect(); } diff --git a/src/core/initializers/initialize-databases.ts b/src/core/initializers/initialize-databases.ts index 1a0eb20ee..96727a108 100644 --- a/src/core/initializers/initialize-databases.ts +++ b/src/core/initializers/initialize-databases.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { CoreApp } from '@services/app'; +import { CoreAppDB } from '@services/app-db'; import { CoreConfig } from '@services/config'; import { CoreCronDelegate } from '@services/cron'; import { CoreFilepool } from '@services/filepool'; @@ -25,7 +25,7 @@ import { CoreStorage } from '@services/storage'; */ export default async function(): Promise { await Promise.all([ - CoreApp.initializeDatabase(), + CoreAppDB.initializeDatabase(), CoreConfig.initializeDatabase(), CoreCronDelegate.initializeDatabase(), CoreFilepool.initializeDatabase(), diff --git a/src/core/initializers/prepare-devtools.ts b/src/core/initializers/prepare-devtools.ts index f2442ebad..f584984e4 100644 --- a/src/core/initializers/prepare-devtools.ts +++ b/src/core/initializers/prepare-devtools.ts @@ -13,16 +13,16 @@ // limitations under the License. import { CorePushNotifications, CorePushNotificationsProvider } from '@features/pushnotifications/services/pushnotifications'; -import { CoreApp, CoreAppProvider } from '@services/app'; import { CoreConfig, CoreConfigProvider } from '@services/config'; import { CoreDB, CoreDbProvider } from '@services/db'; import { CoreCustomURLSchemes, CoreCustomURLSchemesProvider } from '@services/urlschemes'; import { CoreBrowser } from '@singletons/browser'; import { CoreConstants } from '../constants'; +import { CoreAppDB, CoreAppDBService } from '@services/app-db'; type DevelopmentWindow = Window & { browser?: typeof CoreBrowser; - appProvider?: CoreAppProvider; + appDBService?: CoreAppDBService; configProvider?: CoreConfigProvider; dbProvider?: CoreDbProvider; urlSchemes?: CoreCustomURLSchemesProvider; @@ -36,7 +36,7 @@ type DevelopmentWindow = Window & { */ function initializeDevelopmentWindow(window: DevelopmentWindow) { window.browser = CoreBrowser; - window.appProvider = CoreApp.instance; + window.appDBService = CoreAppDB.instance; window.configProvider = CoreConfig.instance; window.dbProvider = CoreDB.instance; window.urlSchemes = CoreCustomURLSchemes.instance; diff --git a/src/core/services/app-db.ts b/src/core/services/app-db.ts new file mode 100644 index 000000000..e420ca5de --- /dev/null +++ b/src/core/services/app-db.ts @@ -0,0 +1,188 @@ +// (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 { CoreDB } from '@services/db'; +import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; + +import { makeSingleton } from '@singletons'; +import { CoreLogger } from '@singletons/logger'; +import { DBNAME, SCHEMA_VERSIONS_TABLE_NAME, SCHEMA_VERSIONS_TABLE_SCHEMA, SchemaVersionsDBEntry } from '@services/database/app'; +import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy'; +import { asyncInstance } from '../utils/async-instance'; +import { CoreDatabaseTable } from '@classes/database/database-table'; + +/** + * Factory to provide access to the global app database. + * + * @description + * Each service or component should be responsible of creating their own database tables. Example: + * + * ```ts + * CoreAppDB.getDB(); + * CoreAppDB.createTableFromSchema(this.tableSchema); + * ``` + */ +@Injectable({ providedIn: 'root' }) +export class CoreAppDBService { + + protected db?: SQLiteDB; + protected logger: CoreLogger; + protected schemaVersionsTable = asyncInstance>(); + + constructor() { + this.logger = CoreLogger.getInstance('CoreAppDB'); + } + + /** + * Initialize database. + */ + async initializeDatabase(): Promise { + const database = this.getDB(); + + await database.createTableFromSchema(SCHEMA_VERSIONS_TABLE_SCHEMA); + + const schemaVersionsTable = new CoreDatabaseTableProxy( + { cachingStrategy: CoreDatabaseCachingStrategy.Eager }, + database, + SCHEMA_VERSIONS_TABLE_NAME, + ['name'], + ); + + await schemaVersionsTable.initialize(); + + this.schemaVersionsTable.setInstance(schemaVersionsTable); + } + + /** + * Install and upgrade a certain schema. + * + * @param schema The schema to create. + */ + async createTablesFromSchema(schema: CoreAppSchema): Promise { + this.logger.debug(`Apply schema to app DB: ${schema.name}`); + + try { + const oldVersion = await this.getInstalledSchemaVersion(schema); + + if (oldVersion >= schema.version) { + // Version already installed, nothing else to do. + return; + } + + this.logger.debug(`Migrating schema '${schema.name}' of app DB from version ${oldVersion} to ${schema.version}`); + + if (schema.tables) { + await this.getDB().createTablesFromSchema(schema.tables); + } + if (schema.install && oldVersion === 0) { + await schema.install(this.getDB()); + } + if (schema.migrate && oldVersion > 0) { + await schema.migrate(this.getDB(), oldVersion); + } + + // Set installed version. + await this.schemaVersionsTable.insert({ name: schema.name, version: schema.version }); + } catch (error) { + // Only log the error, don't throw it. + this.logger.error(`Error applying schema to app DB: ${schema.name}`, error); + } + } + + /** + * Delete table schema. + * + * @param name Schema name. + */ + async deleteTableSchema(name: string): Promise { + await this.schemaVersionsTable.deleteByPrimaryKey({ name }); + } + + /** + * Get the application global database. + * + * @returns App's DB. + */ + getDB(): SQLiteDB { + if (!this.db) { + this.db = CoreDB.getDB(DBNAME); + } + + return this.db; + } + + /** + * Get the installed version for the given schema. + * + * @param schema App schema. + * @returns Installed version number, or 0 if the schema is not installed. + */ + protected async getInstalledSchemaVersion(schema: CoreAppSchema): Promise { + try { + // Fetch installed version of the schema. + const entry = await this.schemaVersionsTable.getOneByPrimaryKey({ name: schema.name }); + + return entry.version; + } catch { + // No installed version yet. + return 0; + } + } + +} + +export const CoreAppDB = makeSingleton(CoreAppDBService); + +/** + * App DB schema and migration function. + */ +export type CoreAppSchema = { + /** + * Name of the schema. + */ + name: string; + + /** + * Latest version of the schema (integer greater than 0). + */ + version: number; + + /** + * Tables to create when installing or upgrading the schema. + */ + tables?: SQLiteDBTableSchema[]; + + /** + * Migrates the schema to the latest version. + * + * Called when upgrading the schema, after creating the defined tables. + * + * @param db The affected DB. + * @param oldVersion Old version of the schema or 0 if not installed. + * @returns Promise resolved when done. + */ + migrate?(db: SQLiteDB, oldVersion: number): Promise; + + /** + * Make changes to install the schema. + * + * Called when installing the schema, after creating the defined tables. + * + * @param db Site database. + * @returns Promise resolved when done. + */ + install?(db: SQLiteDB): Promise | void; +}; diff --git a/src/core/services/app.ts b/src/core/services/app.ts index f5028cb8d..5da40dbb9 100644 --- a/src/core/services/app.ts +++ b/src/core/services/app.ts @@ -14,62 +14,43 @@ import { Injectable } from '@angular/core'; -import { CoreDB } from '@services/db'; -import { CoreEventObserver, CoreEvents } from '@singletons/events'; -import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; - +import { CoreAppDB, CoreAppSchema } from './app-db'; +import { CoreEvents } from '@singletons/events'; +import { SQLiteDB } from '@classes/sqlitedb'; import { makeSingleton, StatusBar } from '@singletons'; import { CoreLogger } from '@singletons/logger'; import { CoreColors } from '@singletons/colors'; -import { DBNAME, SCHEMA_VERSIONS_TABLE_NAME, SCHEMA_VERSIONS_TABLE_SCHEMA, SchemaVersionsDBEntry } from '@services/database/app'; -import { CoreObject } from '@singletons/object'; import { CoreRedirectPayload } from './navigator'; -import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy'; -import { asyncInstance } from '../utils/async-instance'; -import { CoreDatabaseTable } from '@classes/database/database-table'; import { CorePromisedValue } from '@classes/promised-value'; import { Subscription } from 'rxjs'; import { CorePlatform } from '@services/platform'; import { CoreKeyboard } from '@singletons/keyboard'; import { CoreNetwork } from './network'; +import { CoreSSO } from '@singletons/sso'; +import { CoreRedirectData, CoreRedirects } from '@singletons/redirects'; import { MAIN_MENU_VISIBILITY_UPDATED_EVENT } from '@features/mainmenu/constants'; /** - * Factory to provide some global functionalities, like access to the global app database. - * - * @description - * Each service or component should be responsible of creating their own database tables. Example: - * - * ```ts - * constructor(appProvider: CoreAppProvider) { - * this.appDB = appProvider.getDB(); - * this.appDB.createTableFromSchema(this.tableSchema); - * } - * ``` + * Factory to provide some global functionalities. */ @Injectable({ providedIn: 'root' }) export class CoreAppProvider { - protected db?: SQLiteDB; - protected logger: CoreLogger; - protected ssoAuthenticationDeferred?: CorePromisedValue; - protected redirect?: CoreRedirectData; - protected schemaVersionsTable = asyncInstance>(); - protected mainMenuListener?: CoreEventObserver; + protected logger: CoreLogger = CoreLogger.getInstance('CoreApp'); - constructor() { - this.logger = CoreLogger.getInstance('CoreAppProvider'); - if (CorePlatform.isAndroid()) { - this.mainMenuListener = - CoreEvents.on(MAIN_MENU_VISIBILITY_UPDATED_EVENT, () => this.setAndroidNavigationBarColor()); + initialize(): void { + if (!CorePlatform.isAndroid()) { + return; } + + CoreEvents.on(MAIN_MENU_VISIBILITY_UPDATED_EVENT, () => this.setAndroidNavigationBarColor()); } /** * Returns whether the user agent is controlled by automation. I.e. Behat testing. * - * @deprecated since 4.4. Use CorePlatform.isAutomated() instead. * @returns True if the user agent is controlled by automation, false otherwise. + * @deprecated since 4.4. Use CorePlatform.isAutomated() instead. */ static isAutomated(): boolean { return CorePlatform.isAutomated(); @@ -79,38 +60,18 @@ export class CoreAppProvider { * Returns the forced timezone to use. Timezone is forced for automated tests. * * @returns Timezone. Undefined to use the user's timezone. + * @deprecated since 5.0. Use CoreTime.getForcedTimezone() instead. */ static getForcedTimezone(): string | undefined { - if (CorePlatform.isAutomated()) { - // Use the same timezone forced for LMS in tests. - return 'Australia/Perth'; - } - } - - /** - * Initialize database. - */ - async initializeDatabase(): Promise { - const database = this.getDB(); - - await database.createTableFromSchema(SCHEMA_VERSIONS_TABLE_SCHEMA); - - const schemaVersionsTable = new CoreDatabaseTableProxy( - { cachingStrategy: CoreDatabaseCachingStrategy.Eager }, - database, - SCHEMA_VERSIONS_TABLE_NAME, - ['name'], - ); - - await schemaVersionsTable.initialize(); - - this.schemaVersionsTable.setInstance(schemaVersionsTable); + // Use the same timezone forced for LMS in tests. + return CorePlatform.isAutomated() ? 'Australia/Perth' : undefined; } /** * Check if the browser supports mediaDevices.getUserMedia. * * @returns Whether the function is supported. + * @deprecated since 5.0. Use CoreMedia.canGetUserMedia() instead. */ canGetUserMedia(): boolean { return !!(navigator && navigator.mediaDevices && navigator.mediaDevices.getUserMedia); @@ -120,6 +81,7 @@ export class CoreAppProvider { * Check if the browser supports MediaRecorder. * * @returns Whether the function is supported. + * @deprecated since 5.0. Use CoreMedia.canRecordMedia() instead. */ canRecordMedia(): boolean { return !!window.MediaRecorder; @@ -134,60 +96,6 @@ export class CoreAppProvider { CoreKeyboard.close(); } - /** - * Install and upgrade a certain schema. - * - * @param schema The schema to create. - * @returns Promise resolved when done. - */ - async createTablesFromSchema(schema: CoreAppSchema): Promise { - this.logger.debug(`Apply schema to app DB: ${schema.name}`); - - const oldVersion = await this.getInstalledSchemaVersion(schema); - - if (oldVersion >= schema.version) { - // Version already installed, nothing else to do. - return; - } - - this.logger.debug(`Migrating schema '${schema.name}' of app DB from version ${oldVersion} to ${schema.version}`); - - if (schema.tables) { - await this.getDB().createTablesFromSchema(schema.tables); - } - if (schema.install && oldVersion === 0) { - await schema.install(this.getDB()); - } - if (schema.migrate && oldVersion > 0) { - await schema.migrate(this.getDB(), oldVersion); - } - - // Set installed version. - await this.schemaVersionsTable.insert({ name: schema.name, version: schema.version }); - } - - /** - * Delete table schema. - * - * @param name Schema name. - */ - async deleteTableSchema(name: string): Promise { - await this.schemaVersionsTable.deleteByPrimaryKey({ name }); - } - - /** - * Get the application global database. - * - * @returns App's DB. - */ - getDB(): SQLiteDB { - if (!this.db) { - this.db = CoreDB.getDB(DBNAME); - } - - return this.db; - } - /** * Get app store URL. * @@ -244,9 +152,11 @@ export class CoreAppProvider { * Checks if the current window is wider than a mobile. * * @returns Whether the app the current window is wider than a mobile. + * + * @deprecated since 5.0. Use CorePlatform.isWide() instead. */ isWide(): boolean { - return CorePlatform.width() > 768; + return CorePlatform.isWide(); } /** @@ -310,44 +220,40 @@ export class CoreAppProvider { * Start an SSO authentication process. * Please notice that this function should be called when the app receives the new token from the browser, * NOT when the browser is opened. + * + * @deprecated since 5.0. Use CoreSSO.startSSOAuthentication instead. */ startSSOAuthentication(): void { - this.ssoAuthenticationDeferred = new CorePromisedValue(); - - // Resolve it automatically after 10 seconds (it should never take that long). - const cancelTimeout = setTimeout(() => this.finishSSOAuthentication(), 10000); - - // If the promise is resolved because finishSSOAuthentication is called, stop the cancel promise. - // eslint-disable-next-line promise/catch-or-return - this.ssoAuthenticationDeferred.then(() => clearTimeout(cancelTimeout)); + CoreSSO.startSSOAuthentication(); } /** * Finish an SSO authentication process. + * + * @deprecated since 5.0. Use CoreSSO.finishSSOAuthentication instead. */ finishSSOAuthentication(): void { - if (this.ssoAuthenticationDeferred) { - this.ssoAuthenticationDeferred.resolve(); - this.ssoAuthenticationDeferred = undefined; - } + CoreSSO.finishSSOAuthentication(); } /** * Check if there's an ongoing SSO authentication process. * * @returns Whether there's a SSO authentication ongoing. + * @deprecated since 5.0. Use CoreSSO.isSSOAuthenticationOngoing instead. */ isSSOAuthenticationOngoing(): boolean { - return !!this.ssoAuthenticationDeferred; + return CoreSSO.isSSOAuthenticationOngoing(); } /** * Returns a promise that will be resolved once SSO authentication finishes. * * @returns Promise resolved once SSO authentication finishes. + * @deprecated since 5.0. Use CoreSSO.waitForSSOAuthentication instead. */ async waitForSSOAuthentication(): Promise { - await this.ssoAuthenticationDeferred; + return CoreSSO.waitForSSOAuthentication(); } /** @@ -375,55 +281,25 @@ export class CoreAppProvider { resumeSubscription = CorePlatform.resume.subscribe(stopWaiting); timeoutId = timeout ? window.setTimeout(stopWaiting, timeout) : null; - await deferred; - } + await deferred; } /** * Read redirect data from local storage and clear it if it existed. + * + * @deprecated since 5.0. Use CoreRedirects.consumeStorageRedirect instead. */ consumeStorageRedirect(): void { - if (!localStorage?.getItem) { - return; - } - - try { - // Read data from storage. - const jsonData = localStorage.getItem('CoreRedirect'); - - if (!jsonData) { - return; - } - - // Clear storage. - localStorage.removeItem('CoreRedirect'); - - // Remember redirect data. - const data: CoreRedirectData = JSON.parse(jsonData); - - if (!CoreObject.isEmpty(data)) { - this.redirect = data; - } - } catch (error) { - this.logger.error('Error loading redirect data:', error); - } + CoreRedirects.consumeStorageRedirect(); } /** * Retrieve and forget redirect data. * * @returns Redirect data if any. + * @deprecated since 5.0. Use CoreRedirects.consumeMemoryRedirect instead. */ consumeMemoryRedirect(): CoreRedirectData | null { - const redirect = this.getRedirect(); - - this.forgetRedirect(); - - if (redirect && (!redirect.timemodified || Date.now() - redirect.timemodified > 300000)) { - // Redirect data is only valid for 5 minutes, discard it. - return null; - } - - return redirect; + return CoreRedirects.consumeMemoryRedirect(); } /** @@ -436,18 +312,21 @@ export class CoreAppProvider { /** * Forget redirect data. + * + * @deprecated since 5.0. Use CoreRedirects.forgetRedirect instead. */ forgetRedirect(): void { - delete this.redirect; + CoreRedirects.forgetRedirect(); } /** * Retrieve redirect data. * * @returns Redirect data if any. + * @deprecated since 5.0. Use CoreRedirects.getRedirect instead. */ getRedirect(): CoreRedirectData | null { - return this.redirect || null; + return CoreRedirects.getRedirect(); } /** @@ -455,23 +334,11 @@ export class CoreAppProvider { * * @param siteId Site ID. * @param redirectData Redirect data. + * + * @deprecated since 5.0. Use CoreRedirects.storeRedirect instead. */ storeRedirect(siteId: string, redirectData: CoreRedirectPayload = {}): void { - if (!redirectData.redirectPath && !redirectData.urlToOpen) { - return; - } - - try { - const redirect: CoreRedirectData = { - siteId, - timemodified: Date.now(), - ...redirectData, - }; - - localStorage.setItem('CoreRedirect', JSON.stringify(redirect)); - } catch { - // Ignore errors. - } + CoreRedirects.storeRedirect(siteId, redirectData); } /** @@ -479,8 +346,7 @@ export class CoreAppProvider { */ setSystemUIColors(): void { this.setStatusBarColor(); - this.setAndroidNavigationBarColor(); - } + this.setAndroidNavigationBarColor(); } /** * Set StatusBar color depending on platform. @@ -502,24 +368,6 @@ export class CoreAppProvider { StatusBar.backgroundColorByHexString(color); } - /** - * Get the installed version for the given schema. - * - * @param schema App schema. - * @returns Installed version number, or 0 if the schema is not installed. - */ - protected async getInstalledSchemaVersion(schema: CoreAppSchema): Promise { - try { - // Fetch installed version of the schema. - const entry = await this.schemaVersionsTable.getOneByPrimaryKey({ name: schema.name }); - - return entry.version; - } catch { - // No installed version yet. - return 0; - } - } - /** * Set NavigationBar color for Android * @@ -541,18 +389,49 @@ export class CoreAppProvider { ( window).StatusBar.navigationBackgroundColorByHexString(color); } + /** + * Initialize database. + * + * @deprecated since 5.0. Use CoreAppDB.initialize instead. + */ + async initializeDatabase(): Promise { + await CoreAppDB.initializeDatabase(); + } + + /** + * Install and upgrade a certain schema. + * + * @param schema The schema to create. + * @deprecated since 5.0. Use CoreAppDB.createTablesFromSchema instead. + */ + async createTablesFromSchema(schema: CoreAppSchema): Promise { + await CoreAppDB.createTablesFromSchema(schema); + + } + + /** + * Delete table schema. + * + * @param name Schema name. + * @deprecated since 5.0. Use CoreAppDB.deleteTableSchema instead. + */ + async deleteTableSchema(name: string): Promise { + await CoreAppDB.deleteTableSchema(name); + } + + /** + * Get the application global database. + * + * @returns App's DB. + * @deprecated since 5.0. Use CoreAppDB.getDB instead. + */ + getDB(): SQLiteDB { + return CoreAppDB.getDB(); + } + } - export const CoreApp = makeSingleton(CoreAppProvider); -/** - * Data stored for a redirect to another page/site. - */ -export type CoreRedirectData = CoreRedirectPayload & { - siteId?: string; // ID of the site to load. - timemodified?: number; // Timestamp when this redirect was last modified. -}; - /** * Store config data. */ @@ -577,44 +456,3 @@ export type CoreStoreConfig = { */ default?: string; }; - -/** - * App DB schema and migration function. - */ -export type CoreAppSchema = { - /** - * Name of the schema. - */ - name: string; - - /** - * Latest version of the schema (integer greater than 0). - */ - version: number; - - /** - * Tables to create when installing or upgrading the schema. - */ - tables?: SQLiteDBTableSchema[]; - - /** - * Migrates the schema to the latest version. - * - * Called when upgrading the schema, after creating the defined tables. - * - * @param db The affected DB. - * @param oldVersion Old version of the schema or 0 if not installed. - * @returns Promise resolved when done. - */ - migrate?(db: SQLiteDB, oldVersion: number): Promise; - - /** - * Make changes to install the schema. - * - * Called when installing the schema, after creating the defined tables. - * - * @param db Site database. - * @returns Promise resolved when done. - */ - install?(db: SQLiteDB): Promise | void; -}; diff --git a/src/core/services/config.ts b/src/core/services/config.ts index 754678584..aa27a80f2 100644 --- a/src/core/services/config.ts +++ b/src/core/services/config.ts @@ -15,7 +15,7 @@ import { EnvironmentConfig } from '@/types/config'; import { Injectable } from '@angular/core'; import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy'; -import { CoreApp } from '@services/app'; +import { CoreAppDB } from './app-db'; import { APP_SCHEMA, ConfigDBEntry, CONFIG_TABLE_NAME } from '@services/database/config'; import { makeSingleton } from '@singletons'; import { CoreConstants } from '../constants'; @@ -73,15 +73,11 @@ export class CoreConfigProvider { * Initialize database. */ async initializeDatabase(): Promise { - try { - await CoreApp.createTablesFromSchema(APP_SCHEMA); - } catch { - // Ignore errors. - } + await CoreAppDB.createTablesFromSchema(APP_SCHEMA); const table = new CoreDatabaseTableProxy( { cachingStrategy: CoreDatabaseCachingStrategy.Eager }, - CoreApp.getDB(), + CoreAppDB.getDB(), CONFIG_TABLE_NAME, ['name'], ); @@ -129,7 +125,7 @@ export class CoreConfigProvider { * @returns Resolves upon success along with the config data. Reject on failure. */ async getFromDB(name: string): Promise { - const db = CoreApp.getDB(); + const db = CoreAppDB.getDB(); const record = await db.getRecord(CONFIG_TABLE_NAME, { name }); return record.value; diff --git a/src/core/services/cron.ts b/src/core/services/cron.ts index 01c3505b9..b8f393ab6 100644 --- a/src/core/services/cron.ts +++ b/src/core/services/cron.ts @@ -14,7 +14,7 @@ import { Injectable } from '@angular/core'; -import { CoreApp } from '@services/app'; +import { CoreAppDB } from '@services/app-db'; import { CoreNetwork } from '@services/network'; import { CoreConfig } from '@services/config'; import { CoreUtils } from '@services/utils/utils'; @@ -52,15 +52,11 @@ export class CoreCronDelegateService { * Initialize database. */ async initializeDatabase(): Promise { - try { - await CoreApp.createTablesFromSchema(APP_SCHEMA); - } catch { - // Ignore errors. - } + await CoreAppDB.createTablesFromSchema(APP_SCHEMA); const table = new CoreDatabaseTableProxy( { cachingStrategy: CoreDatabaseCachingStrategy.Eager }, - CoreApp.getDB(), + CoreAppDB.getDB(), CRON_TABLE_NAME, ); diff --git a/src/core/services/database/config.ts b/src/core/services/database/config.ts index b441dc78e..37364a8f2 100644 --- a/src/core/services/database/config.ts +++ b/src/core/services/database/config.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { CoreAppSchema } from '@services/app'; +import { CoreAppSchema } from '@services/app-db'; /** * Database variables for for CoreConfig service. diff --git a/src/core/services/database/cron.ts b/src/core/services/database/cron.ts index d96d2aba1..cc6b6b281 100644 --- a/src/core/services/database/cron.ts +++ b/src/core/services/database/cron.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { CoreAppSchema } from '@services/app'; +import { CoreAppSchema } from '@services/app-db'; /** * Database variables for CoreCron service. diff --git a/src/core/services/database/filepool.ts b/src/core/services/database/filepool.ts index 52c701e4b..d896865ea 100644 --- a/src/core/services/database/filepool.ts +++ b/src/core/services/database/filepool.ts @@ -13,7 +13,7 @@ // limitations under the License. import { DownloadStatus } from '@/core/constants'; -import { CoreAppSchema } from '@services/app'; +import { CoreAppSchema } from '@services/app-db'; import { CoreSiteSchema } from '@services/sites'; /** diff --git a/src/core/services/database/local-notifications.ts b/src/core/services/database/local-notifications.ts index 80326c6f3..d28a4324d 100644 --- a/src/core/services/database/local-notifications.ts +++ b/src/core/services/database/local-notifications.ts @@ -13,7 +13,7 @@ // limitations under the License. import { CorePromisedValue } from '@classes/promised-value'; -import { CoreAppSchema } from '@services/app'; +import { CoreAppSchema } from '@services/app-db'; /** * Database variables for CoreLocalNotifications service. diff --git a/src/core/services/database/sites.ts b/src/core/services/database/sites.ts index 8f5bb55b1..6dcd0df53 100644 --- a/src/core/services/database/sites.ts +++ b/src/core/services/database/sites.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { CoreAppSchema } from '@services/app'; +import { CoreAppSchema } from '@services/app-db'; import { CoreSiteSchema } from '@services/sites'; import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; diff --git a/src/core/services/database/storage.ts b/src/core/services/database/storage.ts index 6744b673d..dd9a248a1 100644 --- a/src/core/services/database/storage.ts +++ b/src/core/services/database/storage.ts @@ -13,7 +13,7 @@ // limitations under the License. import { SQLiteDBTableSchema } from '@classes/sqlitedb'; -import { CoreAppSchema } from '@services/app'; +import { CoreAppSchema } from '@services/app-db'; import { CoreSiteSchema } from '@services/sites'; export const TABLE_NAME = 'core_storage'; diff --git a/src/core/services/filepool.ts b/src/core/services/filepool.ts index e0417317f..0d88ba9f0 100644 --- a/src/core/services/filepool.ts +++ b/src/core/services/filepool.ts @@ -15,7 +15,7 @@ import { Injectable } from '@angular/core'; import { Md5 } from 'ts-md5/dist/md5'; -import { CoreApp } from '@services/app'; +import { CoreAppDB } from '@services/app-db'; import { CoreNetwork } from '@services/network'; import { CoreEventPackageStatusChanged, CoreEvents } from '@singletons/events'; import { CoreFile } from '@services/file'; @@ -175,15 +175,11 @@ export class CoreFilepoolProvider { * Initialize database. */ async initializeDatabase(): Promise { - try { - await CoreApp.createTablesFromSchema(APP_SCHEMA); - } catch (e) { - // Ignore errors. - } + await CoreAppDB.createTablesFromSchema(APP_SCHEMA); const queueTable = new CoreDatabaseTableProxy( { cachingStrategy: CoreDatabaseCachingStrategy.Lazy }, - CoreApp.getDB(), + CoreAppDB.getDB(), QUEUE_TABLE_NAME, [...QUEUE_TABLE_PRIMARY_KEYS], ); diff --git a/src/core/services/local-notifications.ts b/src/core/services/local-notifications.ts index 06daa1eea..577de8730 100644 --- a/src/core/services/local-notifications.ts +++ b/src/core/services/local-notifications.ts @@ -16,7 +16,7 @@ import { Injectable } from '@angular/core'; import { Subject, Subscription } from 'rxjs'; import { ILocalNotification } from '@awesome-cordova-plugins/local-notifications'; -import { CoreApp } from '@services/app'; +import { CoreAppDB } from '@services/app-db'; import { CoreConfig } from '@services/config'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreText } from '@singletons/text'; @@ -185,13 +185,9 @@ export class CoreLocalNotificationsProvider { * Initialize database. */ async initializeDatabase(): Promise { - try { - await CoreApp.createTablesFromSchema(APP_SCHEMA); - } catch { - // Ignore errors. - } + await CoreAppDB.createTablesFromSchema(APP_SCHEMA); - const database = CoreApp.getDB(); + const database = CoreAppDB.getDB(); const sitesTable = new CoreDatabaseTableProxy( { cachingStrategy: CoreDatabaseCachingStrategy.None }, database, diff --git a/src/core/services/platform.ts b/src/core/services/platform.ts index 61b1d7194..39658961d 100644 --- a/src/core/services/platform.ts +++ b/src/core/services/platform.ts @@ -91,6 +91,15 @@ export class CorePlatformService extends Platform { return this.is('cordova'); } + /** + * Checks if the current window is wider than a mobile. + * + * @returns Whether the app the current window is wider than a mobile. + */ + isWide(): boolean { + return this.width() > 768; + } + /** * Check whether the device is configured to reduce motion. * diff --git a/src/core/services/sites.ts b/src/core/services/sites.ts index b97e16f93..2914cebc1 100644 --- a/src/core/services/sites.ts +++ b/src/core/services/sites.ts @@ -68,6 +68,8 @@ import { CoreHTMLClasses } from '@singletons/html-classes'; import { CoreSiteErrorDebug } from '@classes/errors/siteerror'; import { CoreErrorHelper } from './error-helper'; import { CoreQueueRunner } from '@classes/queue-runner'; +import { CoreAppDB } from './app-db'; +import { CoreRedirects } from '@singletons/redirects'; export const CORE_SITE_SCHEMAS = new InjectionToken('CORE_SITE_SCHEMAS'); export const CORE_SITE_CURRENT_SITE_ID_CONFIG = 'current_site_id'; @@ -201,15 +203,11 @@ export class CoreSitesProvider { * Initialize database. */ async initializeDatabase(): Promise { - try { - await CoreApp.createTablesFromSchema(APP_SCHEMA); - } catch { - // Ignore errors. - } + await CoreAppDB.createTablesFromSchema(APP_SCHEMA); const sitesTable = new CoreDatabaseTableProxy( { cachingStrategy: CoreDatabaseCachingStrategy.Eager }, - CoreApp.getDB(), + CoreAppDB.getDB(), SITES_TABLE_NAME, ); @@ -1198,7 +1196,7 @@ export class CoreSitesProvider { * @returns Site. */ async getSiteFromDB(siteId: string): Promise { - const db = CoreApp.getDB(); + const db = CoreAppDB.getDB(); try { const record = await db.getRecord(SITES_TABLE_NAME, { id: siteId }); @@ -1500,7 +1498,7 @@ export class CoreSitesProvider { if (CoreSitePlugins.hasSitePluginsLoaded) { // The site has site plugins so the app will be restarted. Store the data and logout. - CoreApp.storeRedirect(siteId, redirectData); + CoreRedirects.storeRedirect(siteId, redirectData); } await this.logout(); @@ -2052,12 +2050,12 @@ export class CoreSitesProvider { } try { - const db = CoreApp.getDB(); + const db = CoreAppDB.getDB(); const { siteId } = await db.getRecord<{ siteId: string }>('current_site'); await CoreConfig.set(CORE_SITE_CURRENT_SITE_ID_CONFIG, siteId); - await CoreApp.deleteTableSchema('current_site'); + await CoreAppDB.deleteTableSchema('current_site'); await db.dropTable('current_site'); } catch { // There was no current site, silence the error. diff --git a/src/core/services/storage.ts b/src/core/services/storage.ts index 4191f8060..f16fb41f0 100644 --- a/src/core/services/storage.ts +++ b/src/core/services/storage.ts @@ -15,7 +15,7 @@ import { Inject, Injectable, Optional } from '@angular/core'; import { AsyncInstance, asyncInstance } from '@/core/utils/async-instance'; -import { CoreApp } from '@services/app'; +import { CoreAppDB } from './app-db'; import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy'; import { CoreDatabaseTable } from '@classes/database/database-table'; import { makeSingleton } from '@singletons'; @@ -32,7 +32,7 @@ import { NULL_INJECTION_TOKEN } from '@/core/constants'; * The data can be scoped to a single site using CoreStorage.forSite(site), and it will be automatically cleared * when the site is deleted. * - * For tabular data, use CoreAppProvider.getDB() or CoreSite.getDb(). + * For tabular data, use CoreAppDB.getDB() or CoreSite.getDb(). */ @Injectable({ providedIn: 'root' }) export class CoreStorageService { @@ -47,13 +47,9 @@ export class CoreStorageService { * Initialize database. */ async initializeDatabase(): Promise { - try { - await CoreApp.createTablesFromSchema(APP_SCHEMA); - } catch { - // Ignore errors. - } + await CoreAppDB.createTablesFromSchema(APP_SCHEMA); - await this.initializeTable(CoreApp.getDB()); + await this.initializeTable(CoreAppDB.getDB()); } /** @@ -97,7 +93,7 @@ export class CoreStorageService { async getFromDB(key: string, defaultValue: T): Promise; async getFromDB(key: string, defaultValue: T | null = null): Promise { try { - const db = CoreApp.getDB(); + const db = CoreAppDB.getDB(); const { value } = await db.getRecord(TABLE_NAME, { key }); return JSON.parse(value); diff --git a/src/core/services/update-manager.ts b/src/core/services/update-manager.ts index e7015b1b6..e14472526 100644 --- a/src/core/services/update-manager.ts +++ b/src/core/services/update-manager.ts @@ -22,7 +22,7 @@ import { CoreH5P } from '@features/h5p/services/h5p'; import { CoreLoginHelper } from '@features/login/services/login-helper'; import { CoreSites } from './sites'; import { CoreUtils } from './utils/utils'; -import { CoreApp } from './app'; +import { CoreRedirects } from '@singletons/redirects'; import { CoreZoomLevel } from '@features/settings/services/settings-helper'; import { CorePromisedValue } from '@classes/promised-value'; import { CoreFile } from './file'; @@ -138,7 +138,7 @@ export class CoreUpdateManagerProvider { await CoreSites.removeStoredCurrentSite(); // Tell the app to open add site so the user can add the new site. - CoreApp.storeRedirect(CoreConstants.NO_SITE_ID, { + CoreRedirects.storeRedirect(CoreConstants.NO_SITE_ID, { redirectPath: '/login/sites', redirectOptions: { params: { diff --git a/src/core/services/urlschemes.ts b/src/core/services/urlschemes.ts index 74a83f90b..35c419d95 100644 --- a/src/core/services/urlschemes.ts +++ b/src/core/services/urlschemes.ts @@ -23,7 +23,7 @@ import { ApplicationInit, makeSingleton, Translate } from '@singletons'; import { CoreLogger } from '@singletons/logger'; import { CorePath } from '@singletons/path'; import { CoreConstants } from '../constants'; -import { CoreApp } from './app'; +import { CoreSSO } from '@singletons/sso'; import { CoreNavigator, CoreRedirectPayload } from './navigator'; import { CoreSiteCheckResponse, CoreSites } from './sites'; import { CoreDomUtils } from './utils/dom'; @@ -217,7 +217,7 @@ export class CoreCustomURLSchemesProvider { modal.dismiss(); if (data.isSSOToken) { - CoreApp.finishSSOAuthentication(); + CoreSSO.finishSSOAuthentication(); } } } @@ -349,13 +349,13 @@ export class CoreCustomURLSchemesProvider { throw new CoreCustomURLSchemesHandleError(null); } - if (CoreApp.isSSOAuthenticationOngoing()) { + if (CoreSSO.isSSOAuthenticationOngoing()) { // Authentication ongoing, probably duplicated request. throw new CoreCustomURLSchemesHandleError('Duplicated'); } // App opened using custom URL scheme. Probably an SSO authentication. - CoreApp.startSSOAuthentication(); + CoreSSO.startSSOAuthentication(); this.logger.debug('App launched by URL with an SSO'); // Delete the sso scheme from the URL. diff --git a/src/core/singletons/media.ts b/src/core/singletons/media.ts index eebca8793..2525f5fb5 100644 --- a/src/core/singletons/media.ts +++ b/src/core/singletons/media.ts @@ -93,6 +93,24 @@ export class CoreMedia { return sources.some(source => CoreMedia.sourceUsesJavascriptPlayer(source)); } + /** + * Check if the browser supports mediaDevices.getUserMedia. + * + * @returns Whether the function is supported. + */ + static canGetUserMedia(): boolean { + return !!(navigator && navigator.mediaDevices && navigator.mediaDevices.getUserMedia); + } + + /** + * Check if the browser supports MediaRecorder. + * + * @returns Whether the function is supported. + */ + static canRecordMedia(): boolean { + return !!window.MediaRecorder; + } + } /** diff --git a/src/core/singletons/redirects.ts b/src/core/singletons/redirects.ts new file mode 100644 index 000000000..f0e5f0a57 --- /dev/null +++ b/src/core/singletons/redirects.ts @@ -0,0 +1,130 @@ +// (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 { CoreRedirectPayload } from '@services/navigator'; +import { CoreLogger } from './logger'; +import { CoreObject } from './object'; + +/** + * Singleton with helper functions to manage redirects. + * + * This singleton is not necessary to be exported for site plugins. + */ +export class CoreRedirects { + + private static redirect?: CoreRedirectData; + protected static logger = CoreLogger.getInstance('CoreRedirects'); + + // Avoid creating singleton instances. + private constructor() { + // Nothing to do. + } + + /** + * Read redirect data from local storage and clear it if it existed. + */ + static consumeStorageRedirect(): void { + if (!localStorage?.getItem) { + return; + } + + try { + // Read data from storage. + const jsonData = localStorage.getItem('CoreRedirect'); + + if (!jsonData) { + return; + } + + // Clear storage. + localStorage.removeItem('CoreRedirect'); + + // Remember redirect data. + const data: CoreRedirectData = JSON.parse(jsonData); + + if (!CoreObject.isEmpty(data)) { + CoreRedirects.redirect = data; + } + } catch (error) { + CoreRedirects.logger.error('Error loading redirect data:', error); + } + } + + /** + * Retrieve and forget redirect data. + * + * @returns Redirect data if any. + */ + static consumeMemoryRedirect(): CoreRedirectData | null { + const redirect = CoreRedirects.getRedirect(); + + CoreRedirects.forgetRedirect(); + + if (redirect && (!redirect.timemodified || Date.now() - redirect.timemodified > 300000)) { + // Redirect data is only valid for 5 minutes, discard it. + return null; + } + + return redirect; + } + + /** + * Forget redirect data. + */ + static forgetRedirect(): void { + delete CoreRedirects.redirect; + } + + /** + * Retrieve redirect data. + * + * @returns Redirect data if any. + */ + static getRedirect(): CoreRedirectData | null { + return CoreRedirects.redirect || null; + } + + /** + * Store redirect params. + * + * @param siteId Site ID. + * @param redirectData Redirect data. + */ + static storeRedirect(siteId: string, redirectData: CoreRedirectPayload = {}): void { + if (!redirectData.redirectPath && !redirectData.urlToOpen) { + return; + } + + try { + const redirect: CoreRedirectData = { + siteId, + timemodified: Date.now(), + ...redirectData, + }; + + localStorage.setItem('CoreRedirect', JSON.stringify(redirect)); + } catch { + // Ignore errors. + } + } + +} + +/** + * Data stored for a redirect to another page/site. + */ +export type CoreRedirectData = CoreRedirectPayload & { + siteId?: string; // ID of the site to load. + timemodified?: number; // Timestamp when this redirect was last modified. +}; diff --git a/src/core/singletons/sso.ts b/src/core/singletons/sso.ts new file mode 100644 index 000000000..ae660f360 --- /dev/null +++ b/src/core/singletons/sso.ts @@ -0,0 +1,75 @@ +// (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 { CorePromisedValue } from '@classes/promised-value'; + +/** + * Singleton with helper functions for Single Sign On. + */ +export class CoreSSO { + + private static ssoAuthenticationDeferred?: CorePromisedValue; + + // Avoid creating singleton instances. + private constructor() { + // Nothing to do. + } + + /** + * Start an SSO authentication process. + * Please notice that this function should be called when the app receives the new token from the browser, + * NOT when the browser is opened. + */ + static startSSOAuthentication(): void { + CoreSSO.ssoAuthenticationDeferred = new CorePromisedValue(); + + // Resolve it automatically after 10 seconds (it should never take that long). + const cancelTimeout = setTimeout(() => CoreSSO.finishSSOAuthentication(), 10000); + + // If the promise is resolved because finishSSOAuthentication is called, stop the cancel promise. + // eslint-disable-next-line promise/catch-or-return + CoreSSO.ssoAuthenticationDeferred.then(() => clearTimeout(cancelTimeout)); + } + + /** + * Finish an SSO authentication process. + */ + static finishSSOAuthentication(): void { + if (!CoreSSO.ssoAuthenticationDeferred) { + return; + } + + CoreSSO.ssoAuthenticationDeferred.resolve(); + CoreSSO.ssoAuthenticationDeferred = undefined; + } + + /** + * Check if there's an ongoing SSO authentication process. + * + * @returns Whether there's a SSO authentication ongoing. + */ + static isSSOAuthenticationOngoing(): boolean { + return !!CoreSSO.ssoAuthenticationDeferred; + } + + /** + * Returns a promise that will be resolved once SSO authentication finishes. + * + * @returns Promise resolved once SSO authentication finishes. + */ + static async waitForSSOAuthentication(): Promise { + await CoreSSO.ssoAuthenticationDeferred; + } + +} diff --git a/src/core/singletons/time.ts b/src/core/singletons/time.ts index 69548a694..a1015828d 100644 --- a/src/core/singletons/time.ts +++ b/src/core/singletons/time.ts @@ -14,12 +14,70 @@ import { Translate } from '@singletons'; import { CoreConstants } from '../constants'; +import { CorePlatform } from '@services/platform'; /** * Singleton with helper functions for time operations. */ export class CoreTime { + protected static readonly LEGACY_TIMEZONES = { + '-13.0': 'Australia/Perth', + '-12.5': 'Etc/GMT+12', + '-12.0': 'Etc/GMT+12', + '-11.5': 'Etc/GMT+11', + '-11.0': 'Etc/GMT+11', + '-10.5': 'Etc/GMT+10', + '-10.0': 'Etc/GMT+10', + '-9.5': 'Etc/GMT+9', + '-9.0': 'Etc/GMT+9', + '-8.5': 'Etc/GMT+8', + '-8.0': 'Etc/GMT+8', + '-7.5': 'Etc/GMT+7', + '-7.0': 'Etc/GMT+7', + '-6.5': 'Etc/GMT+6', + '-6.0': 'Etc/GMT+6', + '-5.5': 'Etc/GMT+5', + '-5.0': 'Etc/GMT+5', + '-4.5': 'Etc/GMT+4', + '-4.0': 'Etc/GMT+4', + '-3.5': 'Etc/GMT+3', + '-3.0': 'Etc/GMT+3', + '-2.5': 'Etc/GMT+2', + '-2.0': 'Etc/GMT+2', + '-1.5': 'Etc/GMT+1', + '-1.0': 'Etc/GMT+1', + '-0.5': 'Etc/GMT', + '0': 'Etc/GMT', + '0.0': 'Etc/GMT', + '0.5': 'Etc/GMT', + '1.0': 'Etc/GMT-1', + '1.5': 'Etc/GMT-1', + '2.0': 'Etc/GMT-2', + '2.5': 'Etc/GMT-2', + '3.0': 'Etc/GMT-3', + '3.5': 'Etc/GMT-3', + '4.0': 'Etc/GMT-4', + '4.5': 'Asia/Kabul', + '5.0': 'Etc/GMT-5', + '5.5': 'Asia/Kolkata', + '6.0': 'Etc/GMT-6', + '6.5': 'Asia/Rangoon', + '7.0': 'Etc/GMT-7', + '7.5': 'Etc/GMT-7', + '8.0': 'Etc/GMT-8', + '8.5': 'Etc/GMT-8', + '9.0': 'Etc/GMT-9', + '9.5': 'Australia/Darwin', + '10.0': 'Etc/GMT-10', + '10.5': 'Etc/GMT-10', + '11.0': 'Etc/GMT-11', + '11.5': 'Etc/GMT-11', + '12.0': 'Etc/GMT-12', + '12.5': 'Etc/GMT-12', + '13.0': 'Etc/GMT-13', + }; + /** * Returns years, months, days, hours, minutes and seconds in a human readable format. * @@ -119,4 +177,24 @@ export class CoreTime { }; } + /** + * Returns the forced timezone to use. Timezone is forced for automated tests. + * + * @returns Timezone. Undefined to use the user's timezone. + */ + static getForcedTimezone(): string | undefined { + // Use the same timezone forced for LMS in tests. + return CorePlatform.isAutomated() ? 'Australia/Perth' : undefined; + } + + /** + * Translates legacy timezone names. + * + * @param tz Timezone name. + * @returns Readable timezone name. + */ + static translateLegacyTimezone(tz: string): string { + return CoreTime.LEGACY_TIMEZONES[tz] ?? tz; + } + } diff --git a/src/testing/testing.module.ts b/src/testing/testing.module.ts index 9c3d29688..d78d2ee7a 100644 --- a/src/testing/testing.module.ts +++ b/src/testing/testing.module.ts @@ -13,7 +13,7 @@ // limitations under the License. import { APP_INITIALIZER, NgModule } from '@angular/core'; -import { CoreAppProvider } from '@services/app'; +import { CoreTime } from '@singletons/time'; import moment from 'moment-timezone'; import { TestingBehatRuntime, TestingBehatRuntimeService } from './services/behat-runtime'; import { CorePlatform } from '@services/platform'; @@ -35,7 +35,7 @@ function initializeAutomatedTests(window: AutomatedTestsWindow) { window.behat = TestingBehatRuntime.instance; // Force timezone for automated tests. - moment.tz.setDefault(CoreAppProvider.getForcedTimezone()); + moment.tz.setDefault(CoreTime.getForcedTimezone()); } @NgModule({