MOBILE-4481 ws.ts: Get mimetype from file transfer headers

main
Alfonso Salces 2023-12-14 08:50:26 +01:00
parent 88931bd50d
commit 8f2c3445e2
9 changed files with 89 additions and 110 deletions

24
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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(),

View File

@ -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<void> {
window.FileTransferError = FileTransferErrorMock;
window.FileTransfer = FileTransferMock;
const fileService = File.instance;

View File

@ -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 {
export class FileTransferMock {
/**
* 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 {
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<unknown> {
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<string, any>,
): 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<void> => {
@ -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<FileUploadResult> {
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<string, string> | undefined };

View File

@ -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,

View File

@ -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<string, string> | 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);

View File

@ -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);

View File

@ -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 ===