diff --git a/config.xml b/config.xml
index 18f423f71..92e603145 100644
--- a/config.xml
+++ b/config.xml
@@ -67,11 +67,12 @@
+
-
+
diff --git a/cordova-plugin-moodleapp/plugin.xml b/cordova-plugin-moodleapp/plugin.xml
index a6abaa381..ddc3cf863 100644
--- a/cordova-plugin-moodleapp/plugin.xml
+++ b/cordova-plugin-moodleapp/plugin.xml
@@ -7,10 +7,20 @@
-
-
+
+
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cordova-plugin-moodleapp/scripts/copy-javascript.js b/cordova-plugin-moodleapp/scripts/copy-javascript.js
index 18f287d08..e843ac866 100755
--- a/cordova-plugin-moodleapp/scripts/copy-javascript.js
+++ b/cordova-plugin-moodleapp/scripts/copy-javascript.js
@@ -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')
diff --git a/cordova-plugin-moodleapp/src/android/SecureStorage.java b/cordova-plugin-moodleapp/src/android/SecureStorage.java
new file mode 100644
index 000000000..b60070a08
--- /dev/null
+++ b/cordova-plugin-moodleapp/src/android/SecureStorage.java
@@ -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
+ );
+ }
+
+}
diff --git a/cordova-plugin-moodleapp/src/android/SystemUI.java b/cordova-plugin-moodleapp/src/android/SystemUI.java
deleted file mode 100644
index ed20bd28a..000000000
--- a/cordova-plugin-moodleapp/src/android/SystemUI.java
+++ /dev/null
@@ -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;
- }
-
-
-}
diff --git a/cordova-plugin-moodleapp/src/ios/SecureStorage.h b/cordova-plugin-moodleapp/src/ios/SecureStorage.h
new file mode 100644
index 000000000..71f1a8c6b
--- /dev/null
+++ b/cordova-plugin-moodleapp/src/ios/SecureStorage.h
@@ -0,0 +1,11 @@
+#import
+#import
+
+@interface SecureStorage : CDVPlugin {}
+
+- (void)get:(CDVInvokedUrlCommand*)command;
+- (void)store:(CDVInvokedUrlCommand*)command;
+- (void)delete:(CDVInvokedUrlCommand*)command;
+- (void)deleteCollection:(CDVInvokedUrlCommand*)command;
+
+@end
diff --git a/cordova-plugin-moodleapp/src/ios/SecureStorage.m b/cordova-plugin-moodleapp/src/ios/SecureStorage.m
new file mode 100644
index 000000000..c71e752ff
--- /dev/null
+++ b/cordova-plugin-moodleapp/src/ios/SecureStorage.m
@@ -0,0 +1,218 @@
+#import
+#import
+#import
+#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
diff --git a/cordova-plugin-moodleapp/src/ts/index.ts b/cordova-plugin-moodleapp/src/ts/index.ts
index 27a15ad10..a11c3eba9 100644
--- a/cordova-plugin-moodleapp/src/ts/index.ts
+++ b/cordova-plugin-moodleapp/src/ts/index.ts
@@ -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,
diff --git a/cordova-plugin-moodleapp/src/ts/plugins/SecureStorage.ts b/cordova-plugin-moodleapp/src/ts/plugins/SecureStorage.ts
new file mode 100644
index 000000000..26e15a65a
--- /dev/null
+++ b/cordova-plugin-moodleapp/src/ts/plugins/SecureStorage.ts
@@ -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> {
+ 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, collection: string): Promise {
+ 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 {
+ 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 {
+ await new Promise((resolve, reject) => {
+ cordova.exec(resolve, reject, 'SecureStorage', 'deleteCollection', [collection]);
+ });
+ }
+
+}
diff --git a/cordova-plugin-moodleapp/src/ts/plugins/SystemUI.ts b/cordova-plugin-moodleapp/src/ts/plugins/SystemUI.ts
deleted file mode 100644
index 28494b2b1..000000000
--- a/cordova-plugin-moodleapp/src/ts/plugins/SystemUI.ts
+++ /dev/null
@@ -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 {
- await new Promise((resolve, reject) => {
- cordova.exec(resolve, reject, 'SystemUI', 'setNavigationBarColor', [color]);
- });
- }
-
-}
diff --git a/cordova-plugin-moodleapp/types/index.d.ts b/cordova-plugin-moodleapp/types/index.d.ts
index 77722c985..59ed28d30 100644
--- a/cordova-plugin-moodleapp/types/index.d.ts
+++ b/cordova-plugin-moodleapp/types/index.d.ts
@@ -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;
diff --git a/package-lock.json b/package-lock.json
index 6872e36fc..3fa03dfcd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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
}
}
},
diff --git a/package.json b/package.json
index a7e78e80a..0b62e4e83 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/resources/android/xml/backup_rules.xml b/resources/android/xml/backup_rules.xml
new file mode 100644
index 000000000..468ee0a6a
--- /dev/null
+++ b/resources/android/xml/backup_rules.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/src/core/features/emulator/classes/SecureStorage.ts b/src/core/features/emulator/classes/SecureStorage.ts
new file mode 100644
index 000000000..bec49d87c
--- /dev/null
+++ b/src/core/features/emulator/classes/SecureStorage.ts
@@ -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> {
+ if (typeof names === 'string') {
+ names = [names];
+ }
+
+ const result: Record = {};
+
+ 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, collection: string): Promise {
+ 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 {
+ 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 {
+ 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);
+ }
+ }
+ }
+
+}
diff --git a/src/core/features/emulator/emulator.module.ts b/src/core/features/emulator/emulator.module.ts
index 48ef1aa05..e82936fe0 100644
--- a/src/core/features/emulator/emulator.module.ts
+++ b/src/core/features/emulator/emulator.module.ts
@@ -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,
},
diff --git a/src/core/features/native/services/native.ts b/src/core/features/native/services/native.ts
index 2220a621d..90654fc90 100644
--- a/src/core/features/native/services/native.ts
+++ b/src/core/features/native/services/native.ts
@@ -24,6 +24,7 @@ import { AsyncInstance, asyncInstance } from '@/core/utils/async-instance';
export class CoreNativeService {
private plugins: Partial> = {};
+ private mocks: Partial> = {};
/**
* Get a native plugin instance.
@@ -31,22 +32,33 @@ export class CoreNativeService {
* @param plugin Plugin name.
* @returns Plugin instance.
*/
- plugin(plugin: Plugin): AsyncInstance | null {
- if (!CorePlatform.isAndroid()) {
- return null;
- }
-
+ plugin(plugin: Plugin): AsyncInstance {
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;
}
+ /**
+ * Register a mock to use in browser instead of the native plugin implementation.
+ *
+ * @param plugin Plugin name.
+ * @param instance Instance to use.
+ */
+ registerBrowserMock(plugin: Plugin, instance: MoodleAppPlugins[Plugin]): void {
+ this.mocks[plugin] = instance;
+ }
+
}
export const CoreNative = makeSingleton(CoreNativeService);
diff --git a/src/core/services/sites.ts b/src/core/services/sites.ts
index fed018cee..265232db3 100644
--- a/src/core/services/sites.ts
+++ b/src/core/services/sites.ts
@@ -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('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 {
- await this.sitesTable.insert({
+ const promises: Promise[] = [];
+ 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 {
- 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[] = [];
+ const newData: Partial = {
+ 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 {
+ 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 {
+ 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 {
+ if (site.token) {
+ return site;
+ }
+
+ const tokens = await this.getTokensFromSecureStorage(site.id);
+
+ return {
+ ...site,
+ ...tokens,
+ };
+ }
+
}
export const CoreSites = makeSingleton(CoreSitesProvider);
diff --git a/src/core/services/update-manager.ts b/src/core/services/update-manager.ts
index 3093e938b..e7015b1b6 100644
--- a/src/core/services/update-manager.ts
+++ b/src/core/services/update-manager.ts
@@ -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);