diff --git a/.eslintrc.js b/.eslintrc.js index 260d9cef712..6007c251e5a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -47,6 +47,7 @@ const appConfig = { Object: { message: 'Use {} instead.', }, + Function: false, }, }, ], diff --git a/.vscode/settings.json b/.vscode/settings.json index 8c748420ba5..d0ad3fae64d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,12 +9,12 @@ }, "editor.formatOnSave": true, "eslint.format.enable": true, - "html.format.endWithNewline": true, "html.format.wrapLineLength": 140, "files.eol": "\n", "files.trimFinalNewlines": true, "files.insertFinalNewline": true, "files.trimTrailingWhitespace": true, + "typescript.tsdk": "./node_modules/typescript/lib", /** * Config files. diff --git a/package-lock.json b/package-lock.json index 1696a5d5641..71595c9648c 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", @@ -142,7 +141,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", @@ -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", @@ -6058,21 +6045,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 +8673,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 +13713,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 +16009,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 +20710,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 +21203,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 +23162,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 0c7095fb8c1..cdf914c8b34 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", @@ -177,7 +176,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 eeb58ea30e4..0405044dc31 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; + + questionPage.isQuestion = true; - if (page.answerdata?.answers) { - page.answerdata.answers.forEach((answer) => { + 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 18b3276c5cb..b12f4efc24c 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 8fe7759d551..3fe5fab5ee0 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 3db6a2106db..b66bd26b105 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 8cfe10e8660..c5c756de6d1 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/compile/pipes/translate.ts b/src/core/features/compile/pipes/translate.ts index 3cf15adbdd4..86e1ac72850 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 {} diff --git a/src/core/features/course/services/course-helper.ts b/src/core/features/course/services/course-helper.ts index b6de863d447..cbc0fd3db5b 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 54eb1ebfa09..0c19e83ea02 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/emulator.module.ts b/src/core/features/emulator/emulator.module.ts index a5436fa313c..9ebb477c82e 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/file.ts b/src/core/features/emulator/services/file.ts index a9a61a124b4..da8dfd3fb7b 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); - - return; - } - // eslint-disable-next-line promise/catch-or-return - calculateByRequest(size * ratio, ratio).then(resolve); - }, () => { - resolve(size / ratio); - }); - }); + const window = this.getEmulatorWindow(); - // General calculation, base 1MB and increasing factor 1.3. - let size = await calculateByRequest(1048576, 1.3); + if (!window.requestFileSystem) { + throw new Error('File system not available.'); + } - // More accurate. Factor is 1.1. - iterations = 0; - maxIterations = 10; + 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); + }); + }); - size = await calculateByRequest(size, 1.1); + // General calculation, base 1MB and increasing factor 1.3. + let size = await calculateByRequest(1048576, 1.3); - return size / 1024; // Return size in KB. + // More accurate. Factor is 1.1. + iterations = 0; + maxIterations = 10; - } else { - throw new Error('File system not available.'); - } + 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 eb2c869c681..56654bd9996 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'; @@ -54,7 +54,6 @@ export class ZipMock extends Zip { * @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 { - // Replace all %20 with spaces. source = source.replace(/%20/g, ' '); destination = destination.replace(/%20/g, ' '); diff --git a/src/core/features/login/services/login-helper.ts b/src/core/features/login/services/login-helper.ts index 6918b9fdbdc..aed48d58310 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 4dfefbc7c66..77dcd42dce0 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/native/native.module.ts b/src/core/features/native/native.module.ts index b1bd71648d4..18449e53440 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 8efe862c2bd..ee50e168069 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 00000000000..aeda6ee6001 --- /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/features/siteplugins/components/plugin-content/plugin-content.ts b/src/core/features/siteplugins/components/plugin-content/plugin-content.ts index a4244dc258b..a2a2ad5a385 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/features/siteplugins/services/siteplugins-helper.ts b/src/core/features/siteplugins/services/siteplugins-helper.ts index 0307eb25aae..ab0aa6b2ea0 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/initializers/inject-ios-scripts.ts b/src/core/initializers/inject-ios-scripts.ts index 36affed3fac..92cc8d83ddd 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/src/core/services/file.ts b/src/core/services/file.ts index 0fccddc0439..4a5e6659b21 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) { diff --git a/src/core/singletons/index.ts b/src/core/singletons/index.ts index ece235b9545..8059bd5534b 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 00000000000..7d7bda00ced --- /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; + }; + +} diff --git a/tsconfig.json b/tsconfig.json index 7b7f668e0f3..d0bde214372 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 cde30bd22da..b4c9aede2ab 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,