forked from EVOgeek/Vmeda.Online
		
	MOBILE-3565 browser: Mock some ionic-native services for browser
This commit is contained in:
		
							parent
							
								
									459302547b
								
							
						
					
					
						commit
						1ed95a4ade
					
				
							
								
								
									
										43
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										43
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -10527,6 +10527,11 @@ | ||||
|       "dev": true, | ||||
|       "optional": true | ||||
|     }, | ||||
|     "immediate": { | ||||
|       "version": "3.0.6", | ||||
|       "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", | ||||
|       "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" | ||||
|     }, | ||||
|     "import-cwd": { | ||||
|       "version": "2.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", | ||||
| @ -11253,8 +11258,7 @@ | ||||
|     "isarray": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", | ||||
|       "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", | ||||
|       "dev": true | ||||
|       "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" | ||||
|     }, | ||||
|     "isexe": { | ||||
|       "version": "2.0.0", | ||||
| @ -12559,6 +12563,17 @@ | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "jszip": { | ||||
|       "version": "3.5.0", | ||||
|       "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.5.0.tgz", | ||||
|       "integrity": "sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA==", | ||||
|       "requires": { | ||||
|         "lie": "~3.3.0", | ||||
|         "pako": "~1.0.2", | ||||
|         "readable-stream": "~2.3.6", | ||||
|         "set-immediate-shim": "~1.0.1" | ||||
|       } | ||||
|     }, | ||||
|     "just-debounce": { | ||||
|       "version": "1.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", | ||||
| @ -12741,6 +12756,14 @@ | ||||
|         "webpack-sources": "^1.2.0" | ||||
|       } | ||||
|     }, | ||||
|     "lie": { | ||||
|       "version": "3.3.0", | ||||
|       "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", | ||||
|       "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", | ||||
|       "requires": { | ||||
|         "immediate": "~3.0.5" | ||||
|       } | ||||
|     }, | ||||
|     "liftoff": { | ||||
|       "version": "3.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", | ||||
| @ -14745,8 +14768,7 @@ | ||||
|     "pako": { | ||||
|       "version": "1.0.11", | ||||
|       "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", | ||||
|       "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", | ||||
|       "dev": true | ||||
|       "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" | ||||
|     }, | ||||
|     "parallel-transform": { | ||||
|       "version": "1.2.0", | ||||
| @ -15936,8 +15958,7 @@ | ||||
|     "process-nextick-args": { | ||||
|       "version": "2.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", | ||||
|       "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", | ||||
|       "dev": true | ||||
|       "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" | ||||
|     }, | ||||
|     "progress": { | ||||
|       "version": "2.0.3", | ||||
| @ -16301,7 +16322,6 @@ | ||||
|       "version": "2.3.7", | ||||
|       "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", | ||||
|       "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "core-util-is": "~1.0.0", | ||||
|         "inherits": "~2.0.3", | ||||
| @ -17465,6 +17485,11 @@ | ||||
|       "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "set-immediate-shim": { | ||||
|       "version": "1.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", | ||||
|       "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" | ||||
|     }, | ||||
|     "set-value": { | ||||
|       "version": "2.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", | ||||
| @ -18381,7 +18406,6 @@ | ||||
|       "version": "1.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", | ||||
|       "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "safe-buffer": "~5.1.0" | ||||
|       } | ||||
| @ -19543,8 +19567,7 @@ | ||||
|     "util-deprecate": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", | ||||
|       "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", | ||||
|       "dev": true | ||||
|       "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" | ||||
|     }, | ||||
|     "util-promisify": { | ||||
|       "version": "2.1.0", | ||||
|  | ||||
| @ -101,6 +101,7 @@ | ||||
|     "cordova-support-google-services": "^1.2.1", | ||||
|     "cordova.plugins.diagnostic": "^6.0.2", | ||||
|     "es6-promise-plugin": "^4.2.2", | ||||
|     "jszip": "^3.5.0", | ||||
|     "moment": "^2.29.0", | ||||
|     "nl.kingsquare.cordova.background-audio": "^1.0.1", | ||||
|     "phonegap-plugin-multidex": "^1.0.0", | ||||
|  | ||||
| @ -13,6 +13,10 @@ | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { NgModule } from '@angular/core'; | ||||
| import { Platform } from '@ionic/angular'; | ||||
| 
 | ||||
| import { CoreInitDelegate } from '@services/init'; | ||||
| import { CoreEmulatorHelperProvider } from './services/helper'; | ||||
| 
 | ||||
| // Ionic Native services.
 | ||||
| import { Clipboard } from '@ionic-native/clipboard/ngx'; | ||||
| @ -36,6 +40,16 @@ import { StatusBar } from '@ionic-native/status-bar/ngx'; | ||||
| import { WebIntent } from '@ionic-native/web-intent/ngx'; | ||||
| import { Zip } from '@ionic-native/zip/ngx'; | ||||
| 
 | ||||
| // Mock services.
 | ||||
| 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 { NetworkMock } from './services/network'; | ||||
| import { ZipMock } from './services/zip'; | ||||
| 
 | ||||
| /** | ||||
|  * This module handles the emulation of Cordova plugins in browser and desktop. | ||||
|  * | ||||
| @ -51,18 +65,47 @@ import { Zip } from '@ionic-native/zip/ngx'; | ||||
|     imports: [ | ||||
|     ], | ||||
|     providers: [ | ||||
|         Clipboard, | ||||
|         CoreEmulatorHelperProvider, | ||||
|         { | ||||
|             provide: Clipboard, | ||||
|             deps: [Platform], // Use platform instead of AppProvider to prevent errors with singleton injection.
 | ||||
|             useFactory: (platform: Platform): Clipboard => platform.is('cordova') ? new Clipboard() : new ClipboardMock(), | ||||
|         }, | ||||
|         Device, | ||||
|         Diagnostic, | ||||
|         File, | ||||
|         FileOpener, | ||||
|         FileTransfer, | ||||
|         Geolocation, | ||||
|         { | ||||
|             provide: File, | ||||
|             deps: [Platform], | ||||
|             useFactory: (platform: Platform): File => platform.is('cordova') ? new File() : new FileMock(), | ||||
|         }, | ||||
|         { | ||||
|             provide: FileOpener, | ||||
|             deps: [Platform], | ||||
|             useFactory: (platform: Platform): FileOpener => platform.is('cordova') ? new FileOpener() : new FileOpenerMock(), | ||||
|         }, | ||||
|         { | ||||
|             provide: FileTransfer, | ||||
|             deps: [Platform], | ||||
|             useFactory: (platform: Platform): FileTransfer => platform.is('cordova') ? new FileTransfer() : new FileTransferMock(), | ||||
|         }, | ||||
|         { | ||||
|             provide: Geolocation, | ||||
|             deps: [Platform], | ||||
|             useFactory: (platform: Platform): Geolocation => platform.is('cordova') ? new Geolocation() : new GeolocationMock(), | ||||
|         }, | ||||
|         HTTP, | ||||
|         InAppBrowser, | ||||
|         { | ||||
|             provide: InAppBrowser, | ||||
|             deps: [Platform], | ||||
|             useFactory: (platform: Platform): InAppBrowser => platform.is('cordova') ? new InAppBrowser() : new InAppBrowserMock(), | ||||
|         }, | ||||
|         Keyboard, | ||||
|         LocalNotifications, | ||||
|         Network, | ||||
|         { | ||||
|             provide: Network, | ||||
|             deps: [Platform], | ||||
|             useFactory: (platform: Platform): Network => platform.is('cordova') ? new Network() : new NetworkMock(), | ||||
|         }, | ||||
|         Push, | ||||
|         QRScanner, | ||||
|         SplashScreen, | ||||
| @ -70,7 +113,25 @@ import { Zip } from '@ionic-native/zip/ngx'; | ||||
|         StatusBar, | ||||
|         WebIntent, | ||||
|         WebView, | ||||
|         Zip, | ||||
|         { | ||||
|             provide: Zip, | ||||
|             deps: [Platform, File], | ||||
|             useFactory: (platform: Platform, file: File): Zip => platform.is('cordova') ? new Zip() : new ZipMock(file), | ||||
|         }, | ||||
|     ], | ||||
| }) | ||||
| export class CoreEmulatorModule { } | ||||
| export class CoreEmulatorModule { | ||||
| 
 | ||||
|     constructor( | ||||
|         platform: Platform, | ||||
|         initDelegate: CoreInitDelegate, | ||||
|         helper: CoreEmulatorHelperProvider, | ||||
|     ) { | ||||
| 
 | ||||
|         if (!platform.is('cordova')) { | ||||
|             // Register an init process to load the Mocks that need it.
 | ||||
|             initDelegate.registerProcess(helper); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
							
								
								
									
										87
									
								
								src/app/core/emulator/services/clipboard.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								src/app/core/emulator/services/clipboard.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,87 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { Clipboard } from '@ionic-native/clipboard/ngx'; | ||||
| 
 | ||||
| /** | ||||
|  * Emulates the Cordova Clipboard plugin in browser. | ||||
|  */ | ||||
| @Injectable() | ||||
| export class ClipboardMock extends Clipboard { | ||||
| 
 | ||||
|     protected copyTextarea: HTMLTextAreaElement; | ||||
| 
 | ||||
|     constructor() { | ||||
|         super(); | ||||
| 
 | ||||
|         // In browser the text must be selected in order to copy it. Create a hidden textarea to put the text in it.
 | ||||
|         this.copyTextarea = document.createElement('textarea'); | ||||
|         this.copyTextarea.className = 'core-browser-copy-area'; | ||||
|         this.copyTextarea.setAttribute('aria-hidden', 'true'); | ||||
|         document.body.appendChild(this.copyTextarea); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Copy some text to the clipboard. | ||||
|      * | ||||
|      * @param text The text to copy. | ||||
|      * @return Promise resolved when copied. | ||||
|      */ | ||||
|     copy(text: string): Promise<void> { | ||||
|         return new Promise((resolve, reject): void => { | ||||
|             // Put the text in the hidden textarea and select it.
 | ||||
|             this.copyTextarea.innerHTML = text; | ||||
|             this.copyTextarea.select(); | ||||
| 
 | ||||
|             try { | ||||
|                 if (document.execCommand('copy')) { | ||||
|                     resolve(); | ||||
|                 } else { | ||||
|                     reject(); | ||||
|                 } | ||||
|             } catch (err) { | ||||
|                 reject(); | ||||
|             } | ||||
| 
 | ||||
|             this.copyTextarea.innerHTML = ''; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /* | ||||
|      * Get the text stored in the clipboard. | ||||
|      * | ||||
|      * @return Promise resolved with the text. | ||||
|      */ | ||||
|     paste(): Promise<string> { | ||||
|         return new Promise((resolve, reject): void => { | ||||
|             // Paste the text in the hidden textarea and get it.
 | ||||
|             this.copyTextarea.innerHTML = ''; | ||||
|             this.copyTextarea.select(); | ||||
| 
 | ||||
|             try { | ||||
|                 if (document.execCommand('paste')) { | ||||
|                     resolve(this.copyTextarea.innerHTML); | ||||
|                 } else { | ||||
|                     reject(); | ||||
|                 } | ||||
|             } catch (err) { | ||||
|                 reject(); | ||||
|             } | ||||
| 
 | ||||
|             this.copyTextarea.innerHTML = ''; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										56
									
								
								src/app/core/emulator/services/file-opener.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/app/core/emulator/services/file-opener.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { FileOpener } from '@ionic-native/file-opener/ngx'; | ||||
| 
 | ||||
| /** | ||||
|  * Emulates the FileOpener plugin in browser. | ||||
|  */ | ||||
| /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any */ | ||||
| @Injectable() | ||||
| export class FileOpenerMock extends FileOpener { | ||||
| 
 | ||||
|     /** | ||||
|      * Check if an app is already installed. | ||||
|      * | ||||
|      * @param packageId Package ID. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     appIsInstalled(packageId: string): Promise<any> { | ||||
|         return Promise.reject('appIsInstalled not supported in browser.'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Open an file. | ||||
|      * | ||||
|      * @param filePath File path. | ||||
|      * @param fileMIMEType File MIME type. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async open(filePath: string, fileMIMEType: string): Promise<any> { | ||||
|         window.open(filePath, '_blank'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Uninstalls a package. | ||||
|      * | ||||
|      * @param packageId Package ID. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     uninstall(packageId: string): Promise<any> { | ||||
|         return Promise.reject('uninstall not supported in browser.'); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										379
									
								
								src/app/core/emulator/services/file-transfer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										379
									
								
								src/app/core/emulator/services/file-transfer.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,379 @@ | ||||
| // (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 { CoreTextUtils } from '@/app/services/utils/text'; | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { FileTransfer, FileTransferObject, FileUploadResult, FileTransferError } from '@ionic-native/file-transfer/ngx'; | ||||
| 
 | ||||
| import { CoreFile } from '@services/file'; | ||||
| 
 | ||||
| /** | ||||
|  * Mock the File Transfer Error. | ||||
|  */ | ||||
| export class FileTransferErrorMock implements FileTransferError { | ||||
| 
 | ||||
|     static readonly FILE_NOT_FOUND_ERR = 1; | ||||
|     static readonly INVALID_URL_ERR = 2; | ||||
|     static readonly CONNECTION_ERR = 3; | ||||
|     static readonly ABORT_ERR = 4; | ||||
|     static readonly NOT_MODIFIED_ERR = 5; | ||||
| 
 | ||||
|     constructor( | ||||
|         public code: number, | ||||
|         public source: string, | ||||
|         public target: string, | ||||
|         public http_status: number, | ||||
|         public body: string, | ||||
|         public exception: string, | ||||
|     ) { } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Emulates the Cordova FileTransfer plugin in desktop apps and in browser. | ||||
|  */ | ||||
| @Injectable() | ||||
| export class FileTransferMock extends FileTransfer { | ||||
| 
 | ||||
|     /** | ||||
|      * Creates a new FileTransferObjectMock object. | ||||
|      */ | ||||
|     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; | ||||
| 
 | ||||
|     /** | ||||
|      * Aborts an in-progress transfer. The onerror callback is passed a FileTransferError | ||||
|      * object which has an error code of FileTransferError.ABORT_ERR. | ||||
|      */ | ||||
|     abort(): void { | ||||
|         if (this.xhr) { | ||||
|             this.xhr.abort(); | ||||
|             this.reject!(new FileTransferErrorMock(FileTransferErrorMock.ABORT_ERR, this.source!, this.target!, 0, '', '')); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Downloads a file from server. | ||||
|      * | ||||
|      * @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 trustAllHosts If set to true, it accepts all security certificates. | ||||
|      * @param options Optional parameters, currently only supports headers. | ||||
|      * @return 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 => { | ||||
|             // Use XMLHttpRequest instead of HttpClient to support onprogress and abort.
 | ||||
|             const basicAuthHeader = this.getBasicAuthHeader(source); | ||||
|             const xhr = new XMLHttpRequest(); | ||||
| 
 | ||||
|             this.xhr = xhr; | ||||
|             this.source = source; | ||||
|             this.target = target; | ||||
|             this.reject = reject; | ||||
| 
 | ||||
|             if (basicAuthHeader) { | ||||
|                 source = source.replace(this.getUrlCredentials(source) + '@', ''); | ||||
| 
 | ||||
|                 options = options || {}; | ||||
|                 options.headers = options.headers || {}; | ||||
|                 options.headers[basicAuthHeader.name] = basicAuthHeader.value; | ||||
|             } | ||||
| 
 | ||||
|             const headers = options?.headers || null; | ||||
| 
 | ||||
|             // Prepare the request.
 | ||||
|             xhr.open('GET', source, true); | ||||
|             xhr.responseType = 'blob'; | ||||
|             for (const name in headers) { | ||||
|                 xhr.setRequestHeader(name, headers[name]); | ||||
|             } | ||||
| 
 | ||||
|             xhr.onprogress = (ev: ProgressEvent): void => { | ||||
|                 if (this.progressListener) { | ||||
|                     this.progressListener(ev); | ||||
|                 } | ||||
|             }; | ||||
| 
 | ||||
|             xhr.onerror = (): void => { | ||||
|                 reject(new FileTransferErrorMock(-1, source, target, xhr.status, xhr.statusText, '')); | ||||
|             }; | ||||
| 
 | ||||
|             xhr.onload = async (): Promise<void> => { | ||||
|                 // Finished dowloading the file.
 | ||||
|                 let response = xhr.response || xhr.responseText; | ||||
| 
 | ||||
|                 const status = Math.max(xhr.status === 1223 ? 204 : xhr.status, 0); | ||||
|                 if (status < 200 || status >= 300) { | ||||
|                     // Request failed. Try to get the error message.
 | ||||
|                     response = await this.parseResponse(response); | ||||
| 
 | ||||
|                     reject(new FileTransferErrorMock(-1, source, target, xhr.status, response || xhr.statusText, '')); | ||||
| 
 | ||||
|                     return; | ||||
|                 } | ||||
| 
 | ||||
|                 if (!response) { | ||||
|                     reject(); | ||||
| 
 | ||||
|                     return; | ||||
|                 } | ||||
| 
 | ||||
|                 const basePath = CoreFile.instance.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.instance.writeFile(target, response).then(resolve, reject); | ||||
|             }; | ||||
| 
 | ||||
|             xhr.send(); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Given a URL, check if it has a credentials in it and, if so, return them in a header object. | ||||
|      * This code is extracted from Cordova FileTransfer plugin. | ||||
|      * | ||||
|      * @param urlString The URL to get the credentials from. | ||||
|      * @return The header with the credentials, null if no credentials. | ||||
|      */ | ||||
|     protected getBasicAuthHeader(urlString: string): {name: string; value: string} | null { | ||||
|         let header: {name: string; value: string} | null = null; | ||||
| 
 | ||||
|         // MS Windows doesn't support credentials in http uris so we detect them by regexp and strip off from result url.
 | ||||
|         if (window.btoa) { | ||||
|             const credentials = this.getUrlCredentials(urlString); | ||||
|             if (credentials) { | ||||
|                 header = { | ||||
|                     name: 'Authorization', | ||||
|                     value: 'Basic ' + window.btoa(credentials), | ||||
|                 }; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return header; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Given an instance of XMLHttpRequest, get the response headers as an object. | ||||
|      * | ||||
|      * @param xhr XMLHttpRequest instance. | ||||
|      * @return Object with the headers. | ||||
|      */ | ||||
|     protected getHeadersAsObject(xhr: XMLHttpRequest): Record<string, string> { | ||||
|         const headersString = xhr.getAllResponseHeaders(); | ||||
|         const result = {}; | ||||
| 
 | ||||
|         if (headersString) { | ||||
|             const headers = headersString.split('\n'); | ||||
|             for (const i in headers) { | ||||
|                 const headerString = headers[i]; | ||||
|                 const separatorPos = headerString.indexOf(':'); | ||||
|                 if (separatorPos != -1) { | ||||
|                     result[headerString.substr(0, separatorPos)] = headerString.substr(separatorPos + 1).trim(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the credentials from a URL. | ||||
|      * This code is extracted from Cordova FileTransfer plugin. | ||||
|      * | ||||
|      * @param urlString The URL to get the credentials from. | ||||
|      * @return Retrieved credentials. | ||||
|      */ | ||||
|     protected getUrlCredentials(urlString: string): string | null { | ||||
|         const credentialsPattern = /^https?:\/\/(?:(?:(([^:@/]*)(?::([^@/]*))?)?@)?([^:/?#]*)(?::(\d*))?).*$/; | ||||
|         const credentials = credentialsPattern.exec(urlString); | ||||
| 
 | ||||
|         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. | ||||
|      * | ||||
|      * @param response The response to parse. | ||||
|      * @return Promise resolved with the parsed response. | ||||
|      */ | ||||
|     protected async parseResponse(response: Blob | ArrayBuffer | string | null): Promise<unknown> { | ||||
|         if (!response) { | ||||
|             return ''; | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         let responseText = ''; | ||||
| 
 | ||||
|         if (response instanceof Blob) { | ||||
|             responseText = await this.blobToText(response); | ||||
| 
 | ||||
|         } else if (response instanceof ArrayBuffer) { | ||||
|             // Convert the ArrayBuffer into text.
 | ||||
|             responseText = String.fromCharCode.apply(null, new Uint8Array(response)); | ||||
| 
 | ||||
|         } else { | ||||
|             responseText = response; | ||||
|         } | ||||
| 
 | ||||
|         return CoreTextUtils.instance.parseJSON(responseText, ''); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Convert a Blob to text. | ||||
|      * | ||||
|      * @param blob Blob to convert. | ||||
|      * @return Promise resolved with blob contents. | ||||
|      */ | ||||
|     protected blobToText(blob: Blob): Promise<string> { | ||||
|         return new Promise<string>((resolve) => { | ||||
|             const reader = new FileReader(); | ||||
|             reader.onloadend = (): void => { | ||||
|                 resolve(<string> reader.result); | ||||
|             }; | ||||
|             reader.readAsText(blob); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sends a file to a server. | ||||
|      * | ||||
|      * @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 options Optional parameters. | ||||
|      * @return 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 => { | ||||
|             const basicAuthHeader = this.getBasicAuthHeader(url); | ||||
|             let fileKey: string | undefined; | ||||
|             let fileName: string | undefined; | ||||
|             let params: any; // eslint-disable-line @typescript-eslint/no-explicit-any
 | ||||
|             let headers: any; // eslint-disable-line @typescript-eslint/no-explicit-any
 | ||||
|             let httpMethod: string | undefined; | ||||
| 
 | ||||
|             if (basicAuthHeader) { | ||||
|                 url = url.replace(this.getUrlCredentials(url) + '@', ''); | ||||
| 
 | ||||
|                 options = options || {}; | ||||
|                 options.headers = options.headers || {}; | ||||
|                 options.headers[basicAuthHeader.name] = basicAuthHeader.value; | ||||
|             } | ||||
| 
 | ||||
|             if (options) { | ||||
|                 fileKey = options.fileKey; | ||||
|                 fileName = options.fileName; | ||||
|                 headers = options.headers; | ||||
|                 httpMethod = options.httpMethod || 'POST'; | ||||
| 
 | ||||
|                 if (httpMethod.toUpperCase() == 'PUT') { | ||||
|                     httpMethod = 'PUT'; | ||||
|                 } else { | ||||
|                     httpMethod = 'POST'; | ||||
|                 } | ||||
| 
 | ||||
|                 params = options.params || {}; | ||||
|             } | ||||
| 
 | ||||
|             // Add fileKey and fileName to the headers.
 | ||||
|             headers = headers || {}; | ||||
|             if (!headers['Content-Disposition']) { | ||||
|                 headers['Content-Disposition'] = 'form-data;' + (fileKey ? ' name="' + fileKey + '";' : '') + | ||||
|                     (fileName ? ' filename="' + fileName + '"' : ''); | ||||
|             } | ||||
| 
 | ||||
|             // 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.instance.getFile(fileUrl).then((fileEntry) => | ||||
|                 CoreFile.instance.getFileObjectFromFileEntry(fileEntry)).then((file) => { | ||||
|                 // Use XMLHttpRequest instead of HttpClient to support onprogress and abort.
 | ||||
|                 const xhr = new XMLHttpRequest(); | ||||
|                 xhr.open(httpMethod || 'POST', url); | ||||
|                 for (const name in headers) { | ||||
|                     // Filter "unsafe" headers.
 | ||||
|                     if (name != 'Connection') { | ||||
|                         xhr.setRequestHeader(name, headers[name]); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 xhr.onprogress = (ev: ProgressEvent): void => { | ||||
|                     if (this.progressListener) { | ||||
|                         this.progressListener(ev); | ||||
|                     } | ||||
|                 }; | ||||
| 
 | ||||
|                 this.xhr = xhr; | ||||
|                 this.source = fileUrl; | ||||
|                 this.target = url; | ||||
|                 this.reject = reject; | ||||
| 
 | ||||
|                 xhr.onerror = (): void => { | ||||
|                     reject(new FileTransferErrorMock(-1, fileUrl, url, xhr.status, xhr.statusText, '')); | ||||
|                 }; | ||||
| 
 | ||||
|                 xhr.onload = (): void => { | ||||
|                     // Finished uploading the file.
 | ||||
|                     resolve({ | ||||
|                         bytesSent: file.size, | ||||
|                         responseCode: xhr.status, | ||||
|                         response: xhr.response, | ||||
|                         headers: this.getHeadersAsObject(xhr), | ||||
|                     }); | ||||
|                 }; | ||||
| 
 | ||||
|                 // Create a form data to send params and the file.
 | ||||
|                 const fd = new FormData(); | ||||
|                 for (const name in params) { | ||||
|                     fd.append(name, params[name]); | ||||
|                 } | ||||
|                 fd.append('file', file, fileName); | ||||
| 
 | ||||
|                 xhr.send(fd); | ||||
| 
 | ||||
|                 return; | ||||
|             }).catch(reject); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										797
									
								
								src/app/core/emulator/services/file.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										797
									
								
								src/app/core/emulator/services/file.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,797 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { File, Entry, DirectoryEntry, FileEntry, IWriteOptions, RemoveResult } from '@ionic-native/file/ngx'; | ||||
| 
 | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| 
 | ||||
| /** | ||||
|  * Implement the File Error because the ionic-native plugin doesn't implement it. | ||||
|  */ | ||||
| class FileError { | ||||
| 
 | ||||
|     static readonly NOT_FOUND_ERR = 1; | ||||
|     static readonly SECURITY_ERR = 2; | ||||
|     static readonly ABORT_ERR = 3; | ||||
|     static readonly NOT_READABLE_ERR = 4; | ||||
|     static readonly ENCODING_ERR = 5; | ||||
|     static readonly NO_MODIFICATION_ALLOWED_ERR = 6; | ||||
|     static readonly INVALID_STATE_ERR = 7; | ||||
|     static readonly SYNTAX_ERR = 8; | ||||
|     static readonly INVALID_MODIFICATION_ERR = 9; | ||||
|     static readonly QUOTA_EXCEEDED_ERR = 10; | ||||
|     static readonly TYPE_MISMATCH_ERR = 11; | ||||
|     static readonly PATH_EXISTS_ERR = 12; | ||||
| 
 | ||||
|     message?: string; | ||||
| 
 | ||||
|     constructor( | ||||
|         public code: number, | ||||
|     ) { } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Emulates the Cordova File plugin in browser. | ||||
|  * Most of the code is extracted from the File class of Ionic Native. | ||||
|  */ | ||||
| @Injectable() | ||||
| export class FileMock extends File { | ||||
| 
 | ||||
|     /** | ||||
|      * Check if a directory exists in a certain path, directory. | ||||
|      * | ||||
|      * @param path Base FileSystem. | ||||
|      * @param dir Name of directory to check | ||||
|      * @return Returns a Promise that resolves to true if the directory exists or rejects with an error. | ||||
|      */ | ||||
|     async checkDir(path: string, dir: string): Promise<boolean> { | ||||
|         const fullPath = CoreTextUtils.instance.concatenatePaths(path, dir); | ||||
| 
 | ||||
|         await this.resolveDirectoryUrl(fullPath); | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if a file exists in a certain path, directory. | ||||
|      * | ||||
|      * @param path Base FileSystem. | ||||
|      * @param file Name of file to check. | ||||
|      * @return Returns a Promise that resolves with a boolean or rejects with an error. | ||||
|      */ | ||||
|     async checkFile(path: string, file: string): Promise<boolean> { | ||||
|         const entry = await this.resolveLocalFilesystemUrl(CoreTextUtils.instance.concatenatePaths(path, file)); | ||||
| 
 | ||||
|         if (entry.isFile) { | ||||
|             return true; | ||||
|         } else { | ||||
|             const error = new FileError(13); | ||||
|             error.message = 'input is not a file'; | ||||
| 
 | ||||
|             throw error; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Copy a file or directory. | ||||
|      * | ||||
|      * @param srce The Entry to copy. | ||||
|      * @param destDir The directory where to put the copy. | ||||
|      * @param newName New name of the file/dir. | ||||
|      * @return Returns a Promise that resolves to the new Entry object or rejects with an error. | ||||
|      */ | ||||
|     private copyMock(srce: Entry, destDir: DirectoryEntry, newName: string): Promise<Entry> { | ||||
|         return new Promise<Entry>((resolve, reject): void => { | ||||
|             newName = newName.replace(/%20/g, ' '); // Replace all %20 with spaces.
 | ||||
| 
 | ||||
|             srce.copyTo(destDir, newName, (deste) => { | ||||
|                 resolve(deste); | ||||
|             }, (err) => { | ||||
|                 this.fillErrorMessageMock(err); | ||||
|                 reject(err); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Copy a directory in various methods. If destination directory exists, will fail to copy. | ||||
|      * | ||||
|      * @param path Base FileSystem. Please refer to the iOS and Android filesystems above. | ||||
|      * @param dirName Name of directory to copy. | ||||
|      * @param newPath Base FileSystem of new location. | ||||
|      * @param newDirName New name of directory to copy to (leave blank to remain the same). | ||||
|      * @return Returns a Promise that resolves to the new Entry object or rejects with an error. | ||||
|      */ | ||||
|     copyDir(path: string, dirName: string, newPath: string, newDirName: string): Promise<Entry> { | ||||
|         return this.copyFileOrDir(path, dirName, newPath, newDirName); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Copy a file in various methods. If file exists, will fail to copy. | ||||
|      * | ||||
|      * @param path Base FileSystem. Please refer to the iOS and Android filesystems above | ||||
|      * @param fileName Name of file to copy | ||||
|      * @param newPath Base FileSystem of new location | ||||
|      * @param newFileName New name of file to copy to (leave blank to remain the same) | ||||
|      * @return Returns a Promise that resolves to an Entry or rejects with an error. | ||||
|      */ | ||||
|     copyFile(path: string, fileName: string, newPath: string, newFileName: string): Promise<Entry> { | ||||
|         return this.copyFileOrDir(path, fileName, newPath, newFileName || fileName); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Copy a file or dir to a given path. | ||||
|      * | ||||
|      * @param sourcePath Path of the file/dir to copy. | ||||
|      * @param sourceName Name of file/dir to copy | ||||
|      * @param destPath Path where to copy. | ||||
|      * @param destName New name of file/dir. | ||||
|      * @return Returns a Promise that resolves to the new Entry or rejects with an error. | ||||
|      */ | ||||
|     async copyFileOrDir(sourcePath: string, sourceName: string, destPath: string, destName: string): Promise<Entry> { | ||||
|         const destFixed = this.fixPathAndName(destPath, destName); | ||||
| 
 | ||||
|         const source = await this.resolveLocalFilesystemUrl(CoreTextUtils.instance.concatenatePaths(sourcePath, sourceName)); | ||||
| 
 | ||||
|         const destParentDir = await this.resolveDirectoryUrl(destFixed.path); | ||||
| 
 | ||||
|         return this.copyMock(source, destParentDir, destFixed.name); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Creates a new directory in the specific path. | ||||
|      * The replace boolean value determines whether to replace an existing directory with the same name. | ||||
|      * If an existing directory exists and the replace value is false, the promise will fail and return an error. | ||||
|      * | ||||
|      * @param path Base FileSystem. | ||||
|      * @param dirName Name of directory to create | ||||
|      * @param replace If true, replaces file with same name. If false returns error | ||||
|      * @return Returns a Promise that resolves with a DirectoryEntry or rejects with an error. | ||||
|      */ | ||||
|     async createDir(path: string, dirName: string, replace: boolean): Promise<DirectoryEntry> { | ||||
|         const options: Flags = { | ||||
|             create: true, | ||||
|         }; | ||||
| 
 | ||||
|         if (!replace) { | ||||
|             options.exclusive = true; | ||||
|         } | ||||
| 
 | ||||
|         const parentDir = await this.resolveDirectoryUrl(path); | ||||
| 
 | ||||
|         return this.getDirectory(parentDir, dirName, options); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Creates a new file in the specific path. | ||||
|      * The replace boolean value determines whether to replace an existing file with the same name. | ||||
|      * If an existing file exists and the replace value is false, the promise will fail and return an error. | ||||
|      * | ||||
|      * @param path Base FileSystem. | ||||
|      * @param fileName Name of file to create. | ||||
|      * @param replace If true, replaces file with same name. If false returns error. | ||||
|      * @return Returns a Promise that resolves to a FileEntry or rejects with an error. | ||||
|      */ | ||||
|     async createFile(path: string, fileName: string, replace: boolean): Promise<FileEntry> { | ||||
|         const options: Flags = { | ||||
|             create: true, | ||||
|         }; | ||||
| 
 | ||||
|         if (!replace) { | ||||
|             options.exclusive = true; | ||||
|         } | ||||
| 
 | ||||
|         const parentDir = await this.resolveDirectoryUrl(path); | ||||
| 
 | ||||
|         return this.getFile(parentDir, fileName, options); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Create a file writer for a certain file. | ||||
|      * | ||||
|      * @param fe File entry object. | ||||
|      * @return Promise resolved with the FileWriter. | ||||
|      */ | ||||
|     private createWriterMock(fe: FileEntry): Promise<FileWriter> { | ||||
|         return new Promise<FileWriter>((resolve, reject): void => { | ||||
|             fe.createWriter((writer) => { | ||||
|                 resolve(writer); | ||||
|             }, (err) => { | ||||
|                 this.fillErrorMessageMock(err); | ||||
|                 reject(err); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Fill the message for an error. | ||||
|      * | ||||
|      * @param error Error. | ||||
|      */ | ||||
|     private fillErrorMessageMock(error: FileError): void { | ||||
|         try { | ||||
|             error.message = this.cordovaFileError[error.code]; | ||||
|         } catch (e) { | ||||
|             // Ignore errors.
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get a directory. | ||||
|      * | ||||
|      * @param directoryEntry Directory entry, obtained by resolveDirectoryUrl method | ||||
|      * @param directoryName Directory name | ||||
|      * @param flags Options | ||||
|      */ | ||||
|     getDirectory(directoryEntry: DirectoryEntry, directoryName: string, flags: Flags): Promise<DirectoryEntry> { | ||||
|         return new Promise<DirectoryEntry>((resolve, reject): void => { | ||||
|             try { | ||||
|                 directoryName = directoryName.replace(/%20/g, ' '); // Replace all %20 with spaces.
 | ||||
| 
 | ||||
|                 directoryEntry.getDirectory(directoryName, flags, (de) => { | ||||
|                     resolve(de); | ||||
|                 }, (err) => { | ||||
|                     this.fillErrorMessageMock(err); | ||||
|                     reject(err); | ||||
|                 }); | ||||
|             } catch (xc) { | ||||
|                 this.fillErrorMessageMock(xc); | ||||
|                 reject(xc); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get a file. | ||||
|      * | ||||
|      * @param directoryEntry Directory entry, obtained by resolveDirectoryUrl method | ||||
|      * @param fileName File name | ||||
|      * @param flags Options | ||||
|      */ | ||||
|     getFile(directoryEntry: DirectoryEntry, fileName: string, flags: Flags): Promise<FileEntry> { | ||||
|         return new Promise<FileEntry>((resolve, reject): void => { | ||||
|             try { | ||||
|                 fileName = fileName.replace(/%20/g, ' '); // Replace all %20 with spaces.
 | ||||
| 
 | ||||
|                 directoryEntry.getFile(fileName, flags, resolve, (err) => { | ||||
|                     this.fillErrorMessageMock(err); | ||||
|                     reject(err); | ||||
|                 }); | ||||
|             } catch (xc) { | ||||
|                 this.fillErrorMessageMock(xc); | ||||
|                 reject(xc); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get free disk space. | ||||
|      * | ||||
|      * @return Promise resolved with the free space. | ||||
|      */ | ||||
|     async getFreeDiskSpace(): Promise<number> { | ||||
|         // 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<number> => | ||||
|                 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.
 | ||||
| 
 | ||||
|         } else { | ||||
|             throw new Error('File system not available.'); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * List files and directory from a given path. | ||||
|      * | ||||
|      * @param path Base FileSystem. Please refer to the iOS and Android filesystems above | ||||
|      * @param dirName Name of directory | ||||
|      * @return Returns a Promise that resolves to an array of Entry objects or rejects with an error. | ||||
|      */ | ||||
|     async listDir(path: string, dirName: string): Promise<Entry[]> { | ||||
|         const parentDir = await this.resolveDirectoryUrl(path); | ||||
| 
 | ||||
|         const dirEntry = await this.getDirectory(parentDir, dirName, { create: false, exclusive: false }); | ||||
| 
 | ||||
|         return this.readEntriesMock(dirEntry.createReader()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Loads an initialize the API for browser. | ||||
|      * | ||||
|      * @return Promise resolved when loaded. | ||||
|      */ | ||||
|     load(): Promise<string> { | ||||
|         return new Promise((resolve, reject): void => { | ||||
|             // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | ||||
|             const win = <any> window; // Convert to <any> to be able to use non-standard properties.
 | ||||
| 
 | ||||
|             if (typeof win.requestFileSystem == 'undefined') { | ||||
|                 win.requestFileSystem = win.webkitRequestFileSystem; | ||||
|             } | ||||
|             if (typeof win.resolveLocalFileSystemURL == 'undefined') { | ||||
|                 win.resolveLocalFileSystemURL = win.webkitResolveLocalFileSystemURL; | ||||
|             } | ||||
|             win.LocalFileSystem = { | ||||
|                 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
 | ||||
|             (<any> navigator).webkitPersistentStorage.requestQuota(500 * 1024 * 1024, (granted) => { | ||||
|                 window.requestFileSystem(LocalFileSystem.PERSISTENT, granted, (entry) => { | ||||
|                     resolve(entry.root.toURL()); | ||||
|                 }, reject); | ||||
|             }, reject); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Move a file or directory. | ||||
|      * | ||||
|      * @param srce The Entry to copy. | ||||
|      * @param destDir The directory where to move the file/dir. | ||||
|      * @param newName New name of the file/dir. | ||||
|      * @return Returns a Promise that resolves to the new Entry object or rejects with an error. | ||||
|      */ | ||||
|     private moveMock(srce: Entry, destDir: DirectoryEntry, newName: string): Promise<Entry> { | ||||
|         return new Promise<Entry>((resolve, reject): void => { | ||||
|             newName = newName.replace(/%20/g, ' '); // Replace all %20 with spaces.
 | ||||
| 
 | ||||
|             srce.moveTo(destDir, newName, (deste) => { | ||||
|                 resolve(deste); | ||||
|             }, (err) => { | ||||
|                 this.fillErrorMessageMock(err); | ||||
|                 reject(err); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Move a directory to a given path. | ||||
|      * | ||||
|      * @param path The source path to the directory. | ||||
|      * @param dirName The source directory name. | ||||
|      * @param newPath The destionation path to the directory. | ||||
|      * @param newDirName The destination directory name. | ||||
|      * @return Returns a Promise that resolves to the new DirectoryEntry object or rejects with | ||||
|      *         an error. | ||||
|      */ | ||||
|     moveDir(path: string, dirName: string, newPath: string, newDirName: string): Promise<DirectoryEntry | Entry> { | ||||
|         return this.moveFileOrDir(path, dirName, newPath, newDirName); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Move a file to a given path. | ||||
|      * | ||||
|      * @param path Base FileSystem. Please refer to the iOS and Android filesystems above | ||||
|      * @param fileName Name of file to move | ||||
|      * @param newPath Base FileSystem of new location | ||||
|      * @param newFileName New name of file to move to (leave blank to remain the same) | ||||
|      * @return Returns a Promise that resolves to the new Entry or rejects with an error. | ||||
|      */ | ||||
|     moveFile(path: string, fileName: string, newPath: string, newFileName: string): Promise<Entry> { | ||||
|         return this.moveFileOrDir(path, fileName, newPath, newFileName || fileName); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Move a file or dir to a given path. | ||||
|      * | ||||
|      * @param sourcePath Path of the file/dir to copy. | ||||
|      * @param sourceName Name of file/dir to copy | ||||
|      * @param destPath Path where to copy. | ||||
|      * @param destName New name of file/dir. | ||||
|      * @return Returns a Promise that resolves to the new Entry or rejects with an error. | ||||
|      */ | ||||
|     async moveFileOrDir(sourcePath: string, sourceName: string, destPath: string, destName: string): Promise<Entry> { | ||||
|         const destFixed = this.fixPathAndName(destPath, destName); | ||||
| 
 | ||||
|         const source = await this.resolveLocalFilesystemUrl(CoreTextUtils.instance.concatenatePaths(sourcePath, sourceName)); | ||||
| 
 | ||||
|         const destParentDir = await this.resolveDirectoryUrl(destFixed.path); | ||||
| 
 | ||||
|         return this.moveMock(source, destParentDir, destFixed.name); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Fix a path and name, making sure the name doesn't contain any folder. If it does, the folder will be moved to the path. | ||||
|      * | ||||
|      * @param path Path to fix. | ||||
|      * @param name Name to fix. | ||||
|      * @return Fixed values. | ||||
|      */ | ||||
|     protected fixPathAndName(path: string, name: string): {path: string; name: string} { | ||||
| 
 | ||||
|         const fullPath = CoreTextUtils.instance.concatenatePaths(path, name); | ||||
| 
 | ||||
|         return { | ||||
|             path: fullPath.substring(0, fullPath.lastIndexOf('/')), | ||||
|             name: fullPath.substr(fullPath.lastIndexOf('/') + 1), | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Read file and return data as an ArrayBuffer. | ||||
|      * | ||||
|      * @param path Base FileSystem. | ||||
|      * @param file Name of file, relative to path. | ||||
|      * @return Returns a Promise that resolves with the contents of the file as ArrayBuffer or rejects | ||||
|      *         with an error. | ||||
|      */ | ||||
|     readAsArrayBuffer(path: string, file: string): Promise<ArrayBuffer> { | ||||
|         return this.readFileMock<ArrayBuffer>(path, file, 'ArrayBuffer'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Read file and return data as a binary data. | ||||
|      * | ||||
|      * @param path Base FileSystem. | ||||
|      * @param file Name of file, relative to path. | ||||
|      * @return Returns a Promise that resolves with the contents of the file as string rejects with an error. | ||||
|      */ | ||||
|     readAsBinaryString(path: string, file: string): Promise<string> { | ||||
|         return this.readFileMock<string>(path, file, 'BinaryString'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Read file and return data as a base64 encoded data url. | ||||
|      * A data url is of the form: | ||||
|      *      data: [<mediatype>][;base64],<data> | ||||
|      * | ||||
|      * @param path Base FileSystem. | ||||
|      * @param file Name of file, relative to path. | ||||
|      * @return Returns a Promise that resolves with the contents of the file as data URL or rejects | ||||
|      *         with an error. | ||||
|      */ | ||||
|     readAsDataURL(path: string, file: string): Promise<string> { | ||||
|         return this.readFileMock<string>(path, file, 'DataURL'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Read the contents of a file as text. | ||||
|      * | ||||
|      * @param path Base FileSystem. | ||||
|      * @param file Name of file, relative to path. | ||||
|      * @return Returns a Promise that resolves with the contents of the file as string or rejects with an error. | ||||
|      */ | ||||
|     readAsText(path: string, file: string): Promise<string> { | ||||
|         return this.readFileMock<string>(path, file, 'Text'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Read all the files and directories inside a directory. | ||||
|      * | ||||
|      * @param directoryReader The directory reader. | ||||
|      * @return Promise resolved with the list of files/dirs. | ||||
|      */ | ||||
|     private readEntriesMock(directoryReader: DirectoryReader): Promise<Entry[]> { | ||||
|         return new Promise<Entry[]>((resolve, reject): void => { | ||||
|             directoryReader.readEntries((entries: Entry[]) => { | ||||
|                 resolve(entries); | ||||
|             }, (error: FileError) => { | ||||
|                 this.fillErrorMessageMock(error); | ||||
|                 reject(error); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Read the contents of a file. | ||||
|      * | ||||
|      * @param path Base FileSystem. | ||||
|      * @param file Name of file, relative to path. | ||||
|      * @param readAs Format to read as. | ||||
|      * @return Returns a Promise that resolves with the contents of the file or rejects with an error. | ||||
|      */ | ||||
|     private async readFileMock<T>( | ||||
|         path: string, | ||||
|         file: string, | ||||
|         readAs: 'ArrayBuffer' | 'BinaryString' | 'DataURL' | 'Text', | ||||
|     ): Promise<T> { | ||||
|         const directoryEntry = await this.resolveDirectoryUrl(path); | ||||
| 
 | ||||
|         const fileEntry = await this.getFile(directoryEntry, file, { create: false }); | ||||
| 
 | ||||
|         const reader = new FileReader(); | ||||
| 
 | ||||
|         return new Promise<T>((resolve, reject): void => { | ||||
|             reader.onloadend = (): void => { | ||||
|                 if (reader.result !== undefined || reader.result !== null) { | ||||
|                     resolve(<T> <unknown> reader.result); | ||||
|                 } else if (reader.error !== undefined || reader.error !== null) { | ||||
|                     reject(reader.error); | ||||
|                 } else { | ||||
|                     reject({ code: null, message: 'READER_ONLOADEND_ERR' }); | ||||
|                 } | ||||
|             }; | ||||
| 
 | ||||
|             fileEntry.file((file) => { | ||||
|                 reader[`readAs${readAs}`].call(reader, file); | ||||
|             }, (error) => { | ||||
|                 reject(error); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Delete a file. | ||||
|      * | ||||
|      * @param entry The file to remove. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     private removeMock(entry: Entry): Promise<RemoveResult> { | ||||
|         return new Promise<RemoveResult>((resolve, reject): void => { | ||||
|             entry.remove(() => { | ||||
|                 resolve({ success: true, fileRemoved: entry }); | ||||
|             }, (err) => { | ||||
|                 this.fillErrorMessageMock(err); | ||||
|                 reject(err); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Remove a directory at a given path. | ||||
|      * | ||||
|      * @param path The path to the directory. | ||||
|      * @param dirName The directory name. | ||||
|      * @return Returns a Promise that resolves to a RemoveResult or rejects with an error. | ||||
|      */ | ||||
|     async removeDir(path: string, dirName: string): Promise<RemoveResult> { | ||||
|         const parentDir = await this.resolveDirectoryUrl(path); | ||||
| 
 | ||||
|         const dirEntry = await this.getDirectory(parentDir, dirName, { create: false }); | ||||
| 
 | ||||
|         return this.removeMock(dirEntry); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Removes a file from a desired location. | ||||
|      * | ||||
|      * @param path Base FileSystem. | ||||
|      * @param fileName Name of file to remove. | ||||
|      * @return Returns a Promise that resolves to a RemoveResult or rejects with an error. | ||||
|      */ | ||||
|     async removeFile(path: string, fileName: string): Promise<RemoveResult> { | ||||
|         const parentDir = await this.resolveDirectoryUrl(path); | ||||
| 
 | ||||
|         const fileEntry = await this.getFile(parentDir, fileName, { create: false }); | ||||
| 
 | ||||
|         return this.removeMock(fileEntry); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Removes all files and the directory from a desired location. | ||||
|      * | ||||
|      * @param path Base FileSystem. Please refer to the iOS and Android filesystems above | ||||
|      * @param dirName Name of directory | ||||
|      * @return Returns a Promise that resolves with a RemoveResult or rejects with an error. | ||||
|      */ | ||||
|     async removeRecursively(path: string, dirName: string): Promise<RemoveResult> { | ||||
|         const parentDir = await this.resolveDirectoryUrl(path); | ||||
| 
 | ||||
|         const dirEntry = await this.getDirectory(parentDir, dirName, { create: false }); | ||||
| 
 | ||||
|         return this.rimrafMock(dirEntry); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Resolves a local directory url. | ||||
|      * | ||||
|      * @param directoryUrl directory system url | ||||
|      */ | ||||
|     async resolveDirectoryUrl(directoryUrl: string): Promise<DirectoryEntry> { | ||||
|         const dirEntry = await this.resolveLocalFilesystemUrl(directoryUrl); | ||||
| 
 | ||||
|         if (dirEntry.isDirectory) { | ||||
|             return <DirectoryEntry> dirEntry; | ||||
|         } else { | ||||
|             const error = new FileError(13); | ||||
|             error.message = 'input is not a directory'; | ||||
| 
 | ||||
|             throw error; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Resolves a local file system URL. | ||||
|      * | ||||
|      * @param fileUrl file system url | ||||
|      */ | ||||
|     resolveLocalFilesystemUrl(fileUrl: string): Promise<Entry> { | ||||
|         return new Promise<Entry>((resolve, reject): void => { | ||||
|             try { | ||||
|                 window.resolveLocalFileSystemURL(fileUrl, (entry: Entry) => { | ||||
|                     resolve(entry); | ||||
|                 }, (error: FileError) => { | ||||
|                     this.fillErrorMessageMock(error); | ||||
|                     reject(error); | ||||
|                 }); | ||||
|             } catch (error) { | ||||
|                 this.fillErrorMessageMock(error); | ||||
|                 reject(error); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Remove a directory and all its contents. | ||||
|      * | ||||
|      * @param de Directory to remove. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     private rimrafMock(de: DirectoryEntry): Promise<RemoveResult> { | ||||
|         return new Promise<RemoveResult>((resolve, reject): void => { | ||||
|             de.removeRecursively(() => { | ||||
|                 resolve({ success: true, fileRemoved: de }); | ||||
|             }, (err) => { | ||||
|                 this.fillErrorMessageMock(err); | ||||
|                 reject(err); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Write some data in a file. | ||||
|      * | ||||
|      * @param writer File writer. | ||||
|      * @param data The data to write. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected writeMock(writer: FileWriter, data: string | Blob | ArrayBuffer): Promise<void> { | ||||
|         if (data instanceof Blob) { | ||||
|             return this.writeFileInChunksMock(writer, data); | ||||
|         } | ||||
| 
 | ||||
|         if (data instanceof ArrayBuffer) { | ||||
|             // Convert to string.
 | ||||
|             data = String.fromCharCode.apply(null, new Uint8Array(data)); | ||||
|         } | ||||
| 
 | ||||
|         return new Promise<void>((resolve, reject) => { | ||||
|             writer.onwriteend = (): void => { | ||||
|                 if (writer.error) { | ||||
|                     reject(writer.error); | ||||
|                 } else { | ||||
|                     resolve(); | ||||
|                 } | ||||
|             }; | ||||
|             writer.write(<string> data); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Write to an existing file. | ||||
|      * | ||||
|      * @param path Base FileSystem. | ||||
|      * @param fileName path relative to base path. | ||||
|      * @param text content or blob to write. | ||||
|      * @return Returns a Promise that resolves or rejects with an error. | ||||
|      */ | ||||
|     async writeExistingFile(path: string, fileName: string, text: string | Blob): Promise<void> { | ||||
|         await this.writeFile(path, fileName, text, { replace: true }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Write a new file to the desired location. | ||||
|      * | ||||
|      * @param path Base FileSystem. Please refer to the iOS and Android filesystems above | ||||
|      * @param fileName path relative to base path | ||||
|      * @param text content or blob to write | ||||
|      * @param options replace file if set to true. See WriteOptions for more information. | ||||
|      * @return Returns a Promise that resolves to updated file entry or rejects with an error. | ||||
|      */ | ||||
|     async writeFile( | ||||
|         path: string, | ||||
|         fileName: string, | ||||
|         text: string | Blob | ArrayBuffer, | ||||
|         options: IWriteOptions = {}, | ||||
|     ): Promise<FileEntry> { | ||||
|         const getFileOpts: Flags = { | ||||
|             create: !options.append, | ||||
|             exclusive: !options.replace, | ||||
|         }; | ||||
| 
 | ||||
|         const parentDir = await this.resolveDirectoryUrl(path); | ||||
| 
 | ||||
|         const fileEntry = await this.getFile(parentDir, fileName, getFileOpts); | ||||
| 
 | ||||
|         return this.writeFileEntryMock(fileEntry, text, options); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Write content to FileEntry. | ||||
|      * | ||||
|      * @param fe File entry object. | ||||
|      * @param text Content or blob to write. | ||||
|      * @param options replace file if set to true. See WriteOptions for more information. | ||||
|      * @return Returns a Promise that resolves to updated file entry or rejects with an error. | ||||
|      */ | ||||
|     private writeFileEntryMock( | ||||
|         fileEntry: FileEntry, | ||||
|         text: string | Blob | ArrayBuffer, | ||||
|         options: IWriteOptions, | ||||
|     ): Promise<FileEntry> { | ||||
|         return this.createWriterMock(fileEntry).then((writer) => { | ||||
|             if (options.append) { | ||||
|                 writer.seek(writer.length); | ||||
|             } | ||||
| 
 | ||||
|             if (options.truncate) { | ||||
|                 writer.truncate(options.truncate); | ||||
|             } | ||||
| 
 | ||||
|             return this.writeMock(writer, text); | ||||
|         }).then(() => fileEntry); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Write a file in chunks. | ||||
|      * | ||||
|      * @param writer File writer. | ||||
|      * @param data Data to write. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     private writeFileInChunksMock(writer: FileWriter, data: Blob): Promise<void> { | ||||
|         let writtenSize = 0; | ||||
|         const BLOCK_SIZE = 1024 * 1024; | ||||
|         const writeNextChunk = () => { | ||||
|             const size = Math.min(BLOCK_SIZE, data.size - writtenSize); | ||||
|             const chunk = data.slice(writtenSize, writtenSize + size); | ||||
| 
 | ||||
|             writtenSize += size; | ||||
|             writer.write(chunk); | ||||
|         }; | ||||
| 
 | ||||
|         return new Promise<void>((resolve, reject): void => { | ||||
|             writer.onerror = reject; | ||||
|             writer.onwriteend = (): void => { | ||||
|                 if (writtenSize < data.size) { | ||||
|                     writeNextChunk(); | ||||
|                 } else { | ||||
|                     resolve(); | ||||
|                 } | ||||
|             }; | ||||
|             writeNextChunk(); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										61
									
								
								src/app/core/emulator/services/geolocation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/app/core/emulator/services/geolocation.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,61 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { Geolocation, GeolocationOptions, Geoposition } from '@ionic-native/geolocation/ngx'; | ||||
| import { Observable, Subscriber, TeardownLogic } from 'rxjs'; | ||||
| 
 | ||||
| /** | ||||
|  * Emulates the Cordova Geolocation plugin in desktop apps and in browser. | ||||
|  */ | ||||
| @Injectable() | ||||
| export class GeolocationMock extends Geolocation { | ||||
| 
 | ||||
|     /** | ||||
|      * Get the device's current position. | ||||
|      * | ||||
|      * @param options The geolocation options. | ||||
|      * @returns Returns a Promise that resolves with the position of the device, or rejects with an error. | ||||
|      */ | ||||
|     getCurrentPosition(options?: GeolocationOptions): Promise<Geoposition> { | ||||
|         return new Promise((resolve, reject) => { | ||||
|             navigator.geolocation.getCurrentPosition((position) => { | ||||
|                 // Convert to unknown first because some fields are incompatible due to null values.
 | ||||
|                 resolve(<Geoposition> <unknown> position); | ||||
|             }, reject, options); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Watch the current device's position.  Clear the watch by unsubscribing from | ||||
|      * Observable changes. | ||||
|      * | ||||
|      * @param options The geolocation options. | ||||
|      * @returns Returns an Observable that notifies with the position of the device, or errors. | ||||
|      */ | ||||
|     watchPosition(options?: GeolocationOptions): Observable<Geoposition> { | ||||
|         return new Observable<Geoposition>((subscriber: Subscriber<Geoposition>): TeardownLogic => { | ||||
|             const watchId = navigator.geolocation.watchPosition( | ||||
|                 subscriber.next.bind(subscriber), | ||||
|                 subscriber.error.bind(subscriber), | ||||
|                 options, | ||||
|             ); | ||||
| 
 | ||||
|             return (): void => { | ||||
|                 navigator.geolocation.clearWatch(watchId); | ||||
|             }; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										63
									
								
								src/app/core/emulator/services/helper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/app/core/emulator/services/helper.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,63 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { File } from '@ionic-native/file/ngx'; | ||||
| 
 | ||||
| import { CoreFile } from '@services/file'; | ||||
| import { CoreInitDelegate, CoreInitHandler } from '@services/init'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { CoreLogger } from '@singletons/logger'; | ||||
| import { FileMock } from './file'; | ||||
| import { FileTransferErrorMock } from './file-transfer'; | ||||
| 
 | ||||
| /** | ||||
|  * Helper service for the emulator feature. It also acts as an init handler. | ||||
|  */ | ||||
| @Injectable() | ||||
| export class CoreEmulatorHelperProvider implements CoreInitHandler { | ||||
| 
 | ||||
|     name = 'CoreEmulator'; | ||||
|     priority = CoreInitDelegate.MAX_RECOMMENDED_PRIORITY + 500; | ||||
|     blocking = true; | ||||
| 
 | ||||
|     protected logger: CoreLogger; | ||||
| 
 | ||||
|     constructor( | ||||
|         protected file: File, | ||||
|     ) { | ||||
|         this.logger = CoreLogger.getInstance('CoreEmulatorHelper'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Load the Mocks that need it. | ||||
|      * | ||||
|      * @return Promise resolved when loaded. | ||||
|      */ | ||||
|     load(): Promise<void> { | ||||
|         const promises: Promise<unknown>[] = []; | ||||
| 
 | ||||
|         window.FileTransferError = FileTransferErrorMock; | ||||
| 
 | ||||
|         promises.push((<FileMock> this.file).load().then((basePath: string) => { | ||||
|             CoreFile.instance.setHTMLBasePath(basePath); | ||||
| 
 | ||||
|             return; | ||||
|         })); | ||||
| 
 | ||||
| 
 | ||||
|         return CoreUtils.instance.allPromises(promises); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										41
									
								
								src/app/core/emulator/services/inappbrowser.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/app/core/emulator/services/inappbrowser.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { InAppBrowser, InAppBrowserObject } from '@ionic-native/in-app-browser/ngx'; | ||||
| 
 | ||||
| /** | ||||
|  * Emulates the Cordova InAppBrowser plugin in desktop apps. | ||||
|  */ | ||||
| @Injectable() | ||||
| export class InAppBrowserMock extends InAppBrowser { | ||||
| 
 | ||||
|     /** | ||||
|      * Opens a URL in a new InAppBrowser instance, the current browser instance, or the system browser. | ||||
|      * | ||||
|      * @param url The URL to load. | ||||
|      * @param target The target in which to load the URL, an optional parameter that defaults to _self. | ||||
|      * @param options Options for the InAppBrowser. | ||||
|      * @return The new instance. | ||||
|      */ | ||||
|     create(url: string, target?: string, options: string = 'location=yes'): InAppBrowserObject { | ||||
|         if (options && typeof options !== 'string') { | ||||
|             // Convert to string.
 | ||||
|             options = Object.keys(options).map((key) => key + '=' + options[key]).join(','); | ||||
|         } | ||||
| 
 | ||||
|         return super.create(url, target, options); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										82
									
								
								src/app/core/emulator/services/network.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/app/core/emulator/services/network.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,82 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { Network } from '@ionic-native/network/ngx'; | ||||
| import { Observable, Subject, merge } from 'rxjs'; | ||||
| 
 | ||||
| /** | ||||
|  * Emulates the Cordova Network plugin in browser. | ||||
|  */ | ||||
| @Injectable() | ||||
| export class NetworkMock extends Network { | ||||
| 
 | ||||
|     type!: string; | ||||
| 
 | ||||
|     constructor() { | ||||
|         super(); | ||||
| 
 | ||||
|         // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | ||||
|         (<any> window).Connection = { | ||||
|             UNKNOWN: 'unknown', // eslint-disable-line @typescript-eslint/naming-convention
 | ||||
|             ETHERNET: 'ethernet', // eslint-disable-line @typescript-eslint/naming-convention
 | ||||
|             WIFI: 'wifi', // eslint-disable-line @typescript-eslint/naming-convention
 | ||||
|             CELL_2G: '2g', // eslint-disable-line @typescript-eslint/naming-convention
 | ||||
|             CELL_3G: '3g', // eslint-disable-line @typescript-eslint/naming-convention
 | ||||
|             CELL_4G: '4g', // eslint-disable-line @typescript-eslint/naming-convention
 | ||||
|             CELL: 'cellular', // eslint-disable-line @typescript-eslint/naming-convention
 | ||||
|             NONE: 'none', // eslint-disable-line @typescript-eslint/naming-convention
 | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns an observable to watch connection changes. | ||||
|      * | ||||
|      * @return Observable. | ||||
|      */ | ||||
|     onchange(): Observable<unknown> { | ||||
|         return merge(this.onConnect(), this.onDisconnect()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns an observable to notify when the app is connected. | ||||
|      * | ||||
|      * @return Observable. | ||||
|      */ | ||||
|     onConnect(): Observable<unknown> { | ||||
|         const observable = new Subject<unknown>(); | ||||
| 
 | ||||
|         window.addEventListener('online', (ev) => { | ||||
|             observable.next(ev); | ||||
|         }, false); | ||||
| 
 | ||||
|         return observable; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns an observable to notify when the app is disconnected. | ||||
|      * | ||||
|      * @return Observable. | ||||
|      */ | ||||
|     onDisconnect(): Observable<unknown> { | ||||
|         const observable = new Subject<unknown>(); | ||||
| 
 | ||||
|         window.addEventListener('offline', (ev) => { | ||||
|             observable.next(ev); | ||||
|         }, false); | ||||
| 
 | ||||
|         return observable; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										129
									
								
								src/app/core/emulator/services/zip.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								src/app/core/emulator/services/zip.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,129 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { File } from '@ionic-native/file/ngx'; | ||||
| import { Zip } from '@ionic-native/zip/ngx'; | ||||
| import * as JSZip from 'jszip'; | ||||
| 
 | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| 
 | ||||
| /** | ||||
|  * Emulates the Cordova Zip plugin in browser. | ||||
|  */ | ||||
| @Injectable() | ||||
| export class ZipMock extends Zip { | ||||
| 
 | ||||
|     constructor(private file: File) { | ||||
|         super(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Create a directory. It creates all the foldes in dirPath 1 by 1 to prevent errors. | ||||
|      * | ||||
|      * @param destination Destination parent folder. | ||||
|      * @param dirPath Relative path to the folder. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected async createDir(destination: string, dirPath: string): Promise<void> { | ||||
|         // Create all the folders 1 by 1 in order, otherwise it fails.
 | ||||
|         const folders = dirPath.split('/'); | ||||
| 
 | ||||
|         for (let i = 0; i < folders.length; i++) { | ||||
|             const folder = folders[i]; | ||||
| 
 | ||||
|             await this.file.createDir(destination, folder, true); | ||||
| 
 | ||||
|             // Folder created, add it to the destination path.
 | ||||
|             destination = CoreTextUtils.instance.concatenatePaths(destination, folder); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Extracts files from a ZIP archive. | ||||
|      * | ||||
|      * @param source Path to the source ZIP file. | ||||
|      * @param destination Destination folder. | ||||
|      * @param onProgress Optional callback to be called on progress update | ||||
|      * @return 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<number> { | ||||
| 
 | ||||
|         // Replace all %20 with spaces.
 | ||||
|         source = source.replace(/%20/g, ' '); | ||||
|         destination = destination.replace(/%20/g, ' '); | ||||
| 
 | ||||
|         const sourceDir = source.substring(0, source.lastIndexOf('/')); | ||||
|         const sourceName = source.substr(source.lastIndexOf('/') + 1); | ||||
|         const zip = new JSZip(); | ||||
| 
 | ||||
|         try { | ||||
|             // Read the file first.
 | ||||
|             const data = await this.file.readAsArrayBuffer(sourceDir, sourceName); | ||||
| 
 | ||||
|             // Now load the file using the JSZip library.
 | ||||
|             await zip.loadAsync(data); | ||||
| 
 | ||||
|             if (!zip.files || !Object.keys(zip.files).length) { | ||||
|                 // Nothing to extract.
 | ||||
|                 return 0; | ||||
|             } | ||||
| 
 | ||||
|             // First of all, create the directory where the files will be unzipped.
 | ||||
|             const destParent = destination.substring(0, destination.lastIndexOf('/')); | ||||
|             const destFolderName = destination.substr(destination.lastIndexOf('/') + 1); | ||||
| 
 | ||||
|             await this.file.createDir(destParent, destFolderName, true); | ||||
| 
 | ||||
|             const total = Object.keys(zip.files).length; | ||||
|             let loaded = 0; | ||||
| 
 | ||||
|             await Promise.all(Object.keys(zip.files).map(async (name) => { | ||||
|                 const file = zip.files[name]; | ||||
| 
 | ||||
|                 if (!file.dir) { | ||||
|                     // It's a file.
 | ||||
|                     const fileDir = name.substring(0, name.lastIndexOf('/')); | ||||
|                     const fileName = name.substr(name.lastIndexOf('/') + 1); | ||||
| 
 | ||||
|                     if (fileDir) { | ||||
|                         // The file is in a subfolder, create it first.
 | ||||
|                         await this.createDir(destination, fileDir); | ||||
|                     } | ||||
| 
 | ||||
|                     // Read the file contents as a Blob.
 | ||||
|                     const fileData = await file.async('blob'); | ||||
| 
 | ||||
|                     // File read and parent folder created, now write the file.
 | ||||
|                     const parentFolder = CoreTextUtils.instance.concatenatePaths(destination, fileDir); | ||||
| 
 | ||||
|                     await this.file.writeFile(parentFolder, fileName, fileData, { replace: true }); | ||||
|                 } else { | ||||
|                     // It's a folder, create it if it doesn't exist.
 | ||||
|                     await this.createDir(destination, name); | ||||
|                 } | ||||
| 
 | ||||
|                 // File unzipped, call the progress.
 | ||||
|                 loaded++; | ||||
|                 onProgress && onProgress({ loaded: loaded, total: total }); | ||||
|             })); | ||||
| 
 | ||||
|             return 0; | ||||
|         } catch (error) { | ||||
|             // Error.
 | ||||
|             return -1; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user