commit
						e647e535fa
					
				| @ -67,11 +67,12 @@ | ||||
|         <resource-file src="resources/android/icon/drawable-hdpi-smallicon.png" target="app/src/main/res/mipmap-hdpi/smallicon.png" /> | ||||
|         <resource-file src="resources/android/icon/drawable-xhdpi-smallicon.png" target="app/src/main/res/mipmap-xhdpi/smallicon.png" /> | ||||
|         <resource-file src="resources/android/xml/network_security_config.xml" target="app/src/main/res/xml/network_security_config.xml" /> | ||||
|         <resource-file src="resources/android/xml/backup_rules.xml" target="app/src/main/res/xml/backup_rules.xml" /> | ||||
|         <edit-config file="AndroidManifest.xml" mode="merge" target="/manifest/application/activity[@android:name='MainActivity']"> | ||||
|             <activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|screenLayout|smallestScreenSize" android:exported="true" /> | ||||
|         </edit-config> | ||||
|         <edit-config file="AndroidManifest.xml" mode="merge" target="/manifest/application"> | ||||
|             <application android:largeHeap="true" android:networkSecurityConfig="@xml/network_security_config" /> | ||||
|             <application android:allowBackup="true" android:dataExtractionRules="@xml/backup_rules" android:largeHeap="true" android:networkSecurityConfig="@xml/network_security_config" /> | ||||
|         </edit-config> | ||||
|         <config-file parent="/manifest/application" target="AndroidManifest.xml"> | ||||
|             <meta-data android:name="firebase_analytics_collection_deactivated" android:value="true" /> | ||||
|  | ||||
| @ -7,10 +7,20 @@ | ||||
|     </js-module> | ||||
|     <platform name="android"> | ||||
|         <config-file target="res/xml/config.xml" parent="/*"> | ||||
|             <feature name="SystemUI"> | ||||
|                 <param name="android-package" value="com.moodle.moodlemobile.SystemUI"/> | ||||
|             <feature name="SecureStorage"> | ||||
|                 <param name="android-package" value="com.moodle.moodlemobile.SecureStorage"/> | ||||
|             </feature> | ||||
|         </config-file> | ||||
|         <source-file src="src/android/SystemUI.java" target-dir="src/com/moodle/moodlemobile" /> | ||||
|         <source-file src="src/android/SecureStorage.java" target-dir="src/com/moodle/moodlemobile" /> | ||||
|     </platform> | ||||
|     <platform name="ios"> | ||||
|         <config-file target="config.xml" parent="/*"> | ||||
|             <feature name="SecureStorage"> | ||||
|                 <param name="ios-package" value="SecureStorage" /> | ||||
|             </feature> | ||||
|         </config-file> | ||||
| 
 | ||||
|         <header-file src="src/ios/SecureStorage.h" /> | ||||
|         <source-file src="src/ios/SecureStorage.m" /> | ||||
|     </platform> | ||||
| </plugin> | ||||
|  | ||||
| @ -22,10 +22,14 @@ const { resolve } = require('path'); | ||||
| 
 | ||||
| const bundle = readFileSync(resolve(__dirname, '../www/index.js')).toString(); | ||||
| const template = readFileSync(resolve(__dirname, './templates/cordova-plugin.js')).toString(); | ||||
| const pluginsPath = resolve(__dirname, '../../plugins/'); | ||||
| const platformsPath = resolve(__dirname, '../../platforms/'); | ||||
| const filePaths = [ | ||||
|     resolve(pluginsPath, 'cordova-plugin-moodleapp/www/index.js'), | ||||
|     resolve(platformsPath, 'android/app/src/main/assets/www/plugins/cordova-plugin-moodleapp/www/index.js'), | ||||
|     resolve(platformsPath, 'android/platform_www/plugins/cordova-plugin-moodleapp/www/index.js'), | ||||
|     resolve(platformsPath, 'ios/platform_www/plugins/cordova-plugin-moodleapp/www/index.js'), | ||||
|     resolve(platformsPath, 'ios/www/plugins/cordova-plugin-moodleapp/www/index.js'), | ||||
| ]; | ||||
| const pluginIndex = template | ||||
|     .replace('[[PLUGIN_NAME]]', 'cordova-plugin-moodleapp.moodleapp') | ||||
|  | ||||
							
								
								
									
										178
									
								
								cordova-plugin-moodleapp/src/android/SecureStorage.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								cordova-plugin-moodleapp/src/android/SecureStorage.java
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,178 @@ | ||||
| // (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. | ||||
| 
 | ||||
| package com.moodle.moodlemobile; | ||||
| 
 | ||||
| import android.os.Build; | ||||
| import android.util.Log; | ||||
| import android.content.Context; | ||||
| import android.content.SharedPreferences; | ||||
| import java.security.GeneralSecurityException; | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| import org.json.JSONArray; | ||||
| import org.json.JSONObject; | ||||
| import org.json.JSONException; | ||||
| import org.apache.cordova.CordovaPlugin; | ||||
| import org.apache.cordova.CallbackContext; | ||||
| import org.apache.cordova.PluginResult; | ||||
| 
 | ||||
| import com.adobe.phonegap.push.EncryptionHandler; | ||||
| 
 | ||||
| public class SecureStorage extends CordovaPlugin { | ||||
| 
 | ||||
|     private static final String TAG = "SecureStorage"; | ||||
|     private static final String SHARED_PREFS_NAME = "moodlemobile_shared_prefs"; | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean execute(String action, JSONArray args, CallbackContext callbackContext) { | ||||
|         try { | ||||
|             switch (action) { | ||||
|                 case "get": | ||||
|                     callbackContext.success(this.get(args.getJSONArray(0), args.getString(1))); | ||||
| 
 | ||||
|                     return true; | ||||
|                 case "store": | ||||
|                     this.store(args.getJSONObject(0), args.getString(1)); | ||||
|                     callbackContext.success(); | ||||
| 
 | ||||
|                     return true; | ||||
|                 case "delete": | ||||
|                     this.delete(args.getJSONArray(0), args.getString(1)); | ||||
|                     callbackContext.success(); | ||||
| 
 | ||||
|                     return true; | ||||
|                 case "deleteCollection": | ||||
|                     this.deleteCollection(args.getString(0)); | ||||
|                     callbackContext.success(); | ||||
| 
 | ||||
|                     return true; | ||||
|             } | ||||
|         } catch (Throwable e) { | ||||
|             Log.e(TAG, "Failed executing action: " + action, e); | ||||
|             callbackContext.error(e.getMessage()); | ||||
|             callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR)); | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get several values from secure storage. | ||||
|      * | ||||
|      * @param names List of names to get. | ||||
|      * @param collection The collection where the values are stored. | ||||
|      * @return Values for each name. | ||||
|      */ | ||||
|     private JSONObject get(JSONArray names, String collection) throws GeneralSecurityException, IOException, JSONException { | ||||
|         Context context = this.cordova.getActivity().getApplicationContext(); | ||||
|         SharedPreferences sharedPreferences = getSharedPreferences(collection); | ||||
|         JSONObject result = new JSONObject(); | ||||
| 
 | ||||
|         Log.d(TAG, "Get values with names " + names.toString()); | ||||
| 
 | ||||
|         for(int i = 0; i < names.length(); i++) { | ||||
|             String name = names.optString(i); | ||||
| 
 | ||||
|             if (name == null || name.isEmpty()) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             String rawValue = sharedPreferences.getString(name, null); | ||||
|             if (rawValue == null) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             result.put(name, EncryptionHandler.Companion.decrypt(context, rawValue)); | ||||
|         } | ||||
| 
 | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Store data in secure storage. | ||||
|      * | ||||
|      * @param data Data to store, using a name -> value format. | ||||
|      * @param collection The collection where to store the values. | ||||
|      */ | ||||
|     private void store(JSONObject data, String collection) throws GeneralSecurityException, IOException, JSONException { | ||||
|         Context context = this.cordova.getActivity().getApplicationContext(); | ||||
|         SharedPreferences sharedPreferences = getSharedPreferences(collection); | ||||
|         SharedPreferences.Editor editor = sharedPreferences.edit(); | ||||
|         JSONArray names = data.names(); | ||||
| 
 | ||||
|         Log.d(TAG, "Store values with names " + names.toString()); | ||||
| 
 | ||||
|         for(int i = 0; i < names.length(); i++) { | ||||
|             String name = names.optString(i); | ||||
| 
 | ||||
|             if (name != null && !name.isEmpty()) { | ||||
|                 editor.putString(name, EncryptionHandler.Companion.encrypt(context, data.getString(name))); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         editor.apply(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Delete some values from secure storage. | ||||
|      * | ||||
|      * @param names Names to delete. | ||||
|      * @param collection The collection where to delete the values. | ||||
|      */ | ||||
|     private void delete(JSONArray names, String collection) throws GeneralSecurityException, IOException { | ||||
|         Log.d(TAG, "Delete value with names " + names.toString()); | ||||
| 
 | ||||
|         SharedPreferences sharedPreferences = getSharedPreferences(collection); | ||||
|         SharedPreferences.Editor editor = sharedPreferences.edit(); | ||||
| 
 | ||||
|         for(int i = 0; i < names.length(); i++) { | ||||
|             String name = names.optString(i); | ||||
| 
 | ||||
|             if (name != null && !name.isEmpty()) { | ||||
|                 editor.remove(name); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         editor.apply(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Delete all values from a collection. | ||||
|      * | ||||
|      * @param collection The collection to delete. | ||||
|      */ | ||||
|     private void deleteCollection(String collection) throws GeneralSecurityException, IOException { | ||||
|         Log.d(TAG, "Delete all values in collection " + collection); | ||||
| 
 | ||||
|         SharedPreferences sharedPreferences = getSharedPreferences(collection); | ||||
|         SharedPreferences.Editor editor = sharedPreferences.edit(); | ||||
|         editor.clear(); | ||||
|         editor.apply(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get shared preferences instance. | ||||
|      * | ||||
|      * @param collection The collection to use. | ||||
|      * @return Shared preferences instance. | ||||
|      */ | ||||
|     private SharedPreferences getSharedPreferences(String collection) { | ||||
|         return this.cordova.getActivity().getApplicationContext().getSharedPreferences( | ||||
|             SHARED_PREFS_NAME + "_" + collection, | ||||
|             Context.MODE_PRIVATE | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,45 +0,0 @@ | ||||
| // (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. | ||||
| 
 | ||||
| package com.moodle.moodlemobile; | ||||
| 
 | ||||
| import android.graphics.Color; | ||||
| import android.os.Build; | ||||
| import android.util.Log; | ||||
| import android.view.Window; | ||||
| 
 | ||||
| import org.apache.cordova.CordovaPlugin; | ||||
| import org.apache.cordova.CallbackContext; | ||||
| 
 | ||||
| import org.json.JSONArray; | ||||
| 
 | ||||
| public class SystemUI extends CordovaPlugin { | ||||
| 
 | ||||
|     private static final String TAG = "SystemUI"; | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean execute(String action, JSONArray args, CallbackContext callbackContext) { | ||||
|         try { | ||||
|             switch (action) { | ||||
|                 // No actions yet. | ||||
|             } | ||||
|         } catch (Throwable e) { | ||||
|             Log.e(TAG, "Failed executing action: " + action, e); | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										11
									
								
								cordova-plugin-moodleapp/src/ios/SecureStorage.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								cordova-plugin-moodleapp/src/ios/SecureStorage.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| #import <Foundation/Foundation.h> | ||||
| #import <Cordova/CDVPlugin.h> | ||||
| 
 | ||||
| @interface SecureStorage : CDVPlugin {} | ||||
| 
 | ||||
| - (void)get:(CDVInvokedUrlCommand*)command; | ||||
| - (void)store:(CDVInvokedUrlCommand*)command; | ||||
| - (void)delete:(CDVInvokedUrlCommand*)command; | ||||
| - (void)deleteCollection:(CDVInvokedUrlCommand*)command; | ||||
| 
 | ||||
| @end | ||||
							
								
								
									
										218
									
								
								cordova-plugin-moodleapp/src/ios/SecureStorage.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										218
									
								
								cordova-plugin-moodleapp/src/ios/SecureStorage.m
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,218 @@ | ||||
| #import <Foundation/Foundation.h> | ||||
| #import <Cordova/CDVPlugin.h> | ||||
| #import <Cordova/CDVPluginResult.h> | ||||
| #import "SecureStorage.h" | ||||
| 
 | ||||
| @implementation SecureStorage | ||||
| 
 | ||||
| - (void)get:(CDVInvokedUrlCommand*)command { | ||||
|     NSArray* names = [command argumentAtIndex:0]; | ||||
|     NSString* collection = [command argumentAtIndex:1 withDefault:@""]; | ||||
|     NSMutableDictionary* result = [NSMutableDictionary new]; | ||||
| 
 | ||||
|     NSLog(@"SecureStorage: Get values with names %@ in collection %@", names, collection); | ||||
| 
 | ||||
|     for (NSString* name in names) { | ||||
|         NSString* value = [self getValue:name inCollection:collection]; | ||||
|         if (value != nil) { | ||||
|             result[name] = value; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result]; | ||||
|     [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; | ||||
| } | ||||
| 
 | ||||
| - (NSString *)getValue:(NSString*)name inCollection:(NSString*)collection { | ||||
|     if ([name length] == 0) { | ||||
|         return nil; | ||||
|     } | ||||
| 
 | ||||
|     NSDictionary* query = @{ | ||||
|         (id)kSecClass: (id)kSecClassGenericPassword, | ||||
|         (id)kSecAttrAccount: name, | ||||
|         (id)kSecAttrService: collection, | ||||
|         (id)kSecReturnData: @YES, | ||||
|         (id)kSecMatchLimit: (id)kSecMatchLimitOne | ||||
|     }; | ||||
|     NSData* storedData = NULL; | ||||
| 
 | ||||
|     OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, (void *) &storedData); | ||||
| 
 | ||||
|     if (status == errSecSuccess) { | ||||
|         return [[NSString alloc] initWithData:storedData encoding:NSUTF8StringEncoding]; | ||||
|     } else if (status != errSecItemNotFound) { | ||||
|         NSLog(@"Error getting value for %@ in collection %@. Status: %d", name, collection, (int) status); | ||||
|     } | ||||
| 
 | ||||
|     return nil; | ||||
| } | ||||
| 
 | ||||
| - (void)store:(CDVInvokedUrlCommand*)command { | ||||
| 
 | ||||
|     NSDictionary* data = [command argumentAtIndex:0]; | ||||
|     NSString* collection = [command argumentAtIndex:1 withDefault:@""]; | ||||
|     NSArray* names = [data allKeys]; | ||||
|     BOOL error = false; | ||||
| 
 | ||||
|     // Variables to be able to rollback changes if something fails. | ||||
|     NSMutableArray* insertedNames = [NSMutableArray new]; | ||||
|     NSMutableDictionary* previousValues = [NSMutableDictionary new]; | ||||
| 
 | ||||
|     NSLog(@"SecureStorage: Store values with names %@ in collection %@", names, collection); | ||||
| 
 | ||||
|     for (NSString* name in data) { | ||||
|         OSStatus status; | ||||
|         NSString* storedValue = [self getValue:name inCollection:collection]; | ||||
| 
 | ||||
|         if (storedValue != nil) { | ||||
|             status = [self updateName:name withValue:data[name] inCollection: collection]; | ||||
|         } else { | ||||
|             status = [self addName:name withValue:data[name] inCollection: collection]; | ||||
|         } | ||||
| 
 | ||||
|         if (status != errSecSuccess) { | ||||
|             NSLog(@"Error storing value for %@ in collection %@. Status: %d", name, collection, (int) status); | ||||
|             error = true; | ||||
| 
 | ||||
|             // Rollback. | ||||
|             for (NSString *name in insertedNames) { | ||||
|                 [self deleteName:name fromCollection:collection]; | ||||
|             } | ||||
|             for(NSString *name in previousValues) { | ||||
|                 [self updateName:name withValue:previousValues[name] inCollection: collection]; | ||||
|             } | ||||
| 
 | ||||
|             break; | ||||
|         } else if (storedValue != nil) { | ||||
|             previousValues[name] = storedValue; | ||||
|         } else { | ||||
|             [insertedNames addObject:name]; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     CDVPluginResult* pluginResult; | ||||
|     if (error) { | ||||
|         pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR | ||||
|             messageAsString:@"Error storing one or more values in secure storage."]; | ||||
|     } else { | ||||
|         pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; | ||||
|     } | ||||
| 
 | ||||
|     [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; | ||||
| } | ||||
| 
 | ||||
| - (OSStatus)addName:(NSString*)name withValue:(NSString*)value inCollection:(NSString*)collection { | ||||
|     NSData* newValue = [value dataUsingEncoding:NSUTF8StringEncoding]; | ||||
|     NSMutableDictionary* query = [NSMutableDictionary new]; | ||||
|     query[(id)kSecClass] = (id)kSecClassGenericPassword; | ||||
|     query[(id)kSecAttrAccount] = name; | ||||
|     query[(id)kSecAttrService] = collection; | ||||
|     query[(id)kSecAttrAccessible] = (id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly; | ||||
|     query[(id)kSecValueData] = newValue; | ||||
| 
 | ||||
|     return SecItemAdd((CFDictionaryRef)query, nil); | ||||
| } | ||||
| 
 | ||||
| - (OSStatus)updateName:(NSString*)name withValue:(NSString*)value inCollection:(NSString*)collection { | ||||
|     NSData* newValue = [value dataUsingEncoding:NSUTF8StringEncoding]; | ||||
|     NSMutableDictionary* query = [NSMutableDictionary new]; | ||||
|     query[(id)kSecClass] = (id)kSecClassGenericPassword; | ||||
|     query[(id)kSecAttrAccount] = name; | ||||
|     query[(id)kSecAttrService] = collection; | ||||
|     query[(id)kSecAttrAccessible] = (id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly; | ||||
| 
 | ||||
|     return SecItemUpdate((CFDictionaryRef)query, (CFDictionaryRef)@{ | ||||
|         (id)kSecValueData: newValue | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| - (void)delete:(CDVInvokedUrlCommand*)command { | ||||
|     NSArray* names = [command argumentAtIndex:0]; | ||||
|     NSString* collection = [command argumentAtIndex:1 withDefault:@""]; | ||||
|     BOOL error = false; | ||||
| 
 | ||||
|     // Variable to be able to rollback changes if something fails. | ||||
|     NSMutableDictionary* deletedValues = [NSMutableDictionary new]; | ||||
| 
 | ||||
|     NSLog(@"SecureStorage: Delete values with names %@ in collection %@", names, collection); | ||||
| 
 | ||||
|     for (NSString* name in names) { | ||||
|         if ([name length] == 0) { | ||||
|             continue; | ||||
|         } | ||||
| 
 | ||||
|         NSString* storedValue = [self getValue:name inCollection:collection]; | ||||
|         if (storedValue == nil) { | ||||
|             continue; | ||||
|         } | ||||
| 
 | ||||
|         OSStatus status = [self deleteName:name fromCollection:collection]; | ||||
| 
 | ||||
|         if (status != errSecSuccess && status != errSecItemNotFound) { | ||||
|             NSLog(@"Error deleting entry with name %@ in collection %@. Status: %d", name, collection, (int) status); | ||||
|             error = true; | ||||
| 
 | ||||
|             // Rollback. | ||||
|             for (NSString *name in deletedValues) { | ||||
|                 [self addName:name withValue:deletedValues[name] inCollection:collection]; | ||||
|             } | ||||
|         } else { | ||||
|             deletedValues[name] = storedValue; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     CDVPluginResult* pluginResult; | ||||
|     if (error) { | ||||
|         pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR | ||||
|             messageAsString:@"Error deleting one or more values from secure storage."]; | ||||
|     } else { | ||||
|         pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; | ||||
|     } | ||||
| 
 | ||||
|     [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; | ||||
| } | ||||
| 
 | ||||
| - (OSStatus)deleteName:(NSString*)name fromCollection:(NSString*)collection { | ||||
|     NSDictionary* query = @{ | ||||
|         (id)kSecClass: (id)kSecClassGenericPassword, | ||||
|         (id)kSecAttrAccount: name, | ||||
|         (id)kSecAttrService: collection, | ||||
|     }; | ||||
| 
 | ||||
|     return SecItemDelete((CFDictionaryRef)query); | ||||
| } | ||||
| 
 | ||||
| - (void)deleteCollection:(CDVInvokedUrlCommand*)command { | ||||
|     NSString* collection = [command argumentAtIndex:0 withDefault:@""]; | ||||
| 
 | ||||
|     if ([collection length] == 0) { | ||||
|         NSLog(@"SecureStorage: Collection cannot be empty in deleteCollection"); | ||||
|         CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; | ||||
|         [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; | ||||
| 
 | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     NSLog(@"SecureStorage: Delete all values in collection %@", collection); | ||||
| 
 | ||||
|     NSDictionary* query = @{ | ||||
|         (id)kSecClass: (id)kSecClassGenericPassword, | ||||
|         (id)kSecAttrService: collection, | ||||
|     }; | ||||
| 
 | ||||
|     OSStatus status = SecItemDelete((CFDictionaryRef)query); | ||||
| 
 | ||||
|     CDVPluginResult* pluginResult; | ||||
|     if (status == errSecSuccess || status == errSecItemNotFound) { | ||||
|         pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; | ||||
|     } else { | ||||
|         NSLog(@"Error deleting all values in collection %@. Status: %d", collection, (int) status); | ||||
|         pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_IO_EXCEPTION | ||||
|             messageAsString:@"Error deleting values from secure storage."]; | ||||
|     } | ||||
| 
 | ||||
|     [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; | ||||
| } | ||||
| 
 | ||||
| @end | ||||
| @ -12,10 +12,10 @@ | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { SystemUI } from './plugins/SystemUI'; | ||||
| import { SecureStorage } from './plugins/SecureStorage'; | ||||
| 
 | ||||
| const api: MoodleAppPlugins = { | ||||
|     systemUI: new SystemUI(), | ||||
|     secureStorage: new SecureStorage(), | ||||
| }; | ||||
| 
 | ||||
| // This is necessary to work around the default transpilation behavior,
 | ||||
|  | ||||
							
								
								
									
										85
									
								
								cordova-plugin-moodleapp/src/ts/plugins/SecureStorage.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								cordova-plugin-moodleapp/src/ts/plugins/SecureStorage.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,85 @@ | ||||
| // (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.
 | ||||
| 
 | ||||
| /** | ||||
|  * Allows retrieving and storing items in a secure storage. | ||||
|  */ | ||||
| export class 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]; | ||||
|         } | ||||
| 
 | ||||
|         return new Promise((resolve, reject) => { | ||||
|             cordova.exec(resolve, reject, 'SecureStorage', 'get', [names, collection]); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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}`); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         await new Promise((resolve, reject) => { | ||||
|             cordova.exec(resolve, reject, 'SecureStorage', 'store', [data, collection]); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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]; | ||||
|         } | ||||
| 
 | ||||
|         await new Promise((resolve, reject) => { | ||||
|             cordova.exec(resolve, reject, 'SecureStorage', 'delete', [names, collection]); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Delete all values for a certain collection. | ||||
|      * | ||||
|      * @param collection The collection to delete. | ||||
|      */ | ||||
|     async deleteCollection(collection: string): Promise<void> { | ||||
|         await new Promise((resolve, reject) => { | ||||
|             cordova.exec(resolve, reject, 'SecureStorage', 'deleteCollection', [collection]); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,31 +0,0 @@ | ||||
| // (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.
 | ||||
| 
 | ||||
| /** | ||||
|  * Manages system UI settings. | ||||
|  */ | ||||
| export class SystemUI { | ||||
| 
 | ||||
|     /** | ||||
|      * Set navigation bar color. | ||||
|      * | ||||
|      * @param color Color. | ||||
|      */ | ||||
|     async setNavigationBarColor(color: string): Promise<void> { | ||||
|         await new Promise((resolve, reject) => { | ||||
|             cordova.exec(resolve, reject, 'SystemUI', 'setNavigationBarColor', [color]); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										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 { SystemUI } from '../src/ts/plugins/SystemUI'; | ||||
| import { SecureStorage as SecureStorageImpl } from '../src/ts/plugins/SecureStorage'; | ||||
| 
 | ||||
| declare global { | ||||
| 
 | ||||
|     interface MoodleAppPlugins { | ||||
|         systemUI: SystemUI; | ||||
|         secureStorage: SecureStorageImpl; | ||||
|     } | ||||
| 
 | ||||
|     interface Cordova { | ||||
| @ -25,3 +25,5 @@ declare global { | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export type SecureStorage = InstanceType<typeof SecureStorageImpl>; | ||||
|  | ||||
							
								
								
									
										607
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										607
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -4835,9 +4835,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "@moodlehq/phonegap-plugin-push": { | ||||
|       "version": "4.0.0-moodle.6", | ||||
|       "resolved": "https://registry.npmjs.org/@moodlehq/phonegap-plugin-push/-/phonegap-plugin-push-4.0.0-moodle.6.tgz", | ||||
|       "integrity": "sha512-0ddoef5tXsCRfv8MKNfsUZdO+63GK+s8dYON8E8hFhO0RxEp1mhqeTj8vnxBvjyjl2IlVGUXiOPwshgabxoBRA==" | ||||
|       "version": "4.0.0-moodle.7", | ||||
|       "resolved": "https://registry.npmjs.org/@moodlehq/phonegap-plugin-push/-/phonegap-plugin-push-4.0.0-moodle.7.tgz", | ||||
|       "integrity": "sha512-Suzk1v4oLogcU/Xpm5Yl1KkzPwamGnzxculkYNIrqaAs6IZ6cSyZvIy0JoM98zOjGJBjKCSEYAAmtMZXLMGhjg==" | ||||
|     }, | ||||
|     "@mrmlnc/readdir-enhanced": { | ||||
|       "version": "2.2.1", | ||||
| @ -8617,6 +8617,7 @@ | ||||
|       "version": "3.1.3", | ||||
|       "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", | ||||
|       "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "normalize-path": "^3.0.0", | ||||
|         "picomatch": "^2.0.4" | ||||
| @ -9800,7 +9801,8 @@ | ||||
|     "binary-extensions": { | ||||
|       "version": "2.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", | ||||
|       "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" | ||||
|       "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "bindings": { | ||||
|       "version": "1.5.0", | ||||
| @ -10963,6 +10965,7 @@ | ||||
|       "version": "3.5.3", | ||||
|       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", | ||||
|       "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "anymatch": "~3.1.2", | ||||
|         "braces": "~3.0.2", | ||||
| @ -10978,6 +10981,7 @@ | ||||
|           "version": "2.3.2", | ||||
|           "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", | ||||
|           "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", | ||||
|           "dev": true, | ||||
|           "optional": true | ||||
|         } | ||||
|       } | ||||
| @ -11144,6 +11148,7 @@ | ||||
|       "version": "5.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", | ||||
|       "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "string-width": "^3.1.0", | ||||
|         "strip-ansi": "^5.2.0", | ||||
| @ -11153,12 +11158,14 @@ | ||||
|         "ansi-regex": { | ||||
|           "version": "4.1.1", | ||||
|           "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", | ||||
|           "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" | ||||
|           "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", | ||||
|           "dev": true | ||||
|         }, | ||||
|         "ansi-styles": { | ||||
|           "version": "3.2.1", | ||||
|           "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", | ||||
|           "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", | ||||
|           "dev": true, | ||||
|           "requires": { | ||||
|             "color-convert": "^1.9.0" | ||||
|           } | ||||
| @ -11166,17 +11173,20 @@ | ||||
|         "emoji-regex": { | ||||
|           "version": "7.0.3", | ||||
|           "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", | ||||
|           "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" | ||||
|           "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", | ||||
|           "dev": true | ||||
|         }, | ||||
|         "is-fullwidth-code-point": { | ||||
|           "version": "2.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", | ||||
|           "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==" | ||||
|           "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", | ||||
|           "dev": true | ||||
|         }, | ||||
|         "string-width": { | ||||
|           "version": "3.1.0", | ||||
|           "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", | ||||
|           "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", | ||||
|           "dev": true, | ||||
|           "requires": { | ||||
|             "emoji-regex": "^7.0.1", | ||||
|             "is-fullwidth-code-point": "^2.0.0", | ||||
| @ -11187,6 +11197,7 @@ | ||||
|           "version": "5.2.0", | ||||
|           "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", | ||||
|           "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", | ||||
|           "dev": true, | ||||
|           "requires": { | ||||
|             "ansi-regex": "^4.1.0" | ||||
|           } | ||||
| @ -11195,6 +11206,7 @@ | ||||
|           "version": "5.1.0", | ||||
|           "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", | ||||
|           "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", | ||||
|           "dev": true, | ||||
|           "requires": { | ||||
|             "ansi-styles": "^3.2.0", | ||||
|             "string-width": "^3.0.0", | ||||
| @ -12182,6 +12194,115 @@ | ||||
|       "version": "file:cordova-plugin-moodleapp", | ||||
|       "dev": true, | ||||
|       "dependencies": { | ||||
|         "@babel/runtime": { | ||||
|           "version": "7.22.6", | ||||
|           "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", | ||||
|           "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", | ||||
|           "requires": { | ||||
|             "regenerator-runtime": "^0.13.11" | ||||
|           } | ||||
|         }, | ||||
|         "@esbuild/darwin-x64": { | ||||
|           "version": "0.18.11", | ||||
|           "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.11.tgz", | ||||
|           "integrity": "sha512-N15Vzy0YNHu6cfyDOjiyfJlRJCB/ngKOAvoBf1qybG3eOq0SL2Lutzz9N7DYUbb7Q23XtHPn6lMDF6uWbGv9Fw==", | ||||
|           "optional": true | ||||
|         }, | ||||
|         "ansi-regex": { | ||||
|           "version": "4.1.1", | ||||
|           "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", | ||||
|           "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" | ||||
|         }, | ||||
|         "ansi-styles": { | ||||
|           "version": "3.2.1", | ||||
|           "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", | ||||
|           "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", | ||||
|           "requires": { | ||||
|             "color-convert": "^1.9.0" | ||||
|           } | ||||
|         }, | ||||
|         "anymatch": { | ||||
|           "version": "3.1.3", | ||||
|           "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", | ||||
|           "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", | ||||
|           "requires": { | ||||
|             "normalize-path": "^3.0.0", | ||||
|             "picomatch": "^2.0.4" | ||||
|           } | ||||
|         }, | ||||
|         "binary-extensions": { | ||||
|           "version": "2.2.0", | ||||
|           "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", | ||||
|           "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" | ||||
|         }, | ||||
|         "braces": { | ||||
|           "version": "3.0.2", | ||||
|           "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", | ||||
|           "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", | ||||
|           "requires": { | ||||
|             "fill-range": "^7.0.1" | ||||
|           } | ||||
|         }, | ||||
|         "camelcase": { | ||||
|           "version": "5.3.1", | ||||
|           "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", | ||||
|           "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" | ||||
|         }, | ||||
|         "chalk": { | ||||
|           "version": "4.1.2", | ||||
|           "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", | ||||
|           "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", | ||||
|           "requires": { | ||||
|             "ansi-styles": "^4.1.0", | ||||
|             "supports-color": "^7.1.0" | ||||
|           }, | ||||
|           "dependencies": { | ||||
|             "ansi-styles": { | ||||
|               "version": "4.3.0", | ||||
|               "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", | ||||
|               "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", | ||||
|               "requires": { | ||||
|                 "color-convert": "^2.0.1" | ||||
|               } | ||||
|             }, | ||||
|             "color-convert": { | ||||
|               "version": "2.0.1", | ||||
|               "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", | ||||
|               "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", | ||||
|               "requires": { | ||||
|                 "color-name": "~1.1.4" | ||||
|               } | ||||
|             }, | ||||
|             "color-name": { | ||||
|               "version": "1.1.4", | ||||
|               "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", | ||||
|               "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" | ||||
|             }, | ||||
|             "supports-color": { | ||||
|               "version": "7.2.0", | ||||
|               "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", | ||||
|               "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", | ||||
|               "requires": { | ||||
|                 "has-flag": "^4.0.0" | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         "chokidar": { | ||||
|           "version": "3.5.3", | ||||
|           "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", | ||||
|           "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", | ||||
|           "requires": { | ||||
|             "anymatch": "~3.1.2", | ||||
|             "braces": "~3.0.2", | ||||
|             "fsevents": "~2.3.2", | ||||
|             "glob-parent": "~5.1.2", | ||||
|             "is-binary-path": "~2.1.0", | ||||
|             "is-glob": "~4.0.1", | ||||
|             "normalize-path": "~3.0.0", | ||||
|             "readdirp": "~3.6.0" | ||||
|           } | ||||
|         }, | ||||
|         "chokidar-cli": { | ||||
|           "version": "3.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/chokidar-cli/-/chokidar-cli-3.0.0.tgz", | ||||
| @ -12189,29 +12310,451 @@ | ||||
|           "requires": { | ||||
|             "chokidar": "^3.5.2", | ||||
|             "lodash.debounce": "^4.0.8", | ||||
|             "lodash.throttle": "^4.1.1", | ||||
|             "yargs": "^13.3.0" | ||||
|           } | ||||
|         }, | ||||
|         "cliui": { | ||||
|           "version": "5.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", | ||||
|           "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", | ||||
|           "requires": { | ||||
|             "string-width": "^3.1.0", | ||||
|             "strip-ansi": "^5.2.0", | ||||
|             "wrap-ansi": "^5.1.0" | ||||
|           } | ||||
|         }, | ||||
|         "color-convert": { | ||||
|           "version": "1.9.3", | ||||
|           "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", | ||||
|           "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", | ||||
|           "requires": { | ||||
|             "color-name": "1.1.3" | ||||
|           } | ||||
|         }, | ||||
|         "color-name": { | ||||
|           "version": "1.1.3", | ||||
|           "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", | ||||
|           "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" | ||||
|         }, | ||||
|         "concurrently": { | ||||
|           "version": "8.2.0", | ||||
|           "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.0.tgz", | ||||
|           "integrity": "sha512-nnLMxO2LU492mTUj9qX/az/lESonSZu81UznYDoXtz1IQf996ixVqPAgHXwvHiHCAef/7S8HIK+fTFK7Ifk8YA==", | ||||
|           "requires": { | ||||
|             "chalk": "^4.1.2", | ||||
|             "date-fns": "^2.30.0", | ||||
|             "lodash": "^4.17.21", | ||||
|             "rxjs": "^7.8.1", | ||||
|             "shell-quote": "^1.8.1", | ||||
|             "spawn-command": "0.0.2", | ||||
|             "tree-kill": "^1.2.2" | ||||
|             "supports-color": "^8.1.1", | ||||
|             "tree-kill": "^1.2.2", | ||||
|             "yargs": "^17.7.2" | ||||
|           }, | ||||
|           "dependencies": { | ||||
|             "ansi-regex": { | ||||
|               "version": "5.0.1", | ||||
|               "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", | ||||
|               "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" | ||||
|             }, | ||||
|             "ansi-styles": { | ||||
|               "version": "4.3.0", | ||||
|               "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", | ||||
|               "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", | ||||
|               "requires": { | ||||
|                 "color-convert": "^2.0.1" | ||||
|               } | ||||
|             }, | ||||
|             "cliui": { | ||||
|               "version": "8.0.1", | ||||
|               "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", | ||||
|               "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", | ||||
|               "requires": { | ||||
|                 "string-width": "^4.2.0", | ||||
|                 "strip-ansi": "^6.0.1", | ||||
|                 "wrap-ansi": "^7.0.0" | ||||
|               } | ||||
|             }, | ||||
|             "color-convert": { | ||||
|               "version": "2.0.1", | ||||
|               "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", | ||||
|               "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", | ||||
|               "requires": { | ||||
|                 "color-name": "~1.1.4" | ||||
|               } | ||||
|             }, | ||||
|             "color-name": { | ||||
|               "version": "1.1.4", | ||||
|               "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", | ||||
|               "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" | ||||
|             }, | ||||
|             "emoji-regex": { | ||||
|               "version": "8.0.0", | ||||
|               "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", | ||||
|               "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" | ||||
|             }, | ||||
|             "is-fullwidth-code-point": { | ||||
|               "version": "3.0.0", | ||||
|               "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", | ||||
|               "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" | ||||
|             }, | ||||
|             "string-width": { | ||||
|               "version": "4.2.3", | ||||
|               "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", | ||||
|               "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", | ||||
|               "requires": { | ||||
|                 "emoji-regex": "^8.0.0", | ||||
|                 "is-fullwidth-code-point": "^3.0.0", | ||||
|                 "strip-ansi": "^6.0.1" | ||||
|               } | ||||
|             }, | ||||
|             "strip-ansi": { | ||||
|               "version": "6.0.1", | ||||
|               "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", | ||||
|               "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", | ||||
|               "requires": { | ||||
|                 "ansi-regex": "^5.0.1" | ||||
|               } | ||||
|             }, | ||||
|             "wrap-ansi": { | ||||
|               "version": "7.0.0", | ||||
|               "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", | ||||
|               "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", | ||||
|               "requires": { | ||||
|                 "ansi-styles": "^4.0.0", | ||||
|                 "string-width": "^4.1.0", | ||||
|                 "strip-ansi": "^6.0.0" | ||||
|               } | ||||
|             }, | ||||
|             "y18n": { | ||||
|               "version": "5.0.8", | ||||
|               "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", | ||||
|               "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" | ||||
|             }, | ||||
|             "yargs": { | ||||
|               "version": "17.7.2", | ||||
|               "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", | ||||
|               "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", | ||||
|               "requires": { | ||||
|                 "cliui": "^8.0.1", | ||||
|                 "escalade": "^3.1.1", | ||||
|                 "get-caller-file": "^2.0.5", | ||||
|                 "require-directory": "^2.1.1", | ||||
|                 "string-width": "^4.2.3", | ||||
|                 "y18n": "^5.0.5", | ||||
|                 "yargs-parser": "^21.1.1" | ||||
|               } | ||||
|             }, | ||||
|             "yargs-parser": { | ||||
|               "version": "21.1.1", | ||||
|               "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", | ||||
|               "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         "date-fns": { | ||||
|           "version": "2.30.0", | ||||
|           "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", | ||||
|           "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", | ||||
|           "requires": { | ||||
|             "@babel/runtime": "^7.21.0" | ||||
|           } | ||||
|         }, | ||||
|         "decamelize": { | ||||
|           "version": "1.2.0", | ||||
|           "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", | ||||
|           "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" | ||||
|         }, | ||||
|         "emoji-regex": { | ||||
|           "version": "7.0.3", | ||||
|           "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", | ||||
|           "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" | ||||
|         }, | ||||
|         "esbuild": { | ||||
|           "version": "0.18.11", | ||||
|           "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.11.tgz", | ||||
|           "integrity": "sha512-i8u6mQF0JKJUlGR3OdFLKldJQMMs8OqM9Cc3UCi9XXziJ9WERM5bfkHaEAy0YAvPRMgqSW55W7xYn84XtEFTtA==" | ||||
|           "integrity": "sha512-i8u6mQF0JKJUlGR3OdFLKldJQMMs8OqM9Cc3UCi9XXziJ9WERM5bfkHaEAy0YAvPRMgqSW55W7xYn84XtEFTtA==", | ||||
|           "requires": { | ||||
|             "@esbuild/darwin-x64": "0.18.11" | ||||
|           } | ||||
|         }, | ||||
|         "escalade": { | ||||
|           "version": "3.1.1", | ||||
|           "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", | ||||
|           "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" | ||||
|         }, | ||||
|         "fill-range": { | ||||
|           "version": "7.0.1", | ||||
|           "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", | ||||
|           "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", | ||||
|           "requires": { | ||||
|             "to-regex-range": "^5.0.1" | ||||
|           } | ||||
|         }, | ||||
|         "find-up": { | ||||
|           "version": "3.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", | ||||
|           "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", | ||||
|           "requires": { | ||||
|             "locate-path": "^3.0.0" | ||||
|           } | ||||
|         }, | ||||
|         "fsevents": { | ||||
|           "version": "2.3.2", | ||||
|           "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", | ||||
|           "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", | ||||
|           "optional": true | ||||
|         }, | ||||
|         "get-caller-file": { | ||||
|           "version": "2.0.5", | ||||
|           "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", | ||||
|           "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" | ||||
|         }, | ||||
|         "glob-parent": { | ||||
|           "version": "5.1.2", | ||||
|           "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", | ||||
|           "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", | ||||
|           "requires": { | ||||
|             "is-glob": "^4.0.1" | ||||
|           } | ||||
|         }, | ||||
|         "has-flag": { | ||||
|           "version": "4.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", | ||||
|           "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" | ||||
|         }, | ||||
|         "is-binary-path": { | ||||
|           "version": "2.1.0", | ||||
|           "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", | ||||
|           "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", | ||||
|           "requires": { | ||||
|             "binary-extensions": "^2.0.0" | ||||
|           } | ||||
|         }, | ||||
|         "is-extglob": { | ||||
|           "version": "2.1.1", | ||||
|           "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", | ||||
|           "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" | ||||
|         }, | ||||
|         "is-fullwidth-code-point": { | ||||
|           "version": "2.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", | ||||
|           "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==" | ||||
|         }, | ||||
|         "is-glob": { | ||||
|           "version": "4.0.3", | ||||
|           "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", | ||||
|           "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", | ||||
|           "requires": { | ||||
|             "is-extglob": "^2.1.1" | ||||
|           } | ||||
|         }, | ||||
|         "is-number": { | ||||
|           "version": "7.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", | ||||
|           "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" | ||||
|         }, | ||||
|         "locate-path": { | ||||
|           "version": "3.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", | ||||
|           "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", | ||||
|           "requires": { | ||||
|             "p-locate": "^3.0.0", | ||||
|             "path-exists": "^3.0.0" | ||||
|           } | ||||
|         }, | ||||
|         "lodash": { | ||||
|           "version": "4.17.21", | ||||
|           "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", | ||||
|           "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" | ||||
|         }, | ||||
|         "lodash.debounce": { | ||||
|           "version": "4.0.8", | ||||
|           "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", | ||||
|           "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" | ||||
|         }, | ||||
|         "lodash.throttle": { | ||||
|           "version": "4.1.1", | ||||
|           "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", | ||||
|           "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" | ||||
|         }, | ||||
|         "normalize-path": { | ||||
|           "version": "3.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", | ||||
|           "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" | ||||
|         }, | ||||
|         "p-limit": { | ||||
|           "version": "2.3.0", | ||||
|           "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", | ||||
|           "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", | ||||
|           "requires": { | ||||
|             "p-try": "^2.0.0" | ||||
|           } | ||||
|         }, | ||||
|         "p-locate": { | ||||
|           "version": "3.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", | ||||
|           "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", | ||||
|           "requires": { | ||||
|             "p-limit": "^2.0.0" | ||||
|           } | ||||
|         }, | ||||
|         "p-try": { | ||||
|           "version": "2.2.0", | ||||
|           "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", | ||||
|           "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" | ||||
|         }, | ||||
|         "path-exists": { | ||||
|           "version": "3.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", | ||||
|           "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==" | ||||
|         }, | ||||
|         "picomatch": { | ||||
|           "version": "2.3.1", | ||||
|           "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", | ||||
|           "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" | ||||
|         }, | ||||
|         "readdirp": { | ||||
|           "version": "3.6.0", | ||||
|           "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", | ||||
|           "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", | ||||
|           "requires": { | ||||
|             "picomatch": "^2.2.1" | ||||
|           } | ||||
|         }, | ||||
|         "regenerator-runtime": { | ||||
|           "version": "0.13.11", | ||||
|           "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", | ||||
|           "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" | ||||
|         }, | ||||
|         "require-directory": { | ||||
|           "version": "2.1.1", | ||||
|           "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", | ||||
|           "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" | ||||
|         }, | ||||
|         "require-main-filename": { | ||||
|           "version": "2.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", | ||||
|           "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" | ||||
|         }, | ||||
|         "rxjs": { | ||||
|           "version": "7.8.1", | ||||
|           "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", | ||||
|           "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", | ||||
|           "requires": { | ||||
|             "tslib": "^2.1.0" | ||||
|           } | ||||
|         }, | ||||
|         "set-blocking": { | ||||
|           "version": "2.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", | ||||
|           "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" | ||||
|         }, | ||||
|         "shell-quote": { | ||||
|           "version": "1.8.1", | ||||
|           "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", | ||||
|           "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==" | ||||
|         }, | ||||
|         "spawn-command": { | ||||
|           "version": "0.0.2", | ||||
|           "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", | ||||
|           "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==" | ||||
|         }, | ||||
|         "string-width": { | ||||
|           "version": "3.1.0", | ||||
|           "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", | ||||
|           "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", | ||||
|           "requires": { | ||||
|             "emoji-regex": "^7.0.1", | ||||
|             "is-fullwidth-code-point": "^2.0.0", | ||||
|             "strip-ansi": "^5.1.0" | ||||
|           } | ||||
|         }, | ||||
|         "strip-ansi": { | ||||
|           "version": "5.2.0", | ||||
|           "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", | ||||
|           "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", | ||||
|           "requires": { | ||||
|             "ansi-regex": "^4.1.0" | ||||
|           } | ||||
|         }, | ||||
|         "supports-color": { | ||||
|           "version": "8.1.1", | ||||
|           "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", | ||||
|           "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", | ||||
|           "requires": { | ||||
|             "has-flag": "^4.0.0" | ||||
|           } | ||||
|         }, | ||||
|         "to-regex-range": { | ||||
|           "version": "5.0.1", | ||||
|           "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", | ||||
|           "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", | ||||
|           "requires": { | ||||
|             "is-number": "^7.0.0" | ||||
|           } | ||||
|         }, | ||||
|         "tree-kill": { | ||||
|           "version": "1.2.2", | ||||
|           "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", | ||||
|           "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==" | ||||
|         }, | ||||
|         "tslib": { | ||||
|           "version": "2.6.0", | ||||
|           "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", | ||||
|           "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" | ||||
|         }, | ||||
|         "typescript": { | ||||
|           "version": "5.1.6", | ||||
|           "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", | ||||
|           "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==" | ||||
|         }, | ||||
|         "which-module": { | ||||
|           "version": "2.0.1", | ||||
|           "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", | ||||
|           "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" | ||||
|         }, | ||||
|         "wrap-ansi": { | ||||
|           "version": "5.1.0", | ||||
|           "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", | ||||
|           "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", | ||||
|           "requires": { | ||||
|             "ansi-styles": "^3.2.0", | ||||
|             "string-width": "^3.0.0", | ||||
|             "strip-ansi": "^5.0.0" | ||||
|           } | ||||
|         }, | ||||
|         "y18n": { | ||||
|           "version": "4.0.3", | ||||
|           "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", | ||||
|           "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" | ||||
|         }, | ||||
|         "yargs": { | ||||
|           "version": "13.3.2", | ||||
|           "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", | ||||
|           "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", | ||||
|           "requires": { | ||||
|             "cliui": "^5.0.0", | ||||
|             "find-up": "^3.0.0", | ||||
|             "get-caller-file": "^2.0.1", | ||||
|             "require-directory": "^2.1.1", | ||||
|             "require-main-filename": "^2.0.0", | ||||
|             "set-blocking": "^2.0.0", | ||||
|             "string-width": "^3.0.0", | ||||
|             "which-module": "^2.0.0", | ||||
|             "y18n": "^4.0.0", | ||||
|             "yargs-parser": "^13.1.2" | ||||
|           } | ||||
|         }, | ||||
|         "yargs-parser": { | ||||
|           "version": "13.1.2", | ||||
|           "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", | ||||
|           "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", | ||||
|           "requires": { | ||||
|             "camelcase": "^5.0.0", | ||||
|             "decamelize": "^1.2.0" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
| @ -13034,6 +13577,7 @@ | ||||
|       "version": "2.30.0", | ||||
|       "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", | ||||
|       "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "@babel/runtime": "^7.21.0" | ||||
|       } | ||||
| @ -13070,7 +13614,8 @@ | ||||
|     "decamelize": { | ||||
|       "version": "1.2.0", | ||||
|       "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", | ||||
|       "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" | ||||
|       "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "decimal.js": { | ||||
|       "version": "10.4.3", | ||||
| @ -16013,7 +16558,8 @@ | ||||
|     "get-caller-file": { | ||||
|       "version": "2.0.5", | ||||
|       "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", | ||||
|       "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" | ||||
|       "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "get-intrinsic": { | ||||
|       "version": "1.2.1", | ||||
| @ -18091,6 +18637,7 @@ | ||||
|       "version": "2.1.0", | ||||
|       "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", | ||||
|       "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "binary-extensions": "^2.0.0" | ||||
|       } | ||||
| @ -21704,7 +22251,8 @@ | ||||
|     "normalize-path": { | ||||
|       "version": "3.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", | ||||
|       "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" | ||||
|       "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "normalize-range": { | ||||
|       "version": "0.1.2", | ||||
| @ -24677,6 +25225,7 @@ | ||||
|       "version": "3.6.0", | ||||
|       "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", | ||||
|       "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "picomatch": "^2.2.1" | ||||
|       } | ||||
| @ -25031,7 +25580,8 @@ | ||||
|     "require-directory": { | ||||
|       "version": "2.1.1", | ||||
|       "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", | ||||
|       "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" | ||||
|       "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "require-from-string": { | ||||
|       "version": "2.0.2", | ||||
| @ -25041,7 +25591,8 @@ | ||||
|     "require-main-filename": { | ||||
|       "version": "2.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", | ||||
|       "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" | ||||
|       "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "requires-port": { | ||||
|       "version": "1.0.0", | ||||
| @ -26572,7 +27123,8 @@ | ||||
|     "spawn-command": { | ||||
|       "version": "0.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", | ||||
|       "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==" | ||||
|       "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "spdx-correct": { | ||||
|       "version": "3.2.0", | ||||
| @ -27906,7 +28458,8 @@ | ||||
|     "tree-kill": { | ||||
|       "version": "1.2.2", | ||||
|       "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", | ||||
|       "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==" | ||||
|       "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "ts-dedent": { | ||||
|       "version": "2.2.0", | ||||
| @ -30072,7 +30625,8 @@ | ||||
|     "which-module": { | ||||
|       "version": "2.0.1", | ||||
|       "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", | ||||
|       "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" | ||||
|       "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "which-typed-array": { | ||||
|       "version": "1.1.9", | ||||
| @ -30381,7 +30935,8 @@ | ||||
|     "y18n": { | ||||
|       "version": "4.0.3", | ||||
|       "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", | ||||
|       "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" | ||||
|       "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "yallist": { | ||||
|       "version": "4.0.0", | ||||
| @ -30398,6 +30953,7 @@ | ||||
|       "version": "13.3.2", | ||||
|       "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", | ||||
|       "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "cliui": "^5.0.0", | ||||
|         "find-up": "^3.0.0", | ||||
| @ -30414,22 +30970,26 @@ | ||||
|         "ansi-regex": { | ||||
|           "version": "4.1.1", | ||||
|           "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", | ||||
|           "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" | ||||
|           "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", | ||||
|           "dev": true | ||||
|         }, | ||||
|         "emoji-regex": { | ||||
|           "version": "7.0.3", | ||||
|           "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", | ||||
|           "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" | ||||
|           "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", | ||||
|           "dev": true | ||||
|         }, | ||||
|         "is-fullwidth-code-point": { | ||||
|           "version": "2.0.0", | ||||
|           "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", | ||||
|           "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==" | ||||
|           "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", | ||||
|           "dev": true | ||||
|         }, | ||||
|         "string-width": { | ||||
|           "version": "3.1.0", | ||||
|           "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", | ||||
|           "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", | ||||
|           "dev": true, | ||||
|           "requires": { | ||||
|             "emoji-regex": "^7.0.1", | ||||
|             "is-fullwidth-code-point": "^2.0.0", | ||||
| @ -30440,6 +31000,7 @@ | ||||
|           "version": "5.2.0", | ||||
|           "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", | ||||
|           "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", | ||||
|           "dev": true, | ||||
|           "requires": { | ||||
|             "ansi-regex": "^4.1.0" | ||||
|           } | ||||
| @ -30450,6 +31011,7 @@ | ||||
|       "version": "13.1.2", | ||||
|       "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", | ||||
|       "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "camelcase": "^5.0.0", | ||||
|         "decamelize": "^1.2.0" | ||||
| @ -30458,7 +31020,8 @@ | ||||
|         "camelcase": { | ||||
|           "version": "5.3.1", | ||||
|           "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", | ||||
|           "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" | ||||
|           "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", | ||||
|           "dev": true | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|  | ||||
| @ -87,7 +87,7 @@ | ||||
|     "@moodlehq/cordova-plugin-statusbar": "4.0.0-moodle.2", | ||||
|     "@moodlehq/cordova-plugin-zip": "3.1.0-moodle.1", | ||||
|     "@moodlehq/ionic-native-push": "5.36.0-moodle.2", | ||||
|     "@moodlehq/phonegap-plugin-push": "4.0.0-moodle.6", | ||||
|     "@moodlehq/phonegap-plugin-push": "4.0.0-moodle.7", | ||||
|     "@ngx-translate/core": "^13.0.0", | ||||
|     "@ngx-translate/http-loader": "^6.0.0", | ||||
|     "@types/chart.js": "^2.9.31", | ||||
|  | ||||
							
								
								
									
										6
									
								
								resources/android/xml/backup_rules.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								resources/android/xml/backup_rules.xml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <data-extraction-rules> | ||||
|  <cloud-backup disableIfNoEncryptionCapabilities="false"> | ||||
|    <exclude domain="sharedpref" path="."/> | ||||
|  </cloud-backup> | ||||
| </data-extraction-rules> | ||||
							
								
								
									
										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.isAndroid()) { | ||||
|             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); | ||||
|  | ||||
| @ -63,6 +63,7 @@ import { CoreConfig } from './config'; | ||||
| import { CoreNetwork } from '@services/network'; | ||||
| import { CoreUserGuestSupportConfig } from '@features/user/classes/support/guest-support-config'; | ||||
| import { CoreLang, CoreLangFormat } from '@services/lang'; | ||||
| import { CoreNative } from '@features/native/services/native'; | ||||
| 
 | ||||
| export const CORE_SITE_SCHEMAS = new InjectionToken<CoreSiteSchema[]>('CORE_SITE_SCHEMAS'); | ||||
| export const CORE_SITE_CURRENT_SITE_ID_CONFIG = 'current_site_id'; | ||||
| @ -821,16 +822,22 @@ export class CoreSitesProvider { | ||||
|         config?: CoreSiteConfig, | ||||
|         oauthId?: number, | ||||
|     ): Promise<void> { | ||||
|         await this.sitesTable.insert({ | ||||
|         const promises: Promise<unknown>[] = []; | ||||
|         const site: SiteDBEntry = { | ||||
|             id, | ||||
|             siteUrl, | ||||
|             token, | ||||
|             token: '', | ||||
|             info: info ? JSON.stringify(info) : undefined, | ||||
|             privateToken, | ||||
|             privateToken: '', | ||||
|             config: config ? JSON.stringify(config) : undefined, | ||||
|             loggedOut: 0, | ||||
|             oauthId, | ||||
|         }); | ||||
|         }; | ||||
| 
 | ||||
|         promises.push(this.sitesTable.insert(site)); | ||||
|         promises.push(this.storeTokensInSecureStorage(id, token, privateToken)); | ||||
| 
 | ||||
|         await Promise.all(promises); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -1058,15 +1065,14 @@ export class CoreSitesProvider { | ||||
|         // Site DB deleted, now delete the app from the list of sites.
 | ||||
|         delete this.sites[siteId]; | ||||
| 
 | ||||
|         try { | ||||
|             await this.sitesTable.deleteByPrimaryKey({ id: siteId }); | ||||
|         } catch (err) { | ||||
|             // DB remove shouldn't fail, but we'll go ahead even if it does.
 | ||||
|         } | ||||
|         // DB remove shouldn't fail, but we'll go ahead even if it does.
 | ||||
|         await CoreUtils.ignoreErrors(this.sitesTable.deleteByPrimaryKey({ id: siteId })); | ||||
| 
 | ||||
|         // Site deleted from sites list, now delete the folder.
 | ||||
|         await site.deleteFolder(); | ||||
| 
 | ||||
|         await CoreUtils.ignoreErrors(CoreNative.plugin('secureStorage').deleteCollection(siteId)); | ||||
| 
 | ||||
|         CoreEvents.trigger(CoreEvents.SITE_DELETED, site, siteId); | ||||
|     } | ||||
| 
 | ||||
| @ -1107,7 +1113,7 @@ export class CoreSitesProvider { | ||||
|         // Retrieve and create the site.
 | ||||
|         let record: SiteDBEntry; | ||||
|         try { | ||||
|             record = await this.sitesTable.getOneByPrimaryKey({ id: siteId }); | ||||
|             record = await this.loadSiteTokens(await this.sitesTable.getOneByPrimaryKey({ id: siteId })); | ||||
|         } catch { | ||||
|             throw new CoreError('SiteId not found.'); | ||||
|         } | ||||
| @ -1144,7 +1150,7 @@ export class CoreSitesProvider { | ||||
|      * @returns Promise resolved with the site. | ||||
|      */ | ||||
|     async getSiteByUrl(siteUrl: string): Promise<CoreSite> { | ||||
|         const data = await this.sitesTable.getOne({ siteUrl }); | ||||
|         const data = await this.loadSiteTokens(await this.sitesTable.getOne({ siteUrl })); | ||||
| 
 | ||||
|         return this.addSiteFromSiteListEntry(data); | ||||
|     } | ||||
| @ -1491,14 +1497,17 @@ export class CoreSitesProvider { | ||||
|         site.privateToken = privateToken; | ||||
|         site.setLoggedOut(false); // Token updated means the user authenticated again, not logged out anymore.
 | ||||
| 
 | ||||
|         await this.sitesTable.update( | ||||
|             { | ||||
|                 token, | ||||
|                 privateToken, | ||||
|                 loggedOut: 0, | ||||
|             }, | ||||
|             { id: siteId }, | ||||
|         ); | ||||
|         const promises: Promise<unknown>[] = []; | ||||
|         const newData: Partial<SiteDBEntry> = { | ||||
|             token: '', | ||||
|             privateToken: '', | ||||
|             loggedOut: 0, | ||||
|         }; | ||||
| 
 | ||||
|         promises.push(this.sitesTable.update(newData, { id: siteId })); | ||||
|         promises.push(this.storeTokensInSecureStorage(siteId, token, privateToken)); | ||||
| 
 | ||||
|         await Promise.all(promises); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -1601,6 +1610,8 @@ export class CoreSitesProvider { | ||||
|             const ids: string[] = []; | ||||
| 
 | ||||
|             await Promise.all(siteEntries.map(async (site) => { | ||||
|                 site = await this.loadSiteTokens(site); | ||||
| 
 | ||||
|                 await this.addSiteFromSiteListEntry(site); | ||||
| 
 | ||||
|                 if (this.sites[site.id].containsUrl(url)) { | ||||
| @ -1962,6 +1973,80 @@ export class CoreSitesProvider { | ||||
|         return this.schemasTables[siteId]; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Move all tokens stored in DB to a secure storage. | ||||
|      */ | ||||
|     async moveTokensToSecureStorage(): Promise<void> { | ||||
|         const sites = await this.sitesTable.getMany(); | ||||
| 
 | ||||
|         await Promise.all(sites.map(async site => { | ||||
|             if (!site.token && !site.privateToken) { | ||||
|                 return; // Tokens are empty, no need to treat them.
 | ||||
|             } | ||||
| 
 | ||||
|             try { | ||||
|                 await this.storeTokensInSecureStorage(site.id, site.token, site.privateToken); | ||||
|             } catch { | ||||
|                 this.logger.error('Error storing tokens in secure storage for site ' + site.id); | ||||
|             } | ||||
|         })); | ||||
| 
 | ||||
|         // Remove tokens from DB even if they couldn't be stored in secure storage.
 | ||||
|         await this.sitesTable.update({ token: '', privateToken: '' }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get tokens from secure storage. | ||||
|      * | ||||
|      * @param siteId Site ID. | ||||
|      * @returns Stored tokens. | ||||
|      */ | ||||
|     protected async getTokensFromSecureStorage(siteId: string): Promise<{ token: string; privateToken?: string }> { | ||||
|         const result = await CoreNative.plugin('secureStorage').get(['token', 'privateToken'], siteId); | ||||
| 
 | ||||
|         return { | ||||
|             token: result?.token ?? '', | ||||
|             privateToken: result?.privateToken ?? undefined, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Store tokens in secure storage. | ||||
|      * | ||||
|      * @param siteId Site ID. | ||||
|      * @param token Site token. | ||||
|      * @param privateToken Site private token. | ||||
|      */ | ||||
|     protected async storeTokensInSecureStorage( | ||||
|         siteId: string, | ||||
|         token: string, | ||||
|         privateToken?: string, | ||||
|     ): Promise<void> { | ||||
|         await CoreNative.plugin('secureStorage').store({ | ||||
|             token: token, | ||||
|             privateToken: privateToken ?? '', | ||||
|         }, siteId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Given a site, load its tokens if needed. | ||||
|      * | ||||
|      * @param site Site data. | ||||
|      * @returns Site with tokens loaded. | ||||
|      */ | ||||
|     protected async loadSiteTokens(site: SiteDBEntry): Promise<SiteDBEntry> { | ||||
|         if (site.token) { | ||||
|             return site; | ||||
|         } | ||||
| 
 | ||||
|         const tokens = await this.getTokensFromSecureStorage(site.id); | ||||
| 
 | ||||
|         return { | ||||
|             ...site, | ||||
|             ...tokens, | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export const CoreSites = makeSingleton(CoreSitesProvider); | ||||
|  | ||||
| @ -89,6 +89,10 @@ export class CoreUpdateManagerProvider { | ||||
|             promises.push(this.upgradeFontSizeNames()); | ||||
|         } | ||||
| 
 | ||||
|         if (versionCode >= 43000 && versionApplied < 43000 && versionApplied > 0) { | ||||
|             promises.push(CoreSites.moveTokensToSecureStorage()); | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             await Promise.all(promises); | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user