diff --git a/package-lock.json b/package-lock.json index be9c20de299..41ee2d71951 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,6 @@ "@awesome-cordova-plugins/diagnostic": "^6.6.0", "@awesome-cordova-plugins/file": "^6.6.0", "@awesome-cordova-plugins/file-opener": "^6.6.0", - "@awesome-cordova-plugins/file-transfer": "^6.6.0", "@awesome-cordova-plugins/geolocation": "^6.6.0", "@awesome-cordova-plugins/http": "^6.6.0", "@awesome-cordova-plugins/in-app-browser": "^6.6.0", @@ -45,6 +44,7 @@ "@moodlehq/cordova-plugin-advanced-http": "3.3.1-moodle.1", "@moodlehq/cordova-plugin-file-opener": "4.0.0-moodle.1", "@moodlehq/cordova-plugin-file-transfer": "2.0.0-moodle.2", + "@moodlehq/cordova-plugin-camera": "6.0.0-moodle.2", "@moodlehq/cordova-plugin-inappbrowser": "5.0.0-moodle.3", "@moodlehq/cordova-plugin-intent": "2.2.0-moodle.3", "@moodlehq/cordova-plugin-ionic-webview": "5.0.0-moodle.3", @@ -153,6 +153,12 @@ "node": ">=18.18.2 <19" } }, + "@moodlehq/cordova-plugin-file-transfer": { + "version": "1.7.1-moodle.7", + "resolved": "https://registry.npmjs.org/@moodlehq/cordova-plugin-file-transfer/-/cordova-plugin-file-transfer-1.7.1-moodle.7.tgz", + "integrity": "sha512-h4PALmtuSAExfy5DBHvB91P+Z+Dc6DLpWaF7GZeYUM1ROa8OCfw5hc1vq8xFMc+tgan8DSVKUYD2CJX6zlhpMg==", + "extraneous": true + }, "cordova-plugin-moodleapp": { "version": "0.0.0", "dev": true, @@ -2610,22 +2616,6 @@ "resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-11.0.3.tgz", "integrity": "sha512-kyuRQ40/NWQVhqGIHq78Ehu2Bf9Mlg0LhmSmis6ZFJK7z933FRfYi8tHe/k/0fB+PGfCf95rJC6TO7dopaFvAg==" }, - "node_modules/@awesome-cordova-plugins/file-transfer": { - "version": "6.6.0", - "license": "MIT", - "dependencies": { - "@types/cordova": "latest" - }, - "peerDependencies": { - "@awesome-cordova-plugins/core": "^6.0.1", - "rxjs": "^5.5.0 || ^6.5.0 || ^7.3.0" - } - }, - "node_modules/@awesome-cordova-plugins/file-transfer/node_modules/@types/cordova": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-11.0.3.tgz", - "integrity": "sha512-kyuRQ40/NWQVhqGIHq78Ehu2Bf9Mlg0LhmSmis6ZFJK7z933FRfYi8tHe/k/0fB+PGfCf95rJC6TO7dopaFvAg==" - }, "node_modules/@awesome-cordova-plugins/file/node_modules/@types/cordova": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-11.0.3.tgz", diff --git a/package.json b/package.json index e9b1465dd9f..440f6af0424 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,6 @@ "@awesome-cordova-plugins/diagnostic": "^6.6.0", "@awesome-cordova-plugins/file": "^6.6.0", "@awesome-cordova-plugins/file-opener": "^6.6.0", - "@awesome-cordova-plugins/file-transfer": "^6.6.0", "@awesome-cordova-plugins/geolocation": "^6.6.0", "@awesome-cordova-plugins/http": "^6.6.0", "@awesome-cordova-plugins/in-app-browser": "^6.6.0", diff --git a/src/core/features/emulator/emulator.module.ts b/src/core/features/emulator/emulator.module.ts index 9ebb477c82e..b40e57b731b 100644 --- a/src/core/features/emulator/emulator.module.ts +++ b/src/core/features/emulator/emulator.module.ts @@ -22,7 +22,6 @@ import { Camera } from '@awesome-cordova-plugins/camera/ngx'; import { Clipboard } from '@awesome-cordova-plugins/clipboard/ngx'; import { File } from '@awesome-cordova-plugins/file/ngx'; import { FileOpener } from '@awesome-cordova-plugins/file-opener/ngx'; -import { FileTransfer } from '@awesome-cordova-plugins/file-transfer/ngx'; 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'; @@ -34,7 +33,6 @@ import { CameraMock } from './services/camera'; import { ClipboardMock } from './services/clipboard'; import { FileMock } from './services/file'; import { FileOpenerMock } from './services/file-opener'; -import { FileTransferMock } from './services/file-transfer'; import { GeolocationMock } from './services/geolocation'; import { InAppBrowserMock } from './services/inappbrowser'; import { LocalNotificationsMock } from './services/local-notifications'; @@ -75,10 +73,6 @@ import { SecureStorageMock } from '@features/emulator/classes/SecureStorage'; provide: FileOpener, useFactory: (): FileOpener => CorePlatform.is('cordova') ? new FileOpener() : new FileOpenerMock(), }, - { - provide: FileTransfer, - useFactory: (): FileTransfer => CorePlatform.is('cordova') ? new FileTransfer() : new FileTransferMock(), - }, { provide: Geolocation, useFactory: (): Geolocation => CorePlatform.is('cordova') ? new Geolocation() : new GeolocationMock(), diff --git a/src/core/features/emulator/services/emulator-helper.ts b/src/core/features/emulator/services/emulator-helper.ts index 42a8f5d9498..1682d964c72 100644 --- a/src/core/features/emulator/services/emulator-helper.ts +++ b/src/core/features/emulator/services/emulator-helper.ts @@ -18,7 +18,7 @@ import { CoreFile } from '@services/file'; import { File, makeSingleton } from '@singletons'; import { CoreLogger } from '@singletons/logger'; import { FileMock } from './file'; -import { FileTransferErrorMock } from './file-transfer'; +import { FileTransferErrorMock, FileTransferMock } from './file-transfer'; /** * Helper service for the emulator feature. It also acts as an init handler. @@ -39,6 +39,7 @@ export class CoreEmulatorHelperProvider { */ async load(): Promise { window.FileTransferError = FileTransferErrorMock; + window.FileTransfer = FileTransferMock; const fileService = File.instance; diff --git a/src/core/features/emulator/services/file-transfer.ts b/src/core/features/emulator/services/file-transfer.ts index 7b758bb4d4a..53a08957a78 100644 --- a/src/core/features/emulator/services/file-transfer.ts +++ b/src/core/features/emulator/services/file-transfer.ts @@ -13,9 +13,6 @@ // limitations under the License. import { CoreTextUtils } from '@services/utils/text'; -import { Injectable } from '@angular/core'; -import { FileTransfer, FileTransferObject, FileUploadResult, FileTransferError } from '@awesome-cordova-plugins/file-transfer/ngx'; - import { CoreFile } from '@services/file'; /** @@ -43,40 +40,25 @@ export class FileTransferErrorMock implements FileTransferError { /** * Emulates the Cordova FileTransfer plugin in desktop apps and in browser. */ -@Injectable() -export class FileTransferMock extends FileTransfer { - - /** - * Creates a new FileTransferObjectMock object. - * - * @returns a new file transfer mock. - */ - create(): FileTransferObjectMock { - return new FileTransferObjectMock(); - } - -} - -/** - * Emulates the FileTransferObject class in desktop apps and in browser. - */ -export class FileTransferObjectMock extends FileTransferObject { +export class FileTransferMock { - progressListener?: (event: ProgressEvent) => void; source?: string; target?: string; xhr?: XMLHttpRequest; - protected reject?: (reason?: unknown) => void; + // eslint-disable-next-line @typescript-eslint/no-empty-function + onprogress: (event: ProgressEvent) => void = () => {}; + + errorCallback?: (error: FileTransferError) => void; /** * Aborts an in-progress transfer. The onerror callback is passed a FileTransferError * object which has an error code of FileTransferError.ABORT_ERR. */ - abort(): void { + abort(): void { if (this.xhr) { this.xhr.abort(); - this.reject?.( + this.errorCallback?.( new FileTransferErrorMock(FileTransferErrorMock.ABORT_ERR, this.source || '', this.target || '', 0, '', ''), ); } @@ -87,13 +69,20 @@ export class FileTransferObjectMock extends FileTransferObject { * * @param source URL of the server to download the file, as encoded by encodeURI(). * @param target Filesystem url representing the file on the device. + * @param successCallback Callback to execute if download the file sucessfully. + * @param errorCallback Callback to execute if an error happened downloading the file. * @param trustAllHosts If set to true, it accepts all security certificates. * @param options Optional parameters, currently only supports headers. - * @returns Returns a Promise that resolves to a FileEntry object. */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - download(source: string, target: string, trustAllHosts?: boolean, options?: { [s: string]: any }): Promise { - return new Promise((resolve, reject): void => { + download( + source: string, + target: string, + successCallback: (params: CoreFileTransferDownloadResponse) => void, + errorCallback: (error: FileTransferError) => void, + trustAllHosts?: boolean, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + options?: Record, + ): void { // Use XMLHttpRequest instead of HttpClient to support onprogress and abort. const basicAuthHeader = this.getBasicAuthHeader(source); const xhr = new XMLHttpRequest(); @@ -101,7 +90,7 @@ export class FileTransferObjectMock extends FileTransferObject { this.xhr = xhr; this.source = source; this.target = target; - this.reject = reject; + this.errorCallback = errorCallback; if (basicAuthHeader) { source = source.replace(this.getUrlCredentials(source) + '@', ''); @@ -124,13 +113,12 @@ export class FileTransferObjectMock extends FileTransferObject { } xhr.onprogress = (ev: ProgressEvent): void => { - if (this.progressListener) { - this.progressListener(ev); - } + this.onprogress(ev); }; xhr.onerror = (): void => { - reject(new FileTransferErrorMock(-1, source, target, xhr.status, xhr.statusText, '')); + const error = new FileTransferErrorMock(-1, source, target, xhr.status, xhr.statusText, ''); + errorCallback(error); }; xhr.onload = async (): Promise => { @@ -141,28 +129,27 @@ export class FileTransferObjectMock extends FileTransferObject { if (status < 200 || status >= 300) { // Request failed. Try to get the error message. response = await this.parseResponse(response); + const error = new FileTransferErrorMock(-1, source, target, xhr.status, response || xhr.statusText, ''); - reject(new FileTransferErrorMock(-1, source, target, xhr.status, response || xhr.statusText, '')); - - return; + return errorCallback(error); } if (!response) { - reject(); + const error = new FileTransferErrorMock(-1, source, target, xhr.status, xhr.statusText, 'No response obtained'); - return; + return errorCallback(error); } const basePath = CoreFile.getBasePathInstant(); target = target.replace(basePath, ''); // Remove basePath from the target. target = target.replace(/%20/g, ' '); // Replace all %20 with spaces. - - // eslint-disable-next-line promise/catch-or-return - CoreFile.writeFile(target, response).then(resolve, reject); + CoreFile.writeFile(target, response) + .then(entry => + successCallback({ entry: entry as unknown as globalThis.FileEntry, headers: response.headers })) + .catch(error => errorCallback(error)); }; xhr.send(); - }); } /** @@ -227,15 +214,6 @@ export class FileTransferObjectMock extends FileTransferObject { return credentials && credentials[1]; } - /** - * Registers a listener that gets called whenever a new chunk of data is transferred. - * - * @param listener Listener that takes a progress event. - */ - onProgress(listener: (event: ProgressEvent) => void): void { - this.progressListener = listener; - } - /** * Parse a response, converting it into text and the into an object if needed. * @@ -285,11 +263,17 @@ export class FileTransferObjectMock extends FileTransferObject { * * @param fileUrl Filesystem URL representing the file on the device or a data URI. * @param url URL of the server to receive the file, as encoded by encodeURI(). + * @param successCallback Callback to execute if upload the file sucessfully. + * @param errorCallback Callback to execute if an error happened uploading the file. * @param options Optional parameters. - * @returns Promise that resolves to a FileUploadResult and rejects with FileTransferError. */ - upload(fileUrl: string, url: string, options?: FileUploadOptions): Promise { - return new Promise((resolve, reject): void => { + upload( + fileUrl: string, + url: string, + successCallback: (result: FileUploadResult) => void, + errorCallback: (error: FileTransferError) => void, + options?: FileUploadOptions, + ): void { const basicAuthHeader = this.getBasicAuthHeader(url); let fileKey: string | undefined; let fileName: string | undefined; @@ -330,7 +314,6 @@ export class FileTransferObjectMock extends FileTransferObject { // Adding a Content-Type header with the mimeType makes the request fail (it doesn't detect the token in the params). // Don't include this header, and delete it if it's supplied. delete headers['Content-Type']; - // Get the file to upload. CoreFile.getFile(fileUrl).then((fileEntry) => CoreFile.getFileObjectFromFileEntry(fileEntry)).then((file) => { @@ -345,28 +328,28 @@ export class FileTransferObjectMock extends FileTransferObject { } xhr.onprogress = (ev: ProgressEvent): void => { - if (this.progressListener) { - this.progressListener(ev); - } + this.onprogress(ev); }; - this.xhr = xhr; this.source = fileUrl; this.target = url; - this.reject = reject; + this.errorCallback = errorCallback; xhr.onerror = (): void => { - reject(new FileTransferErrorMock(-1, fileUrl, url, xhr.status, xhr.statusText, '')); + const error = new FileTransferErrorMock(-1, fileUrl, url, xhr.status, xhr.statusText, ''); + errorCallback(error); }; xhr.onload = (): void => { - // Finished uploading the file. - resolve({ + const result = { bytesSent: file.size, responseCode: xhr.status, response: xhr.response, headers: this.getHeadersAsObject(xhr), - }); + }; + + // Finished uploading the file. + successCallback(result); }; // Create a form data to send params and the file. @@ -374,13 +357,14 @@ export class FileTransferObjectMock extends FileTransferObject { for (const name in params) { fd.append(name, params[name]); } - fd.append('file', file, fileName); + fd.append('file', file, fileName); xhr.send(fd); return; - }).catch(reject); - }); + }).catch(error => errorCallback(error)); } } + +export type CoreFileTransferDownloadResponse = { entry: globalThis.FileEntry; headers: Record | undefined }; diff --git a/src/core/features/native/native.module.ts b/src/core/features/native/native.module.ts index 218e3c96127..102e004f482 100644 --- a/src/core/features/native/native.module.ts +++ b/src/core/features/native/native.module.ts @@ -22,7 +22,6 @@ import { Device } from '@awesome-cordova-plugins/device/ngx'; import { Diagnostic } from '@awesome-cordova-plugins/diagnostic/ngx'; import { File } from '@awesome-cordova-plugins/file/ngx'; import { FileOpener } from '@awesome-cordova-plugins/file-opener/ngx'; -import { FileTransfer } from '@awesome-cordova-plugins/file-transfer/ngx'; import { Geolocation } from '@awesome-cordova-plugins/geolocation/ngx'; import { HTTP } from '@awesome-cordova-plugins/http/ngx'; import { InAppBrowser } from '@awesome-cordova-plugins/in-app-browser/ngx'; @@ -47,7 +46,6 @@ export const CORE_NATIVE_SERVICES = [ Diagnostic, File, FileOpener, - FileTransfer, Geolocation, HTTP, InAppBrowser, @@ -73,7 +71,6 @@ export const CORE_NATIVE_SERVICES = [ Diagnostic, File, FileOpener, - FileTransfer, Geolocation, HTTP, InAppBrowser, diff --git a/src/core/services/ws.ts b/src/core/services/ws.ts index 90a45244d9a..b1084347eac 100644 --- a/src/core/services/ws.ts +++ b/src/core/services/ws.ts @@ -16,7 +16,6 @@ import { Injectable } from '@angular/core'; import { HttpResponse, HttpParams, HttpErrorResponse } from '@angular/common/http'; import { FileEntry } from '@awesome-cordova-plugins/file/ngx'; -import { FileUploadOptions, FileUploadResult } from '@awesome-cordova-plugins/file-transfer/ngx'; import { Md5 } from 'ts-md5/dist/md5'; import { Observable, firstValueFrom } from 'rxjs'; import { timeout } from 'rxjs/operators'; @@ -29,7 +28,7 @@ import { CoreTextErrorObject, CoreTextUtils } from '@services/utils/text'; import { CoreConstants } from '@/core/constants'; import { CoreError } from '@classes/errors/error'; import { CoreInterceptor } from '@classes/interceptor'; -import { makeSingleton, Translate, FileTransfer, Http, NativeHttp } from '@singletons'; +import { makeSingleton, Translate, Http, NativeHttp } from '@singletons'; import { CoreLogger } from '@singletons/logger'; import { CoreWSError } from '@classes/errors/wserror'; import { CoreAjaxError } from '@classes/errors/ajaxerror'; @@ -254,14 +253,25 @@ export class CoreWSProvider { // Create the tmp file as an empty file. const fileEntry = await CoreFile.createFile(tmpPath); - const transfer = FileTransfer.create(); - onProgress && transfer.onProgress(onProgress); + const transfer = new window.FileTransfer(); + + if (onProgress) { + transfer.onprogress = onProgress; + } // Download the file in the tmp file. - await transfer.download(url, CoreFile.getFileEntryURL(fileEntry), true, { - headers: { - 'User-Agent': navigator.userAgent, - }, + const fileDownloaded = await new Promise<{ + entry: globalThis.FileEntry; + headers: Record | undefined; + }>((resolve, reject) => { + transfer.download( + url, + CoreFile.getFileEntryURL(fileEntry), + (result) => resolve(result), + (error: FileTransferError) => reject(error), + true, + { headers: { 'User-Agent': navigator.userAgent } }, + ); }); let extension = ''; @@ -271,8 +281,10 @@ export class CoreWSProvider { // Google Drive extensions will be considered invalid since Moodle usually converts them. if (!extension || ['gdoc', 'gsheet', 'gslides', 'gdraw', 'php'].includes(extension)) { + // Not valid, get the file's mimetype. - const mimetype = await this.getRemoteFileMimeType(url); + const requestContentType = fileDownloaded.headers?.['Content-Type']?.split(';')[0]; + const mimetype = requestContentType ?? await this.getRemoteFileMimeType(url); if (mimetype) { const remoteExtension = CoreMimetypeUtils.getExtension(mimetype, url); @@ -983,9 +995,11 @@ export class CoreWSProvider { } const uploadUrl = preSets.siteUrl + '/webservice/upload.php'; - const transfer = FileTransfer.create(); + const transfer = new window.FileTransfer(); - onProgress && transfer.onProgress(onProgress); + if (onProgress) { + transfer.onprogress = onProgress; + } options.httpMethod = 'POST'; options.params = { @@ -1002,7 +1016,8 @@ export class CoreWSProvider { let success: FileUploadResult; try { - success = await transfer.upload(filePath, uploadUrl, options, true); + success = await new Promise((resolve, reject) => + transfer.upload( filePath, uploadUrl, (result) => resolve(result), (error) => reject(error), options, true)); } catch (error) { this.logger.error('Error while uploading file', filePath, error); diff --git a/src/core/singletons/index.ts b/src/core/singletons/index.ts index 894929b381b..1bef7dd7db0 100644 --- a/src/core/singletons/index.ts +++ b/src/core/singletons/index.ts @@ -44,7 +44,6 @@ import { Diagnostic as DiagnosticService } from '@awesome-cordova-plugins/diagno import { Device as DeviceService } from '@awesome-cordova-plugins/device/ngx'; import { File as FileService } from '@awesome-cordova-plugins/file/ngx'; import { FileOpener as FileOpenerService } from '@awesome-cordova-plugins/file-opener/ngx'; -import { FileTransfer as FileTransferService } from '@awesome-cordova-plugins/file-transfer/ngx'; import { Geolocation as GeolocationService } from '@awesome-cordova-plugins/geolocation/ngx'; import { HTTP } from '@awesome-cordova-plugins/http/ngx'; import { InAppBrowser as InAppBrowserService } from '@awesome-cordova-plugins/in-app-browser/ngx'; @@ -175,7 +174,6 @@ export const Clipboard = makeSingleton(ClipboardService); export const Diagnostic = makeSingleton(DiagnosticService); export const File = makeSingleton(FileService); export const FileOpener = makeSingleton(FileOpenerService); -export const FileTransfer = makeSingleton(FileTransferService); export const Geolocation = makeSingleton(GeolocationService); export const InAppBrowser = makeSingleton(InAppBrowserService); export const Keyboard = makeSingleton(KeyboardService); diff --git a/upgrade.txt b/upgrade.txt index 0689f7b5eb6..155c45e9a4e 100644 --- a/upgrade.txt +++ b/upgrade.txt @@ -13,6 +13,7 @@ For more information about upgrading, read the official documentation: https://m - CoreLoginHelper.getErrorMessages has been removed. Please create the messages object yourself. - CoreAppProvider.isAutomated() has been deprecated, use CorePlatformService.isAutomated() instead. - Due to a breaking change in cordova-plugin-file, avoid using FileEntry.toURL(). Use CoreFileProvider.getFileEntryURL instead. + - FileTransfer service is no longer available, now we recommend use window.FileTransfer instead. === 4.3.0 ===