Merge pull request #3780 from dpalou/MOBILE-3839

Mobile 3839
main
Noel De Martin 2023-09-12 14:07:26 +02:00 committed by GitHub
commit e647e535fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1375 additions and 134 deletions

View File

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

View File

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

View File

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

View 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
);
}
}

View File

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

View 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

View 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

View File

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

View 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]);
});
}
}

View File

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

View File

@ -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
View File

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

View File

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

View 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>

View 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);
}
}
}
}

View File

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

View File

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

View File

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

View File

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