From 7f630b287afddd4fa340a7b3f27e53c35a06e0c3 Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Wed, 29 Nov 2023 12:16:20 +0100 Subject: [PATCH 1/6] MOBILE-3947 eslint: Unban Function type --- .eslintrc.js | 1 + src/core/features/siteplugins/services/siteplugins-helper.ts | 2 -- src/core/services/file.ts | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 260d9cef7..6007c251e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -47,6 +47,7 @@ const appConfig = { Object: { message: 'Use {} instead.', }, + Function: false, }, }, ], diff --git a/src/core/features/siteplugins/services/siteplugins-helper.ts b/src/core/features/siteplugins/services/siteplugins-helper.ts index 0307eb25a..ab0aa6b2e 100644 --- a/src/core/features/siteplugins/services/siteplugins-helper.ts +++ b/src/core/features/siteplugins/services/siteplugins-helper.ts @@ -613,7 +613,6 @@ export class CoreSitePluginsHelperProvider { for (const property of handlerProperties) { if (property !== 'constructor' && typeof handler[property] === 'function' && typeof jsResult[property] === 'function') { - // eslint-disable-next-line @typescript-eslint/ban-types handler[property] = ( jsResult[property]).bind(handler); } } @@ -837,7 +836,6 @@ export class CoreSitePluginsHelperProvider { for (const property of handlerProperties) { if (property !== 'constructor' && typeof handler[property] === 'function' && typeof jsResult[property] === 'function') { - // eslint-disable-next-line @typescript-eslint/ban-types handler[property] = ( jsResult[property]).bind(handler); } } diff --git a/src/core/services/file.ts b/src/core/services/file.ts index 0fccddc04..4a5e6659b 100644 --- a/src/core/services/file.ts +++ b/src/core/services/file.ts @@ -933,7 +933,6 @@ export class CoreFileProvider { // If destFolder is not set, use same location as ZIP file. We need to use absolute paths (including basePath). destFolder = this.addBasePathIfNeeded(destFolder || CoreMimetypeUtils.removeExtension(path)); - // eslint-disable-next-line @typescript-eslint/ban-types const result = await Zip.unzip(fileEntry.toURL(), destFolder, onProgress as unknown as Function); if (result == -1) { From 2449aca7813a73b31e2527271a53dcc7ca064eaf Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Wed, 29 Nov 2023 12:18:36 +0100 Subject: [PATCH 2/6] MOBILE-3947 ng: Fix build errors Target in app tsconfig is set to es2022 by the Angular CLI, so we must set it as well to be consistent in the rest of the tooling. Angular compilation later uses browserslist for further transpilations. Target in unit tests is kept at es2016 because of a known bug in Angular: https://github.com/angular/angular/issues/31730 --- package-lock.json | 97 +++------ package.json | 2 +- .../lesson/pages/user-retake/user-retake.ts | 18 +- .../mod/lesson/services/lesson-helper.ts | 2 +- src/addons/mod/scorm/services/scorm.ts | 10 +- src/app/app-routing.module.ts | 8 +- src/core/classes/application-init-status.ts | 4 +- .../features/course/services/course-helper.ts | 3 +- .../courses/services/courses-helper.ts | 3 +- src/core/features/emulator/services/file.ts | 191 ++++++++++++++---- src/core/features/emulator/services/zip.ts | 5 +- .../features/login/services/login-helper.ts | 5 +- .../components/user-menu/user-menu.ts | 2 +- .../plugin-content/plugin-content.ts | 3 +- src/core/initializers/inject-ios-scripts.ts | 13 +- tsconfig.json | 3 +- tsconfig.spec.json | 1 + 17 files changed, 235 insertions(+), 135 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1696a5d56..8cff3413b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -142,7 +142,7 @@ "jest-preset-angular": "^13.1.4", "jsonc-parser": "^2.3.1", "keytar": "^7.2.0", - "minimatch": "^5.1.0", + "minimatch": "^9.0.3", "native-run": "^2.0.0", "patch-package": "^6.5.0", "ts-jest": "^29.1.1", @@ -6058,21 +6058,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@tufjs/models/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -8701,21 +8686,6 @@ "node": ">=12" } }, - "node_modules/cacache/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/cacache/node_modules/minipass": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", @@ -13756,6 +13726,18 @@ "minimatch": "^5.0.1" } }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -16040,21 +16022,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/ignore-walk/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/image-size": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", @@ -20756,6 +20723,18 @@ "node": ">=12" } }, + "node_modules/make-fetch-happen/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/make-fetch-happen/node_modules/minipass": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", @@ -21237,15 +21216,18 @@ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, "node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -23193,21 +23175,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/pacote/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/pacote/node_modules/normalize-package-data": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", diff --git a/package.json b/package.json index 0c7095fb8..f385530cd 100644 --- a/package.json +++ b/package.json @@ -177,7 +177,7 @@ "jest-preset-angular": "^13.1.4", "jsonc-parser": "^2.3.1", "keytar": "^7.2.0", - "minimatch": "^5.1.0", + "minimatch": "^9.0.3", "native-run": "^2.0.0", "patch-package": "^6.5.0", "ts-jest": "^29.1.1", diff --git a/src/addons/mod/lesson/pages/user-retake/user-retake.ts b/src/addons/mod/lesson/pages/user-retake/user-retake.ts index eeb58ea30..0405044dc 100644 --- a/src/addons/mod/lesson/pages/user-retake/user-retake.ts +++ b/src/addons/mod/lesson/pages/user-retake/user-retake.ts @@ -218,7 +218,7 @@ export class AddonModLessonUserRetakePage implements OnInit { * @returns Formatted data. */ protected formatRetake(retakeData: AddonModLessonGetUserAttemptWSResponse): RetakeToDisplay { - const formattedData = retakeData; + const formattedData = retakeData; if (formattedData.userstats.gradeinfo) { // Completed. @@ -229,19 +229,23 @@ export class AddonModLessonUserRetakePage implements OnInit { // Format pages data. formattedData.answerpages.forEach((page) => { if (AddonModLesson.answerPageIsContent(page)) { - page.isContent = true; + const contentPage = page as AnswerPage; - if (page.answerdata?.answers) { - page.answerdata.answers.forEach((answer) => { + contentPage.isContent = true; + + if (contentPage.answerdata?.answers) { + contentPage.answerdata.answers.forEach((answer) => { // Content pages only have 1 valid field in the answer array. answer[0] = AddonModLessonHelper.getContentPageAnswerDataFromHtml(answer[0]); }); } } else if (AddonModLesson.answerPageIsQuestion(page)) { - page.isQuestion = true; + const questionPage = page as AnswerPage; - if (page.answerdata?.answers) { - page.answerdata.answers.forEach((answer) => { + questionPage.isQuestion = true; + + if (questionPage.answerdata?.answers) { + questionPage.answerdata.answers.forEach((answer) => { // Only the first field of the answer array requires to be parsed. answer[0] = AddonModLessonHelper.getQuestionPageAnswerDataFromHtml(answer[0]); }); diff --git a/src/addons/mod/lesson/services/lesson-helper.ts b/src/addons/mod/lesson/services/lesson-helper.ts index 18b3276c5..b12f4efc2 100644 --- a/src/addons/mod/lesson/services/lesson-helper.ts +++ b/src/addons/mod/lesson/services/lesson-helper.ts @@ -270,7 +270,7 @@ export class AddonModLessonHelperProvider { if (option.checked || multiChoiceQuestion.multi) { // Add the control. const value = multiChoiceQuestion.multi ? - { value: option.checked, disabled: option.disabled } : option.value; + { value: option.checked, disabled: option.disabled } : option.checked; questionForm.addControl(option.name, this.formBuilder.control(value)); controlAdded = true; } diff --git a/src/addons/mod/scorm/services/scorm.ts b/src/addons/mod/scorm/services/scorm.ts index 8fe7759d5..3fe5fab5e 100644 --- a/src/addons/mod/scorm/services/scorm.ts +++ b/src/addons/mod/scorm/services/scorm.ts @@ -337,7 +337,7 @@ export class AddonModScormProvider { const re = /^(\d+)\*\{(.+)\}$/; // Sets like 3*{S34, S36, S37, S39}. const reOther = /^(.+)(=|<>)(.+)$/; // Other symbols. - let matches = element.match(re); + const matches = element.match(re); if (matches) { const repeat = Number(matches[1]); @@ -363,18 +363,18 @@ export class AddonModScormProvider { element = '!'; } else if (reOther.test(element)) { // Other symbols = | <> . - matches = element.match(reOther) ?? []; - element = matches[1]?.trim(); + const otherMatches = element.match(reOther) ?? []; + element = otherMatches[1]?.trim(); if (trackData[element] !== undefined) { - let value = matches[3].trim().replace(/('|")/gi, ''); + let value = otherMatches[3].trim().replace(/('|")/gi, ''); let oper: string; if (STATUSES[value] !== undefined) { value = STATUSES[value]; } - if (matches[2] == '<>') { + if (otherMatches[2] == '<>') { oper = '!='; } else { oper = '=='; diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 3db6a2106..b66bd26b1 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { InjectionToken, Injector, ModuleWithProviders, NgModule } from '@angular/core'; +import { InjectionToken, Injector, ModuleWithProviders, NgModule, Type } from '@angular/core'; import { PreloadAllModules, RouterModule, @@ -97,6 +97,12 @@ function buildConditionalUrlMatcher(pathOrMatcher: string | UrlMatcher, conditio }; } +/** + * Type to declare lazy route modules. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type LazyRoutesModule = Type; + /** * Build url matcher using a regular expression. * diff --git a/src/core/classes/application-init-status.ts b/src/core/classes/application-init-status.ts index 8cfe10e86..c5c756de6 100644 --- a/src/core/classes/application-init-status.ts +++ b/src/core/classes/application-init-status.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { ApplicationInitStatus, APP_INITIALIZER, Injectable, Injector } from '@angular/core'; +import { ApplicationInitStatus, Injectable, Injector } from '@angular/core'; import { setSingletonsInjector } from '@singletons'; @Injectable() @@ -21,7 +21,7 @@ export class CoreApplicationInitStatus extends ApplicationInitStatus { constructor(injector: Injector) { setSingletonsInjector(injector); - super(injector.get(APP_INITIALIZER, [])); + super(); } whenDone(callback: () => unknown): void { diff --git a/src/core/features/course/services/course-helper.ts b/src/core/features/course/services/course-helper.ts index b6de863d4..cbc0fd3db 100644 --- a/src/core/features/course/services/course-helper.ts +++ b/src/core/features/course/services/course-helper.ts @@ -75,6 +75,7 @@ import { CoreRemindersPushNotificationData } from '@features/reminders/services/ import { CoreLocalNotifications } from '@services/local-notifications'; import { CoreEnrol } from '@features/enrol/services/enrol'; import { CoreEnrolAction, CoreEnrolDelegate } from '@features/enrol/services/enrol-delegate'; +import { LazyRoutesModule } from '@/app/app-routing.module'; /** * Prefetch info of a module. @@ -1990,7 +1991,7 @@ export class CoreCourseHelperProvider { * * @returns Course summary page module. */ - async getCourseSummaryRouteModule(): Promise { + async getCourseSummaryRouteModule(): Promise { return import('../course-summary-lazy.module').then(m => m.CoreCourseSummaryLazyModule); } diff --git a/src/core/features/courses/services/courses-helper.ts b/src/core/features/courses/services/courses-helper.ts index 54eb1ebfa..0c19e83ea 100644 --- a/src/core/features/courses/services/courses-helper.ts +++ b/src/core/features/courses/services/courses-helper.ts @@ -30,6 +30,7 @@ import { of, firstValueFrom } from 'rxjs'; import { zipIncludingComplete } from '@/core/utils/rxjs'; import { catchError, map } from 'rxjs/operators'; import { chainRequests, WSObservable } from '@classes/sites/authenticated-site'; +import { LazyRoutesModule } from '@/app/app-routing.module'; // Id for a course item representing all courses (for example, for course filters). export const ALL_COURSES_ID = -1; @@ -432,7 +433,7 @@ export class CoreCoursesHelperProvider { * * @returns My courses page module. */ - async getMyRouteModule(): Promise { + async getMyRouteModule(): Promise { return import('../courses-my-lazy.module').then(m => m.CoreCoursesMyLazyModule); } diff --git a/src/core/features/emulator/services/file.ts b/src/core/features/emulator/services/file.ts index a9a61a124..da8dfd3fb 100644 --- a/src/core/features/emulator/services/file.ts +++ b/src/core/features/emulator/services/file.ts @@ -12,8 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. +/* eslint-disable deprecation/deprecation */ + import { Injectable } from '@angular/core'; -import { File, Entry, DirectoryEntry, FileEntry, IWriteOptions, RemoveResult } from '@awesome-cordova-plugins/file/ngx'; +import { + File, + Entry, + FileEntry, + FileSystem, + IWriteOptions, + RemoveResult, + DirectoryEntry, + DirectoryReader, +} from '@awesome-cordova-plugins/file/ngx'; import { CorePath } from '@singletons/path'; /** @@ -42,6 +53,82 @@ class FileError { } +/** + * Native APIs used in webkit window. + */ +interface WebkitWindow { + + /** + * @deprecated + * @see https://www.w3.org/TR/2012/WD-file-system-api-20120417/ + */ + // eslint-disable-next-line @typescript-eslint/naming-convention + LocalFileSystem: { + readonly TEMPORARY: number; + readonly PERSISTENT: number; + }; + + /** + * @deprecated + * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/requestFileSystem + */ + requestFileSystem( + type: LocalFileSystem, + size: number, + successCallback: (fileSystem: FileSystem) => void, + errorCallback?: (fileError: FileError) => void, + ): void; + + /** + * @deprecated + */ + webkitRequestFileSystem( + type: LocalFileSystem, + size: number, + successCallback: (fileSystem: FileSystem) => void, + errorCallback?: (fileError: FileError) => void, + ): void; + + /** + * @deprecated + * @see https://www.w3.org/TR/2012/WD-file-system-api-20120417/ + */ + resolveLocalFileSystemURL( + url: string, + successCallback: (entry: Entry) => void, + errorCallback?: (fileError: FileError) => void, + ): void; + + /** + * @deprecated + */ + webkitResolveLocalFileSystemURL( + url: string, + successCallback: (entry: Entry) => void, + errorCallback?: (fileError: FileError) => void, + ): void; + +} + +/** + * Native APIs used in webkit navigator. + */ +interface WebkitNavigator { + + /** + * @deprecated + * @see https://developer.chrome.com/docs/apps/offline_storage/ + */ + webkitPersistentStorage: { + requestQuota( + newQuotaInBytes: number, + successCallback?: (bytesGranted: number) => void, + errorCallback?: (error: Error) => void, + ): void; + }; + +} + /** * Emulates the Cordova File plugin in browser. * Most of the code is extracted from the File class of Ionic Native. @@ -285,39 +372,40 @@ export class FileMock extends File { */ async getFreeDiskSpace(): Promise { // Request a file system instance with a minimum size until we get an error. - if (window.requestFileSystem) { - let iterations = 0; - let maxIterations = 50; - const calculateByRequest = (size: number, ratio: number): Promise => - new Promise((resolve): void => { - window.requestFileSystem(LocalFileSystem.PERSISTENT, size, () => { - iterations++; - if (iterations > maxIterations) { - resolve(size); + const window = this.getEmulatorWindow(); - return; - } - // eslint-disable-next-line promise/catch-or-return - calculateByRequest(size * ratio, ratio).then(resolve); - }, () => { - resolve(size / ratio); - }); - }); - - // General calculation, base 1MB and increasing factor 1.3. - let size = await calculateByRequest(1048576, 1.3); - - // More accurate. Factor is 1.1. - iterations = 0; - maxIterations = 10; - - size = await calculateByRequest(size, 1.1); - - return size / 1024; // Return size in KB. - - } else { + if (!window.requestFileSystem) { throw new Error('File system not available.'); } + + let iterations = 0; + let maxIterations = 50; + const calculateByRequest = (size: number, ratio: number): Promise => + new Promise((resolve): void => { + window.requestFileSystem(LocalFileSystem.PERSISTENT, size, () => { + iterations++; + if (iterations > maxIterations) { + resolve(size); + + return; + } + // eslint-disable-next-line promise/catch-or-return + calculateByRequest(size * ratio, ratio).then(resolve); + }, () => { + resolve(size / ratio); + }); + }); + + // General calculation, base 1MB and increasing factor 1.3. + let size = await calculateByRequest(1048576, 1.3); + + // More accurate. Factor is 1.1. + iterations = 0; + maxIterations = 10; + + size = await calculateByRequest(size, 1.1); + + return size / 1024; // Return size in KB. } /** @@ -342,24 +430,23 @@ export class FileMock extends File { */ load(): Promise { return new Promise((resolve, reject): void => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const win = window; // Convert to to be able to use non-standard properties. + const window = this.getEmulatorWindow(); - if (win.requestFileSystem === undefined) { - win.requestFileSystem = win.webkitRequestFileSystem; + if (window.requestFileSystem === undefined) { + window.requestFileSystem = window.webkitRequestFileSystem; } - if (win.resolveLocalFileSystemURL === undefined) { - win.resolveLocalFileSystemURL = win.webkitResolveLocalFileSystemURL; + if (window.resolveLocalFileSystemURL === undefined) { + window.resolveLocalFileSystemURL = window.webkitResolveLocalFileSystemURL; } - win.LocalFileSystem = { + window.LocalFileSystem = { + TEMPORARY: 0, // eslint-disable-line @typescript-eslint/naming-convention PERSISTENT: 1, // eslint-disable-line @typescript-eslint/naming-convention }; // Request a quota to use. Request 500MB. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ( navigator).webkitPersistentStorage.requestQuota(500 * 1024 * 1024, (granted) => { - window.requestFileSystem(LocalFileSystem.PERSISTENT, granted, (entry) => { - resolve(entry.root.toURL()); + this.getEmulatorNavigator().webkitPersistentStorage.requestQuota(500 * 1024 * 1024, (granted) => { + window.requestFileSystem(LocalFileSystem.PERSISTENT, granted, (fileSystem: FileSystem) => { + resolve(fileSystem.root.toURL()); }, reject); }, reject); }); @@ -642,7 +729,7 @@ export class FileMock extends File { resolveLocalFilesystemUrl(fileUrl: string): Promise { return new Promise((resolve, reject): void => { try { - window.resolveLocalFileSystemURL(fileUrl, (entry: Entry) => { + this.getEmulatorWindow().resolveLocalFileSystemURL(fileUrl, (entry: Entry) => { resolve(entry); }, (error: FileError) => { this.fillErrorMessageMock(error); @@ -799,4 +886,22 @@ export class FileMock extends File { }); } + /** + * Get emulator window. + * + * @returns Emulator window. + */ + private getEmulatorWindow(): WebkitWindow { + return window as unknown as WebkitWindow; + } + + /** + * Get emulator navigator. + * + * @returns Emulator navigator. + */ + private getEmulatorNavigator(): WebkitNavigator { + return navigator as unknown as WebkitNavigator; + } + } diff --git a/src/core/features/emulator/services/zip.ts b/src/core/features/emulator/services/zip.ts index eb2c869c6..3f4ea7e6e 100644 --- a/src/core/features/emulator/services/zip.ts +++ b/src/core/features/emulator/services/zip.ts @@ -50,10 +50,11 @@ export class ZipMock extends Zip { * * @param source Path to the source ZIP file. * @param destination Destination folder. - * @param onProgress Optional callback to be called on progress update + * @param onProgressFunction Optional callback to be called on progress update * @returns Promise that resolves with a number. 0 is success, -1 is error. */ - async unzip(source: string, destination: string, onProgress?: (ev: {loaded: number; total: number}) => void): Promise { + async unzip(source: string, destination: string, onProgressFunction?: Function): Promise { + const onProgress = onProgressFunction as (ev: {loaded: number; total: number}) => void; // Replace all %20 with spaces. source = source.replace(/%20/g, ' '); diff --git a/src/core/features/login/services/login-helper.ts b/src/core/features/login/services/login-helper.ts index 6918b9fdb..aed48d583 100644 --- a/src/core/features/login/services/login-helper.ts +++ b/src/core/features/login/services/login-helper.ts @@ -56,6 +56,7 @@ import { IDENTITY_PROVIDERS_FEATURE_NAME, IDENTITY_PROVIDER_FEATURE_NAME_PREFIX, } from '../constants'; +import { LazyRoutesModule } from '@/app/app-routing.module'; /** * Helper provider that provides some common features regarding authentication. @@ -1440,7 +1441,7 @@ export class CoreLoginHelperProvider { * * @returns Reconnect page route module. */ - async getReconnectRouteModule(): Promise { + async getReconnectRouteModule(): Promise { return import('@features/login/login-reconnect-lazy.module').then(m => m.CoreLoginReconnectLazyModule); } @@ -1449,7 +1450,7 @@ export class CoreLoginHelperProvider { * * @returns Credentials page route module. */ - async getCredentialsRouteModule(): Promise { + async getCredentialsRouteModule(): Promise { return import('@features/login/login-credentials-lazy.module').then(m => m.CoreLoginCredentialsLazyModule); } diff --git a/src/core/features/mainmenu/components/user-menu/user-menu.ts b/src/core/features/mainmenu/components/user-menu/user-menu.ts index 4dfefbc7c..77dcd42dc 100644 --- a/src/core/features/mainmenu/components/user-menu/user-menu.ts +++ b/src/core/features/mainmenu/components/user-menu/user-menu.ts @@ -246,7 +246,7 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy { cssClass: 'core-modal-lateral core-modal-lateral-sm', }); - if (closeAll) { + if (thisModal && closeAll) { await ModalController.dismiss(undefined, undefined, thisModal.id); } } diff --git a/src/core/features/siteplugins/components/plugin-content/plugin-content.ts b/src/core/features/siteplugins/components/plugin-content/plugin-content.ts index a4244dc25..a2a2ad5a3 100644 --- a/src/core/features/siteplugins/components/plugin-content/plugin-content.ts +++ b/src/core/features/siteplugins/components/plugin-content/plugin-content.ts @@ -235,7 +235,8 @@ export class CoreSitePluginsPluginContentComponent implements OnInit, DoCheck { this.args = args; this.dataLoaded = false; this.preSets = preSets || this.preSets; - if (jsData) { + + if (this.data && jsData) { Object.assign(this.data, jsData); } diff --git a/src/core/initializers/inject-ios-scripts.ts b/src/core/initializers/inject-ios-scripts.ts index 36affed3f..92cc8d83d 100644 --- a/src/core/initializers/inject-ios-scripts.ts +++ b/src/core/initializers/inject-ios-scripts.ts @@ -14,6 +14,17 @@ import { CorePlatform } from '@services/platform'; import { CoreIframeUtils } from '@services/utils/iframe'; +import { WKUserScriptWindow } from 'cordova-plugin-wkuserscript'; + +/** + * Check Whether the window object has WKUserScript set. + * + * @param window Window object. + * @returns Whether the window object has WKUserScript set. + */ +function isWKUserScriptWindow(window: object): window is WKUserScriptWindow { + return CorePlatform.isIOS() && 'WKUserScript' in window; +} /** * Inject some scripts for iOS iframes. @@ -21,7 +32,7 @@ import { CoreIframeUtils } from '@services/utils/iframe'; export default async function(): Promise { await CorePlatform.ready(); - if (!CorePlatform.isIOS() || !('WKUserScript' in window)) { + if (!isWKUserScriptWindow(window)) { return; } diff --git a/tsconfig.json b/tsconfig.json index 7b7f668e0..d0bde2143 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "outDir": "./dist/out-tsc", "sourceMap": true, "declaration": false, + "skipLibCheck": true, "downlevelIteration": true, "experimentalDecorators": true, "strictNullChecks": true, @@ -14,7 +15,7 @@ "module": "esnext", "moduleResolution": "node", "importHelpers": true, - "target": "es2015", + "target": "es2022", "resolveJsonModule": true, "allowSyntheticDefaultImports": true, "lib": [ diff --git a/tsconfig.spec.json b/tsconfig.spec.json index cde30bd22..b4c9aede2 100644 --- a/tsconfig.spec.json +++ b/tsconfig.spec.json @@ -2,6 +2,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/tests", + "target": "es2016", "allowJs": true, "esModuleInterop": true, "emitDecoratorMetadata": true, From fe9aef76f507ad21168a3259349d6cf0422357dc Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Wed, 29 Nov 2023 12:18:48 +0100 Subject: [PATCH 3/6] MOBILE-3947 vscode: Remove deprecated setting --- .vscode/settings.json | 1 - 1 file changed, 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 8c748420b..f37da53d7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,7 +9,6 @@ }, "editor.formatOnSave": true, "eslint.format.enable": true, - "html.format.endWithNewline": true, "html.format.wrapLineLength": 140, "files.eol": "\n", "files.trimFinalNewlines": true, From 16955c1e56b33ba368d2da3e585cee63bd7bf178 Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Wed, 29 Nov 2023 12:28:42 +0100 Subject: [PATCH 4/6] MOBILE-3947 vscode: Force typescript sdk path --- .vscode/settings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index f37da53d7..d0ad3fae6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,6 +14,7 @@ "files.trimFinalNewlines": true, "files.insertFinalNewline": true, "files.trimTrailingWhitespace": true, + "typescript.tsdk": "./node_modules/typescript/lib", /** * Config files. From 9a5329cdc35ad2614e877e2af75625e29530ae46 Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Wed, 29 Nov 2023 12:44:18 +0100 Subject: [PATCH 5/6] MOBILE-3947 core: Fix translate pipe linting --- src/core/features/compile/pipes/translate.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/features/compile/pipes/translate.ts b/src/core/features/compile/pipes/translate.ts index 3cf15adbd..86e1ac728 100644 --- a/src/core/features/compile/pipes/translate.ts +++ b/src/core/features/compile/pipes/translate.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Injectable, Pipe } from '@angular/core'; +import { Injectable, Pipe, PipeTransform } from '@angular/core'; import { TranslatePipe } from '@ngx-translate/core'; /** @@ -25,4 +25,4 @@ import { TranslatePipe } from '@ngx-translate/core'; pure: false, // required to update the value when the promise is resolved standalone: true, }) -export class TranslatePipeForCompile extends TranslatePipe {} +export class TranslatePipeForCompile extends TranslatePipe implements PipeTransform {} From 7325a1dd6d1e0b8d11c16709b6287040cab61e2a Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Wed, 29 Nov 2023 16:50:43 +0100 Subject: [PATCH 6/6] MOBILE-3947 native: Fix zip plugin --- package-lock.json | 13 ------- package.json | 1 - src/core/features/emulator/emulator.module.ts | 2 +- src/core/features/emulator/services/zip.ts | 8 ++--- src/core/features/native/native.module.ts | 3 +- src/core/features/native/plugins/index.ts | 2 ++ src/core/features/native/plugins/zip.ts | 35 +++++++++++++++++++ src/core/singletons/index.ts | 2 -- src/types/cordova-plugin-zip.d.ts | 27 ++++++++++++++ 9 files changed, 69 insertions(+), 24 deletions(-) create mode 100644 src/core/features/native/plugins/zip.ts create mode 100644 src/types/cordova-plugin-zip.d.ts diff --git a/package-lock.json b/package-lock.json index 8cff3413b..71595c964 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,6 @@ "@awesome-cordova-plugins/sqlite": "^6.3.0", "@awesome-cordova-plugins/status-bar": "^6.3.0", "@awesome-cordova-plugins/web-intent": "^6.3.0", - "@awesome-cordova-plugins/zip": "^6.3.0", "@ionic/angular": "^7.0.0", "@ionic/cordova-builders": "^10.0.0", "@moodlehq/cordova-plugin-advanced-http": "3.3.1-moodle.1", @@ -1034,18 +1033,6 @@ "rxjs": "^5.5.0 || ^6.5.0 || ^7.3.0" } }, - "node_modules/@awesome-cordova-plugins/zip": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/@awesome-cordova-plugins/zip/-/zip-6.4.0.tgz", - "integrity": "sha512-s6Bg+sBepwhVvN+8fdns8QOOY5Mo5pg9Iy1aJiFDBUipuQOLaO++zw5u+no0igObnYcaQAdSO2XyEBX/h0T59g==", - "dependencies": { - "@types/cordova": "latest" - }, - "peerDependencies": { - "@awesome-cordova-plugins/core": "^6.0.1", - "rxjs": "^5.5.0 || ^6.5.0 || ^7.3.0" - } - }, "node_modules/@babel/code-frame": { "version": "7.23.4", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.4.tgz", diff --git a/package.json b/package.json index f385530cd..cdf914c8b 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,6 @@ "@awesome-cordova-plugins/sqlite": "^6.3.0", "@awesome-cordova-plugins/status-bar": "^6.3.0", "@awesome-cordova-plugins/web-intent": "^6.3.0", - "@awesome-cordova-plugins/zip": "^6.3.0", "@ionic/angular": "^7.0.0", "@ionic/cordova-builders": "^10.0.0", "@moodlehq/cordova-plugin-advanced-http": "3.3.1-moodle.1", diff --git a/src/core/features/emulator/emulator.module.ts b/src/core/features/emulator/emulator.module.ts index a5436fa31..9ebb477c8 100644 --- a/src/core/features/emulator/emulator.module.ts +++ b/src/core/features/emulator/emulator.module.ts @@ -27,7 +27,7 @@ import { Geolocation } from '@awesome-cordova-plugins/geolocation/ngx'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; import { LocalNotifications } from '@awesome-cordova-plugins/local-notifications/ngx'; import { MediaCapture } from '@awesome-cordova-plugins/media-capture/ngx'; -import { Zip } from '@awesome-cordova-plugins/zip/ngx'; +import { Zip } from '@features/native/plugins/zip'; // Mock services. import { CameraMock } from './services/camera'; diff --git a/src/core/features/emulator/services/zip.ts b/src/core/features/emulator/services/zip.ts index 3f4ea7e6e..56654bd99 100644 --- a/src/core/features/emulator/services/zip.ts +++ b/src/core/features/emulator/services/zip.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { Zip } from '@awesome-cordova-plugins/zip/ngx'; +import { Zip } from '@features/native/plugins/zip'; import * as JSZip from 'jszip'; import { CorePath } from '@singletons/path'; import { File } from '@singletons'; @@ -50,12 +50,10 @@ export class ZipMock extends Zip { * * @param source Path to the source ZIP file. * @param destination Destination folder. - * @param onProgressFunction Optional callback to be called on progress update + * @param onProgress Optional callback to be called on progress update * @returns Promise that resolves with a number. 0 is success, -1 is error. */ - async unzip(source: string, destination: string, onProgressFunction?: Function): Promise { - const onProgress = onProgressFunction as (ev: {loaded: number; total: number}) => void; - + async unzip(source: string, destination: string, onProgress?: (ev: {loaded: number; total: number}) => void): Promise { // Replace all %20 with spaces. source = source.replace(/%20/g, ' '); destination = destination.replace(/%20/g, ' '); diff --git a/src/core/features/native/native.module.ts b/src/core/features/native/native.module.ts index b1bd71648..18449e534 100644 --- a/src/core/features/native/native.module.ts +++ b/src/core/features/native/native.module.ts @@ -36,7 +36,7 @@ import { SplashScreen } from '@awesome-cordova-plugins/splash-screen/ngx'; import { SQLite } from '@awesome-cordova-plugins/sqlite/ngx'; import { StatusBar } from '@awesome-cordova-plugins/status-bar/ngx'; import { WebIntent } from '@awesome-cordova-plugins/web-intent/ngx'; -import { Zip } from '@awesome-cordova-plugins/zip/ngx'; +import { Zip } from '@features/native/plugins/zip'; export const CORE_NATIVE_SERVICES = [ Badge, @@ -87,7 +87,6 @@ export const CORE_NATIVE_SERVICES = [ StatusBar, WebIntent, WebView, - Zip, ], }) export class CoreNativeModule {} diff --git a/src/core/features/native/plugins/index.ts b/src/core/features/native/plugins/index.ts index 8efe862c2..ee50e1680 100644 --- a/src/core/features/native/plugins/index.ts +++ b/src/core/features/native/plugins/index.ts @@ -14,5 +14,7 @@ import { makeSingleton } from '@singletons'; import { Chooser as ChooserService } from './chooser'; +import { Zip as ZipService } from './zip'; export const Chooser = makeSingleton(ChooserService); +export const Zip = makeSingleton(ZipService); diff --git a/src/core/features/native/plugins/zip.ts b/src/core/features/native/plugins/zip.ts new file mode 100644 index 000000000..aeda6ee60 --- /dev/null +++ b/src/core/features/native/plugins/zip.ts @@ -0,0 +1,35 @@ +// (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'; + +/** + * Zip plugin wrapper. + */ +@Injectable({ providedIn: 'root' }) +export class Zip { + + /** + * Extracts files from a ZIP archive + * + * @param source Source ZIP file + * @param destination Destination folder + * @param onProgress Callback to be called on progress update + * @returns 0 is success, -1 is error + */ + unzip(source: string, destination: string, onProgress?: (ev: {loaded: number; total: number}) => void): Promise { + return new Promise(resolve => window.zip.unzip(source, destination, (result: number) => resolve(result), onProgress)); + } + +} diff --git a/src/core/singletons/index.ts b/src/core/singletons/index.ts index ece235b95..8059bd553 100644 --- a/src/core/singletons/index.ts +++ b/src/core/singletons/index.ts @@ -58,7 +58,6 @@ import { StatusBar as StatusBarService } from '@awesome-cordova-plugins/status-b import { SplashScreen as SplashScreenService } from '@awesome-cordova-plugins/splash-screen/ngx'; import { SQLite as SQLiteService } from '@awesome-cordova-plugins/sqlite/ngx'; import { WebIntent as WebIntentService } from '@awesome-cordova-plugins/web-intent/ngx'; -import { Zip as ZipService } from '@awesome-cordova-plugins/zip/ngx'; import { TranslateService } from '@ngx-translate/core'; @@ -192,7 +191,6 @@ export const SplashScreen = makeSingleton(SplashScreenService); export const SQLite = makeSingleton(SQLiteService); export const WebIntent = makeSingleton(WebIntentService); export const WebView = makeSingleton(WebViewService); -export const Zip = makeSingleton(ZipService); export const Camera = makeSingleton(CameraService); diff --git a/src/types/cordova-plugin-zip.d.ts b/src/types/cordova-plugin-zip.d.ts new file mode 100644 index 000000000..7d7bda00c --- /dev/null +++ b/src/types/cordova-plugin-zip.d.ts @@ -0,0 +1,27 @@ +// (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. + +/** + * Types for file cordova plugin. + * + * @see https://github.com/moodlemobile/cordova-plugin-zip + */ + +interface Window { + + zip: { + unzip(source: string, destination: string, onSuccess: Function, onProgress?: Function): void; + }; + +}