MOBILE-3839 browser: Support SecureStorage in browser
This commit is contained in:
		
							parent
							
								
									c4ce1edfe0
								
							
						
					
					
						commit
						7e7aae4853
					
				
							
								
								
									
										6
									
								
								cordova-plugin-moodleapp/types/index.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								cordova-plugin-moodleapp/types/index.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -12,12 +12,12 @@ | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { SecureStorage } from '../src/ts/plugins/SecureStorage'; | ||||
| import { SecureStorage as SecureStorageImpl } from '../src/ts/plugins/SecureStorage'; | ||||
| 
 | ||||
| declare global { | ||||
| 
 | ||||
|     interface MoodleAppPlugins { | ||||
|         secureStorage: SecureStorage; | ||||
|         secureStorage: SecureStorageImpl; | ||||
|     } | ||||
| 
 | ||||
|     interface Cordova { | ||||
| @ -25,3 +25,5 @@ declare global { | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export type SecureStorage = InstanceType<typeof SecureStorageImpl>; | ||||
|  | ||||
							
								
								
									
										134
									
								
								src/core/features/emulator/classes/SecureStorage.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								src/core/features/emulator/classes/SecureStorage.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,134 @@ | ||||
| // (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 { SecureStorage } from 'cordova-plugin-moodleapp'; | ||||
| 
 | ||||
| /** | ||||
|  * Mock for SecureStorage plugin. It will store the data without being encrypted. | ||||
|  */ | ||||
| export class SecureStorageMock implements SecureStorage { | ||||
| 
 | ||||
|     /** | ||||
|      * Get one or more values. | ||||
|      * | ||||
|      * @param names Names of the values to get. | ||||
|      * @param collection The collection where the values are stored. | ||||
|      * @returns Object with name -> value. If a name isn't found it won't be included in the result. | ||||
|      */ | ||||
|     async get(names: string | string[], collection: string): Promise<Record<string, string>> { | ||||
|         if (typeof names === 'string') { | ||||
|             names = [names]; | ||||
|         } | ||||
| 
 | ||||
|         const result: Record<string, string> = {}; | ||||
| 
 | ||||
|         for (let i = 0; i < names.length; i++) { | ||||
|             const name = names[i]; | ||||
|             if (!name) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             const storedValue = localStorage.getItem(this.getPrefixedName(name, collection)); | ||||
|             if (storedValue !== null) { | ||||
|                 result[name] = storedValue; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the prefix to add to a name, including the collection. | ||||
|      * | ||||
|      * @param collection Collection name. | ||||
|      * @returns Prefix. | ||||
|      */ | ||||
|     private getCollectionPrefix(collection: string): string { | ||||
|         return `SecureStorage_${collection}_`; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the full name to retrieve, store or delete an item. | ||||
|      * | ||||
|      * @param name Name inside collection. | ||||
|      * @param collection Collection name. | ||||
|      * @returns Full name. | ||||
|      */ | ||||
|     private getPrefixedName(name: string, collection: string): string { | ||||
|         return this.getCollectionPrefix(collection) + name; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set one or more values. | ||||
|      * | ||||
|      * @param data Object with values to store, in format name -> value. Null or undefined valid values will be ignored. | ||||
|      * @param collection The collection where to store the values. | ||||
|      */ | ||||
|     async store(data: Record<string, string>, collection: string): Promise<void> { | ||||
|         for (const name in data) { | ||||
|             const value = data[name]; | ||||
|             if (value === undefined || value === null) { | ||||
|                 delete data[name]; | ||||
|             } else if (typeof value !== 'string') { | ||||
|                 throw new Error(`SecureStorage: Invalid value for ${name}. Expected string, received ${typeof value}`); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         for (const name in data) { | ||||
|             if (!name) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             const value = data[name]; | ||||
|             localStorage.setItem(this.getPrefixedName(name, collection), value); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Delete one or more values. | ||||
|      * | ||||
|      * @param names Names to delete. | ||||
|      * @param collection The collection where to delete the values. | ||||
|      */ | ||||
|     async delete(names: string | string[], collection: string): Promise<void> { | ||||
|         if (typeof names === 'string') { | ||||
|             names = [names]; | ||||
|         } | ||||
| 
 | ||||
|         for (let i = 0; i < names.length; i++) { | ||||
|             const name = names[i]; | ||||
|             if (!name) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             localStorage.removeItem(this.getPrefixedName(name, collection)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Delete all values for a certain collection. | ||||
|      * | ||||
|      * @param collection The collection to delete. | ||||
|      */ | ||||
|     async deleteCollection(collection: string): Promise<void> { | ||||
|         const names = Object.keys(localStorage); | ||||
|         for (let i = 0; i < names.length; i++) { | ||||
|             const name = names[i]; | ||||
|             if (name.startsWith(this.getCollectionPrefix(collection))) { | ||||
|                 localStorage.removeItem(name); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -42,6 +42,8 @@ import { MediaCaptureMock } from './services/media-capture'; | ||||
| import { ZipMock } from './services/zip'; | ||||
| import { CorePlatform } from '@services/platform'; | ||||
| import { CoreLocalNotifications } from '@services/local-notifications'; | ||||
| import { CoreNative } from '@features/native/services/native'; | ||||
| import { SecureStorageMock } from '@features/emulator/classes/SecureStorage'; | ||||
| 
 | ||||
| /** | ||||
|  * This module handles the emulation of Cordova plugins in browser and desktop. | ||||
| @ -101,12 +103,14 @@ import { CoreLocalNotifications } from '@services/local-notifications'; | ||||
|         }, | ||||
|         { | ||||
|             provide: APP_INITIALIZER, | ||||
|             useFactory: () => () => { | ||||
|             useValue: async () => { | ||||
|                 if (CorePlatform.is('cordova')) { | ||||
|                     return; | ||||
|                 } | ||||
| 
 | ||||
|                 return CoreEmulatorHelper.load(); | ||||
|                 CoreNative.registerBrowserMock('secureStorage', new SecureStorageMock()); | ||||
| 
 | ||||
|                 await CoreEmulatorHelper.load(); | ||||
|             }, | ||||
|             multi: true, | ||||
|         }, | ||||
|  | ||||
| @ -24,6 +24,7 @@ import { AsyncInstance, asyncInstance } from '@/core/utils/async-instance'; | ||||
| export class CoreNativeService { | ||||
| 
 | ||||
|     private plugins: Partial<Record<keyof MoodleAppPlugins, AsyncInstance>> = {}; | ||||
|     private mocks: Partial<Record<keyof MoodleAppPlugins, MoodleAppPlugins[keyof MoodleAppPlugins]>> = {}; | ||||
| 
 | ||||
|     /** | ||||
|      * Get a native plugin instance. | ||||
| @ -31,22 +32,33 @@ export class CoreNativeService { | ||||
|      * @param plugin Plugin name. | ||||
|      * @returns Plugin instance. | ||||
|      */ | ||||
|     plugin<Plugin extends keyof MoodleAppPlugins>(plugin: Plugin): AsyncInstance<MoodleAppPlugins[Plugin]> | null { | ||||
|         if (!CorePlatform.isMobile()) { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|     plugin<Plugin extends keyof MoodleAppPlugins>(plugin: Plugin): AsyncInstance<MoodleAppPlugins[Plugin]> { | ||||
|         if (!(plugin in this.plugins)) { | ||||
|             this.plugins[plugin] = asyncInstance(async () => { | ||||
|                 await CorePlatform.ready(); | ||||
| 
 | ||||
|                 return window.cordova?.MoodleApp?.[plugin]; | ||||
|                 const instance = CorePlatform.isMobile() ? window.cordova?.MoodleApp?.[plugin] : this.mocks[plugin]; | ||||
|                 if (!instance) { | ||||
|                     throw new Error(`Plugin ${plugin} not found.`); | ||||
|                 } | ||||
| 
 | ||||
|                 return instance; | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         return this.plugins[plugin] as AsyncInstance<MoodleAppPlugins[Plugin]>; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Register a mock to use in browser instead of the native plugin implementation. | ||||
|      * | ||||
|      * @param plugin Plugin name. | ||||
|      * @param instance Instance to use. | ||||
|      */ | ||||
|     registerBrowserMock<Plugin extends keyof MoodleAppPlugins>(plugin: Plugin, instance: MoodleAppPlugins[Plugin]): void { | ||||
|         this.mocks[plugin] = instance; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export const CoreNative = makeSingleton(CoreNativeService); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user