From 7c376dc50dbee31837a9a052fa54529795424fda Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Wed, 26 Feb 2025 10:14:58 +0700 Subject: [PATCH 01/77] Update README.md --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c12316d3..ac43e84e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ # vpnclient_controller_flutter -A new Flutter project. +Flutter wrapper for VPN controller + +Supported platforms +* iOS 15+ (iPhone, iPad, MacOS M) +* Android +* MacOS Intel (later) +* Windows (later) +* Ubuntu (later) ## Getting Started From 07e2f19d5f4f1bd805d25eae75f17a02cf5e3878 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Wed, 26 Feb 2025 10:15:42 +0700 Subject: [PATCH 02/77] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ac43e84e..e1f80041 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# vpnclient_controller_flutter +# VPN Client Controller Flutter Flutter wrapper for VPN controller From 1571f40bdf8e33fb40fc1d1a078c91e1bee8bf04 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Wed, 26 Feb 2025 10:16:49 +0700 Subject: [PATCH 03/77] Create 1.txt --- dev/1.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 dev/1.txt diff --git a/dev/1.txt b/dev/1.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/dev/1.txt @@ -0,0 +1 @@ + From f3a1d3548b45e22a9d7c8a576c25f9e0b2ba01b8 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Wed, 26 Feb 2025 10:17:09 +0700 Subject: [PATCH 04/77] Create 1.txt --- docs/1.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/1.txt diff --git a/docs/1.txt b/docs/1.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/docs/1.txt @@ -0,0 +1 @@ + From 93da7bbef82f183a414296da0f682ca599f43e5d Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Wed, 26 Feb 2025 10:17:25 +0700 Subject: [PATCH 05/77] Create 1.txt --- docs/assets/1.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/assets/1.txt diff --git a/docs/assets/1.txt b/docs/assets/1.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/docs/assets/1.txt @@ -0,0 +1 @@ + From 56a1c4866fd6e92345856f51855bd916e6af06dd Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Wed, 26 Feb 2025 10:18:19 +0700 Subject: [PATCH 06/77] Add files via upload --- docs/assets/vpnclient_controller.jpeg | Bin 0 -> 71919 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/assets/vpnclient_controller.jpeg diff --git a/docs/assets/vpnclient_controller.jpeg b/docs/assets/vpnclient_controller.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..7b7a49c35abc5c2e775613bc8c92b5ef4e8623d7 GIT binary patch literal 71919 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 literal 0 HcmV?d00001 From ba46165f6e142b4ff84151a24e446c85411e5693 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Wed, 26 Feb 2025 10:19:39 +0700 Subject: [PATCH 07/77] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e1f80041..40a5ff65 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ Supported platforms * Windows (later) * Ubuntu (later) +![VPN Client Controller](https://raw.githubusercontent.com/VPNclient/VPNclient-controller-flutter/refs/heads/main/docs/assets/vpnclient_controller.jpeg) + ## Getting Started This project is a starting point for a Flutter application. From 8bcd9536fe100c9f9144f6c8a92ada28178e3f07 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Wed, 26 Feb 2025 10:24:07 +0700 Subject: [PATCH 08/77] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 40a5ff65..c713e19b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Supported platforms * Windows (later) * Ubuntu (later) -![VPN Client Controller](https://raw.githubusercontent.com/VPNclient/VPNclient-controller-flutter/refs/heads/main/docs/assets/vpnclient_controller.jpeg) +![VPN Client Controller](https://raw.githubusercontent.com/VPNclient/.github/refs/heads/main/assets/vpnclient_scheme2.png) ## Getting Started From 71057a8ee7d61d81c30e9022ac2514acfba4e2ad Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Mon, 3 Mar 2025 17:35:01 +0700 Subject: [PATCH 09/77] Create controller.md --- docs/controller.md | 170 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 docs/controller.md diff --git a/docs/controller.md b/docs/controller.md new file mode 100644 index 00000000..5ede07eb --- /dev/null +++ b/docs/controller.md @@ -0,0 +1,170 @@ +### **Methods (API)** +1. **initialize()** + - Initializes the VPN controller and prepares it for use. + - Should be called before any other method. + +2. **connect({required String serverAddress, required String credentials})** + - Connects to the specified VPN server using the provided credentials. + - Parameters: + - `serverAddress`: The address of the VPN server. + - `credentials`: Authentication details (e.g., username, password, or token). + +3. **disconnect()** + - Disconnects the active VPN connection. + +4. **switchServer({required String newServerAddress})** + - Switches the VPN connection to a new server. + - Parameters: + - `newServerAddress`: The address of the new VPN server. + +5. **getConnectionStatus()** + - Returns the current connection status (e.g., connected, disconnected, connecting, error). + +6. **getServerList()** + - Fetches the list of available VPN servers. + - Returns a list of server addresses or server objects. + +7. **pingServer({required String serverAddress})** + - Pings a specific server to check latency. + - Parameters: + - `serverAddress`: The address of the server to ping. + - Returns the latency in milliseconds. + +8. **setRoutingRules({required List rules})** + - Configures routing rules for specific apps or domains. + - Parameters: + - `rules`: A list of routing rules (e.g., route YouTube traffic through VPN, block ads.com). + +9. **loadSubscription({required String subscriptionLink})** + - Loads a VPN subscription from the provided link. + - Parameters: + - `subscriptionLink`: The link to the subscription file. + +10. **updateController()** + - Checks for and applies updates to the VPN controller (e.g., new versions of X-Ray or other dependencies). + +11. **setProxy({required ProxyConfig proxyConfig})** + - Configures a proxy for the VPN connection. + - Parameters: + - `proxyConfig`: Configuration for the proxy (e.g., type, address, port). + +12. **getSessionStatistics()** + - Returns statistics for the current VPN session (e.g., data usage, duration). + +13. **setAutoConnect({required bool enable})** + - Enables or disables auto-connect functionality. + - Parameters: + - `enable`: Whether to enable auto-connect. + +14. **setKillSwitch({required bool enable})** + - Enables or disables the kill switch (block all traffic if VPN disconnects). + - Parameters: + - `enable`: Whether to enable the kill switch. + +15. **setLanguage({required String languageCode})** + - Sets the language for error messages and UI localization. + - Parameters: + - `languageCode`: The language code (e.g., "en", "ru"). + +--- + +### **Events** +1. **onConnectionStatusChanged** + - Fired when the VPN connection status changes. + - Payload: `ConnectionStatus` (e.g., connected, disconnected, error). + +2. **onError** + - Fired when an error occurs (e.g., connection failed, invalid credentials). + - Payload: `ErrorCode` and `ErrorMessage`. + +3. **onServerSwitched** + - Fired when the VPN server is successfully switched. + - Payload: `newServerAddress`. + +4. **onPingResult** + - Fired when a ping operation completes. + - Payload: `serverAddress` and `latencyInMs`. + +5. **onSubscriptionLoaded** + - Fired when a subscription is successfully loaded. + - Payload: `subscriptionDetails`. + +6. **onDataUsageUpdated** + - Fired periodically with updated data usage statistics. + - Payload: `dataUsed` and `dataRemaining`. + +7. **onRoutingRulesApplied** + - Fired when routing rules are successfully applied. + - Payload: `List`. + +8. **onControllerUpdated** + - Fired when the VPN controller is successfully updated. + - Payload: `newVersion`. + +9. **onProxyConfigured** + - Fired when the proxy is successfully configured. + - Payload: `ProxyConfig`. + +10. **onKillSwitchTriggered** + - Fired when the kill switch is activated (e.g., VPN disconnects unexpectedly). + - Payload: None. + +--- + +### **Data Models** +1. **ConnectionStatus** + - Enum: `connecting`, `connected`, `disconnected`, `error`. + +2. **Server** + - Properties: `address`, `latency`, `location`, `isPreferred`. + +3. **RoutingRule** + - Properties: `appName`, `domain`, `action` (e.g., `block`, `allow`, `routeThroughVPN`). + +4. **ProxyConfig** + - Properties: `type` (e.g., `socks5`, `http`), `address`, `port`, `credentials`. + +5. **ErrorCode** + - Enum: `invalidCredentials`, `serverUnavailable`, `subscriptionExpired`, `unknownError`. + +6. **SubscriptionDetails** + - Properties: `expiryDate`, `dataLimit`, `usedData`. + +--- + +### **Example Usage** +```dart +// Initialize the controller +vpnController.initialize(); + +// Connect to a VPN server +vpnController.connect( + serverAddress: "vpn.example.com", + credentials: "user:password", +); + +// Listen for connection status changes +vpnController.onConnectionStatusChanged.listen((status) { + print("Connection status: $status"); +}); + +// Set routing rules +vpnController.setRoutingRules( + rules: [ + RoutingRule(appName: "YouTube", action: "routeThroughVPN"), + RoutingRule(domain: "ads.com", action: "block"), + ], +); + +// Ping a server +vpnController.pingServer(serverAddress: "vpn.example.com"); +vpnController.onPingResult.listen((result) { + print("Ping result: ${result.latencyInMs} ms"); +}); +``` + +--- + +--- + +This specification provides a clean, modular, and extensible interface for your VPN client application. Let me know if you need further refinements or additional details! From 09d05c75626c506f6404da9ae0a6bebb655af246 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Mon, 3 Mar 2025 17:35:30 +0700 Subject: [PATCH 10/77] Update controller.md --- docs/controller.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/controller.md b/docs/controller.md index 5ede07eb..298b850e 100644 --- a/docs/controller.md +++ b/docs/controller.md @@ -164,7 +164,3 @@ vpnController.onPingResult.listen((result) { ``` --- - ---- - -This specification provides a clean, modular, and extensible interface for your VPN client application. Let me know if you need further refinements or additional details! From 839beeea8dae71325395d4c0eeb52b7ad05e2bec Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Mon, 3 Mar 2025 17:46:48 +0700 Subject: [PATCH 11/77] Update controller.md --- docs/controller.md | 65 ++++++++++++++++------------------------------ 1 file changed, 22 insertions(+), 43 deletions(-) diff --git a/docs/controller.md b/docs/controller.md index 298b850e..e68b3fb0 100644 --- a/docs/controller.md +++ b/docs/controller.md @@ -3,68 +3,50 @@ - Initializes the VPN controller and prepares it for use. - Should be called before any other method. -2. **connect({required String serverAddress, required String credentials})** - - Connects to the specified VPN server using the provided credentials. +2. **connect({required Integer index})** + - Connects to the specified VPN server. - Parameters: - - `serverAddress`: The address of the VPN server. - - `credentials`: Authentication details (e.g., username, password, or token). + - `index`: The index from from getServerList. 3. **disconnect()** - Disconnects the active VPN connection. -4. **switchServer({required String newServerAddress})** - - Switches the VPN connection to a new server. - - Parameters: - - `newServerAddress`: The address of the new VPN server. - -5. **getConnectionStatus()** +4. **getConnectionStatus()** - Returns the current connection status (e.g., connected, disconnected, connecting, error). -6. **getServerList()** +5. **getServerList()** - Fetches the list of available VPN servers. - Returns a list of server addresses or server objects. -7. **pingServer({required String serverAddress})** +6. **pingServer({required Integer index})** - Pings a specific server to check latency. - Parameters: - - `serverAddress`: The address of the server to ping. + - `index`: The index from from getServerList. - Returns the latency in milliseconds. -8. **setRoutingRules({required List rules})** +7. **setRoutingRules({required List rules})** - Configures routing rules for specific apps or domains. - Parameters: - `rules`: A list of routing rules (e.g., route YouTube traffic through VPN, block ads.com). -9. **loadSubscription({required String subscriptionLink})** +8. **loadSubscription({required String subscriptionLink})** - Loads a VPN subscription from the provided link. - Parameters: - `subscriptionLink`: The link to the subscription file. -10. **updateController()** - - Checks for and applies updates to the VPN controller (e.g., new versions of X-Ray or other dependencies). - -11. **setProxy({required ProxyConfig proxyConfig})** - - Configures a proxy for the VPN connection. - - Parameters: - - `proxyConfig`: Configuration for the proxy (e.g., type, address, port). - -12. **getSessionStatistics()** +9. **getSessionStatistics()** - Returns statistics for the current VPN session (e.g., data usage, duration). -13. **setAutoConnect({required bool enable})** +10. **setAutoConnect({required bool enable})** - Enables or disables auto-connect functionality. - Parameters: - `enable`: Whether to enable auto-connect. -14. **setKillSwitch({required bool enable})** +11. **setKillSwitch({required bool enable})** - Enables or disables the kill switch (block all traffic if VPN disconnects). - Parameters: - `enable`: Whether to enable the kill switch. -15. **setLanguage({required String languageCode})** - - Sets the language for error messages and UI localization. - - Parameters: - - `languageCode`: The language code (e.g., "en", "ru"). --- @@ -79,11 +61,12 @@ 3. **onServerSwitched** - Fired when the VPN server is successfully switched. + - Может в onConnectionStatusChanged? - Payload: `newServerAddress`. 4. **onPingResult** - Fired when a ping operation completes. - - Payload: `serverAddress` and `latencyInMs`. + - Payload: `serverIndex` and `latencyInMs`. 5. **onSubscriptionLoaded** - Fired when a subscription is successfully loaded. @@ -97,15 +80,7 @@ - Fired when routing rules are successfully applied. - Payload: `List`. -8. **onControllerUpdated** - - Fired when the VPN controller is successfully updated. - - Payload: `newVersion`. - -9. **onProxyConfigured** - - Fired when the proxy is successfully configured. - - Payload: `ProxyConfig`. - -10. **onKillSwitchTriggered** +8. **onKillSwitchTriggered** - Fired when the kill switch is activated (e.g., VPN disconnects unexpectedly). - Payload: None. @@ -137,10 +112,14 @@ // Initialize the controller vpnController.initialize(); +// load subscription +vpnController.loadSubscription( + subscriptionLink: "https://pastebin.com/raw/ZCYiJ98W" +); + // Connect to a VPN server vpnController.connect( - serverAddress: "vpn.example.com", - credentials: "user:password", + serverAddress: 1 ); // Listen for connection status changes @@ -157,7 +136,7 @@ vpnController.setRoutingRules( ); // Ping a server -vpnController.pingServer(serverAddress: "vpn.example.com"); +vpnController.pingServer(index: 1); vpnController.onPingResult.listen((result) { print("Ping result: ${result.latencyInMs} ms"); }); From 587bbb9906ee52831fa9621c37483b5deacaac10 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Tue, 4 Mar 2025 20:23:36 +0700 Subject: [PATCH 12/77] Update README.md --- README.md | 187 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 170 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index c713e19b..3b73ee88 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,178 @@ -# VPN Client Controller Flutter +# 🚀 VPN Client Controller Flutter -Flutter wrapper for VPN controller - -Supported platforms -* iOS 15+ (iPhone, iPad, MacOS M) -* Android -* MacOS Intel (later) -* Windows (later) -* Ubuntu (later) +## 🌍 Overview +VPN Client Controller Flutter is a Flutter wrapper for managing VPN connections with an intuitive API. It provides seamless integration with various platforms, allowing developers to control VPN connections efficiently. ![VPN Client Controller](https://raw.githubusercontent.com/VPNclient/.github/refs/heads/main/assets/vpnclient_scheme2.png) -## Getting Started +### ✅ Supported Platforms +- iOS 15+ (iPhone, iPad, MacOS M) +- Android +- 🏗️ MacOS Intel (coming soon) +- 🏗️ Windows (coming soon) +- 🏗️ Ubuntu (coming soon) + +## 📥 Getting Started + +To start using VPN Client Controller Flutter, ensure you have Flutter installed and set up your project accordingly. + +### 📦 Installation +```sh +flutter pub add vpnclient_controller +``` + +## 📌 Example Usage + +```dart +// Initialize the controller +vpnController.initialize(); + +// Load subscription +vpnController.loadSubscription( + subscriptionLink: "https://pastebin.com/raw/ZCYiJ98W" +); + +// Connect to a VPN server +vpnController.connect(index: 1); + +// Listen for connection status changes +vpnController.onConnectionStatusChanged.listen((status) { + print("Connection status: $status"); +}); + +// Set routing rules +vpnController.setRoutingRules( + rules: [ + RoutingRule(appName: "YouTube", action: "routeThroughVPN"), + RoutingRule(domain: "ads.com", action: "block"), + ], +); + +// Ping a server +vpnController.pingServer(index: 1); +vpnController.onPingResult.listen((result) { + print("Ping result: ${result.latencyInMs} ms"); +}); +``` + +--- + +## ⚙️ API Methods + +### 🔹 1. initialize() +Initializes the VPN controller. This should be called before using any other method. + +### 🔹 2. connect({required int index}) +Connects to the specified VPN server. +- `index`: Index of the server from `getServerList()`. + +### 🔹 3. disconnect() +Disconnects the active VPN connection. + +### 🔹 4. getConnectionStatus() +Returns the current connection status (`connected`, `disconnected`, `connecting`, `error`). + +### 🔹 5. getServerList() +Fetches the list of available VPN servers. + +### 🔹 6. pingServer({required int index}) +Pings a specific server to check latency. +- `index`: Index of the server from `getServerList()`. +- Returns: Latency in milliseconds. + +### 🔹 7. setRoutingRules({required List rules}) +Configures routing rules for apps or domains. +- `rules`: List of routing rules (e.g., route YouTube traffic through VPN, block ads.com). + +### 🔹 8. loadSubscription({required String subscriptionLink}) +Loads a VPN subscription from the provided link. +- `subscriptionLink`: The subscription file URL. + +### 🔹 9. getSessionStatistics() +Returns statistics for the current VPN session (e.g., data usage, session duration). + +### 🔹 10. setAutoConnect({required bool enable}) +Enables or disables auto-connect functionality. +- `enable`: `true` to enable, `false` to disable. + +### 🔹 11. setKillSwitch({required bool enable}) +Enables or disables the kill switch. +- `enable`: `true` to enable, `false` to disable. + +--- + +## 🔔 Events + +### 📡 1. onConnectionStatusChanged +Triggered when VPN connection status changes. +- Payload: `ConnectionStatus` (e.g., `connected`, `disconnected`, `error`). + +### ⚠️ 2. onError +Triggered when an error occurs. +- Payload: `ErrorCode` and `ErrorMessage`. + +### 🔄 3. onServerSwitched +Triggered when the VPN server is switched. +- Payload: `newServerAddress`. + +### 📊 4. onPingResult +Triggered when a ping operation completes. +- Payload: `serverIndex` and `latencyInMs`. + +### 🔑 5. onSubscriptionLoaded +Triggered when a subscription is loaded successfully. +- Payload: `subscriptionDetails`. + +### 📈 6. onDataUsageUpdated +Triggered periodically with updated data usage statistics. +- Payload: `dataUsed` and `dataRemaining`. + +### 📌 7. onRoutingRulesApplied +Triggered when routing rules are applied. +- Payload: `List`. + +### 🚨 8. onKillSwitchTriggered +Triggered when the kill switch is activated. + +--- + +## 📂 Data Models + +### 🔹 1. ConnectionStatus +Enum: `connecting`, `connected`, `disconnected`, `error`. + +### 🔹 2. Server +- `address` +- `latency` +- `location` +- `isPreferred` + +### 🔹 3. RoutingRule +- `appName` +- `domain` +- `action` (`block`, `allow`, `routeThroughVPN`). + +### 🔹 4. ProxyConfig +- `type` (`socks5`, `http`) +- `address` +- `port` +- `credentials` + +### 🔹 5. ErrorCode +Enum: `invalidCredentials`, `serverUnavailable`, `subscriptionExpired`, `unknownError`. + +### 🔹 6. SubscriptionDetails +- `expiryDate` +- `dataLimit` +- `usedData` + +--- + +## 🤝 Contributing -This project is a starting point for a Flutter application. +Contributions are welcome! Feel free to submit issues and pull requests. -A few resources to get you started if this is your first Flutter project: +## 📜 License -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) +This project is licensed under ... -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. From 04532da996313450824cef846e101dbaeb60f24b Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 27 Mar 2025 12:50:27 +0700 Subject: [PATCH 13/77] controller->engine --- README.md | 28 +++++++++---------- android/app/build.gradle.kts | 4 +-- android/app/src/main/AndroidManifest.xml | 2 +- .../MainActivity.kt | 2 +- ios/Runner.xcodeproj/project.pbxproj | 12 ++++---- ios/Runner/Info.plist | 4 +-- lib/main.dart | 6 ++-- pubspec.yaml | 2 +- test/widget_test.dart | 2 +- web/index.html | 4 +-- web/manifest.json | 4 +-- 11 files changed, 35 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 3b73ee88..6c791e59 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -# 🚀 VPN Client Controller Flutter +# 🚀 VPN Client Engine Flutter ## 🌍 Overview -VPN Client Controller Flutter is a Flutter wrapper for managing VPN connections with an intuitive API. It provides seamless integration with various platforms, allowing developers to control VPN connections efficiently. -![VPN Client Controller](https://raw.githubusercontent.com/VPNclient/.github/refs/heads/main/assets/vpnclient_scheme2.png) +VPN Client Engine Flutter is a Flutter wrapper for managing VPN connections with an intuitive API. It provides seamless integration with various platforms, allowing developers to control VPN connections efficiently. +![VPN Client Engine](https://raw.githubusercontent.com/VPNclient/.github/refs/heads/main/assets/vpnclient_scheme2.png) ### ✅ Supported Platforms - iOS 15+ (iPhone, iPad, MacOS M) @@ -14,34 +14,34 @@ VPN Client Controller Flutter is a Flutter wrapper for managing VPN connections ## 📥 Getting Started -To start using VPN Client Controller Flutter, ensure you have Flutter installed and set up your project accordingly. +To start using VPN Client Engine Flutter, ensure you have Flutter installed and set up your project accordingly. ### 📦 Installation ```sh -flutter pub add vpnclient_controller +flutter pub add vpnclient_engine ``` ## 📌 Example Usage ```dart -// Initialize the controller -vpnController.initialize(); +// Initialize the Engine +VPNengine.initialize(); // Load subscription -vpnController.loadSubscription( +VPNengine.loadSubscription( subscriptionLink: "https://pastebin.com/raw/ZCYiJ98W" ); // Connect to a VPN server -vpnController.connect(index: 1); +VPNengine.connect(index: 1); // Listen for connection status changes -vpnController.onConnectionStatusChanged.listen((status) { +VPNengine.onConnectionStatusChanged.listen((status) { print("Connection status: $status"); }); // Set routing rules -vpnController.setRoutingRules( +VPNengine.setRoutingRules( rules: [ RoutingRule(appName: "YouTube", action: "routeThroughVPN"), RoutingRule(domain: "ads.com", action: "block"), @@ -49,8 +49,8 @@ vpnController.setRoutingRules( ); // Ping a server -vpnController.pingServer(index: 1); -vpnController.onPingResult.listen((result) { +VPNengine.pingServer(index: 1); +VPNengine.onPingResult.listen((result) { print("Ping result: ${result.latencyInMs} ms"); }); ``` @@ -60,7 +60,7 @@ vpnController.onPingResult.listen((result) { ## ⚙️ API Methods ### 🔹 1. initialize() -Initializes the VPN controller. This should be called before using any other method. +Initializes the VPN Engine. This should be called before using any other method. ### 🔹 2. connect({required int index}) Connects to the specified VPN server. diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 35cd5600..aa0fcc5d 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -6,7 +6,7 @@ plugins { } android { - namespace = "com.example.vpnclient_controller_flutter" + namespace = "com.example.vpnclient_engine_flutter" compileSdk = flutter.compileSdkVersion ndkVersion = flutter.ndkVersion @@ -21,7 +21,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId = "com.example.vpnclient_controller_flutter" + applicationId = "com.example.vpnclient_engine_flutter" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. minSdk = flutter.minSdkVersion diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index eec1cd60..d2b173e4 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - Vpnclient Controller Flutter + VPNclient Engine Flutter CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -13,7 +13,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - vpnclient_controller_flutter + vpnclient_engine_flutter CFBundlePackageType APPL CFBundleShortVersionString diff --git a/lib/main.dart b/lib/main.dart index 77a90ac1..59c0cc73 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,6 @@ import 'dart:async'; -class Controller { +class VPNengine { static String setTitle(int x) { switch (x) { case 1: @@ -23,6 +23,6 @@ class Controller { } void main() async { - await Controller.connect(); - await Controller.disconnect(); + await VPNengine.connect(); + await VPNengine.disconnect(); } \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 97213bca..5ca9b329 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,4 +1,4 @@ -name: vpnclient_controller_flutter +name: vpnclient_engine_flutter description: "A new Flutter project." # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. diff --git a/test/widget_test.dart b/test/widget_test.dart index 6afc4f58..a1cf4798 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -8,7 +8,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:vpnclient_controller_flutter/main.dart'; +import 'package:vpnclient_engine_flutter/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { diff --git a/web/index.html b/web/index.html index fd5e0bf6..55e5e310 100644 --- a/web/index.html +++ b/web/index.html @@ -23,13 +23,13 @@ - + - vpnclient_controller_flutter + vpnclient_engine_flutter diff --git a/web/manifest.json b/web/manifest.json index 072f63b8..17f2c0f6 100644 --- a/web/manifest.json +++ b/web/manifest.json @@ -1,6 +1,6 @@ { - "name": "vpnclient_controller_flutter", - "short_name": "vpnclient_controller_flutter", + "name": "vpnclient_engine_flutter", + "short_name": "vpnclient_engine_flutter", "start_url": ".", "display": "standalone", "background_color": "#0175C2", From 9e2ea816a7545d7bb4bc7e723bd21a8467f0cd71 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 27 Mar 2025 13:08:36 +0700 Subject: [PATCH 14/77] controller->engine --- README.md | 18 +++++++++--------- lib/main.dart | 6 +++--- pubspec.yaml | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 6c791e59..0392a78f 100644 --- a/README.md +++ b/README.md @@ -18,30 +18,30 @@ To start using VPN Client Engine Flutter, ensure you have Flutter installed and ### 📦 Installation ```sh -flutter pub add vpnclient_engine +flutter pub add vpnclient_engine_flutter ``` ## 📌 Example Usage ```dart // Initialize the Engine -VPNengine.initialize(); +VPNclientEngine.initialize(); // Load subscription -VPNengine.loadSubscription( +VPNclientEngine.loadSubscription( subscriptionLink: "https://pastebin.com/raw/ZCYiJ98W" ); // Connect to a VPN server -VPNengine.connect(index: 1); +VPNclientEngine.connect(index: 1); // Listen for connection status changes -VPNengine.onConnectionStatusChanged.listen((status) { +VPNclientEngine.onConnectionStatusChanged.listen((status) { print("Connection status: $status"); }); // Set routing rules -VPNengine.setRoutingRules( +VPNclientEngine.setRoutingRules( rules: [ RoutingRule(appName: "YouTube", action: "routeThroughVPN"), RoutingRule(domain: "ads.com", action: "block"), @@ -49,8 +49,8 @@ VPNengine.setRoutingRules( ); // Ping a server -VPNengine.pingServer(index: 1); -VPNengine.onPingResult.listen((result) { +VPNclientEngine.pingServer(index: 1); +VPNclientEngine.onPingResult.listen((result) { print("Ping result: ${result.latencyInMs} ms"); }); ``` @@ -60,7 +60,7 @@ VPNengine.onPingResult.listen((result) { ## ⚙️ API Methods ### 🔹 1. initialize() -Initializes the VPN Engine. This should be called before using any other method. +Initializes the VPN Client Engine. This should be called before using any other method. ### 🔹 2. connect({required int index}) Connects to the specified VPN server. diff --git a/lib/main.dart b/lib/main.dart index 59c0cc73..d86cbe05 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,6 @@ import 'dart:async'; -class VPNengine { +class VPNclientEngine { static String setTitle(int x) { switch (x) { case 1: @@ -23,6 +23,6 @@ class VPNengine { } void main() async { - await VPNengine.connect(); - await VPNengine.disconnect(); + await VPNclientEngine.connect(); + await VPNclientEngine.disconnect(); } \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 5ca9b329..f10ccc9e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: vpnclient_engine_flutter -description: "A new Flutter project." +description: "VPNclient Engine Flutter" # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev From 39890a3a7cb2287676dfbeffdc4682777c3090f3 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 27 Mar 2025 14:03:37 +0700 Subject: [PATCH 15/77] connection status + statistics --- README.md | 14 +++++++------- lib/main.dart | 13 +++++++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 0392a78f..fd044d1b 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,8 @@ flutter pub add vpnclient_engine_flutter VPNclientEngine.initialize(); // Load subscription -VPNclientEngine.loadSubscription( - subscriptionLink: "https://pastebin.com/raw/ZCYiJ98W" +VPNclientEngine.loadSubscriptions( + subscriptionLinks: ["https://pastebin.com/raw/ZCYiJ98W"] ); // Connect to a VPN server @@ -62,9 +62,9 @@ VPNclientEngine.onPingResult.listen((result) { ### 🔹 1. initialize() Initializes the VPN Client Engine. This should be called before using any other method. -### 🔹 2. connect({required int index}) +### 🔹 2. connect({required int subscriptionIndex,required int serverIndex}) Connects to the specified VPN server. -- `index`: Index of the server from `getServerList()`. +- `index`: Index of the server from `getServerList()`.s ### 🔹 3. disconnect() Disconnects the active VPN connection. @@ -84,9 +84,9 @@ Pings a specific server to check latency. Configures routing rules for apps or domains. - `rules`: List of routing rules (e.g., route YouTube traffic through VPN, block ads.com). -### 🔹 8. loadSubscription({required String subscriptionLink}) -Loads a VPN subscription from the provided link. -- `subscriptionLink`: The subscription file URL. +### 🔹 8. loadSubscriptions({required List subscriptionLinks}) +Loads VPN subscriptions from the provided list of links. +- `subscriptionLinks`: A list of subscription file URLs. ### 🔹 9. getSessionStatistics() Returns statistics for the current VPN session (e.g., data usage, session duration). diff --git a/lib/main.dart b/lib/main.dart index d86cbe05..cb0a0898 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,17 @@ import 'dart:async'; +enum ConnectionStatus { connecting, connected, disconnected, error } + +class SessionStatistics { + final Duration sessionDuration; + final int dataInBytes; + final int dataOutBytes; + + SessionStatistics({ + required this.sessionDuration, + required this.dataInBytes, + required this.dataOutBytes, + }); +} class VPNclientEngine { static String setTitle(int x) { From b63500ad0d2a49f0abbc966b0a742460b1915cbf Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 27 Mar 2025 14:57:42 +0700 Subject: [PATCH 16/77] main() --- README.md | 67 +++++++++++++++++++++++++++++---------------------- lib/main.dart | 38 ++++++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index fd044d1b..57596d79 100644 --- a/README.md +++ b/README.md @@ -24,35 +24,44 @@ flutter pub add vpnclient_engine_flutter ## 📌 Example Usage ```dart -// Initialize the Engine -VPNclientEngine.initialize(); - -// Load subscription -VPNclientEngine.loadSubscriptions( - subscriptionLinks: ["https://pastebin.com/raw/ZCYiJ98W"] -); - -// Connect to a VPN server -VPNclientEngine.connect(index: 1); - -// Listen for connection status changes -VPNclientEngine.onConnectionStatusChanged.listen((status) { - print("Connection status: $status"); -}); - -// Set routing rules -VPNclientEngine.setRoutingRules( - rules: [ - RoutingRule(appName: "YouTube", action: "routeThroughVPN"), - RoutingRule(domain: "ads.com", action: "block"), - ], -); - -// Ping a server -VPNclientEngine.pingServer(index: 1); -VPNclientEngine.onPingResult.listen((result) { - print("Ping result: ${result.latencyInMs} ms"); -}); + // Initialize the Engine + VPNclientEngine.initialize(); + + // Clear subscriptions + VPNclientEngine.ClearSubscriptions(); + + // Add subscription + VPNclientEngine.addSubscription(subscriptionURL: ["https://pastebin.com/raw/ZCYiJ98W"]); + + // Update subscription + await VPNclientEngine.updateSubscription(subscriptionIndex: 0); + + // Listen for connection status changes + VPNclientEngine.onConnectionStatusChanged.listen((status) { + print("Connection status: $status"); + }); + + await VPNclientEngine.connect(subscriptionIndex: 0, serverIndex: 1); + + // Set routing rules + VPNclientEngine.setRoutingRules( + rules: [ + RoutingRule(appName: "YouTube", action: "proxy"), + RoutingRule(appName: "google.com", action: "direct"), + RoutingRule(domain: "ads.com", action: "block"), + ], + ); + + // Ping a server + VPNclientEngine.pingServer(subscriptionIndex: 0, index: 1); + + VPNclientEngine.onPingResult.listen((result) { + print("Ping result: ${result.latencyInMs} ms"); + }); + + await Future.delayed(Duration(seconds: 10)); + + await VPNclientEngine.disconnect(); ``` --- diff --git a/lib/main.dart b/lib/main.dart index cb0a0898..2a7b8ba7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -36,6 +36,42 @@ class VPNclientEngine { } void main() async { - await VPNclientEngine.connect(); + // Initialize the Engine + VPNclientEngine.initialize(); + + // Clear subscriptions + VPNclientEngine.ClearSubscriptions(); + + // Add subscription + VPNclientEngine.addSubscription(subscriptionURL: ["https://pastebin.com/raw/ZCYiJ98W"]); + + // Update subscription + await VPNclientEngine.updateSubscription(subscriptionIndex: 0); + + // Listen for connection status changes + VPNclientEngine.onConnectionStatusChanged.listen((status) { + print("Connection status: $status"); + }); + + await VPNclientEngine.connect(subscriptionIndex: 0, serverIndex: 1); + + // Set routing rules + VPNclientEngine.setRoutingRules( + rules: [ + RoutingRule(appName: "YouTube", action: "proxy"), + RoutingRule(appName: "google.com", action: "direct"), + RoutingRule(domain: "ads.com", action: "block"), + ], + ); + + // Ping a server + VPNclientEngine.pingServer(subscriptionIndex: 0, index: 1); + + VPNclientEngine.onPingResult.listen((result) { + print("Ping result: ${result.latencyInMs} ms"); + }); + + await Future.delayed(Duration(seconds: 10)); + await VPNclientEngine.disconnect(); } \ No newline at end of file From eae383a597d4ea038c70828719989c189310699d Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 27 Mar 2025 15:06:31 +0700 Subject: [PATCH 17/77] main functions --- lib/main.dart | 83 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 79 insertions(+), 4 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 2a7b8ba7..bace8729 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -13,6 +13,19 @@ class SessionStatistics { }); } +class RoutingRule { + final String? appName; + final String? domain; + final String action; // proxy, direct, block + + RoutingRule({this.appName, this.domain, required this.action}); +} + +class PingResult { + final int latencyInMs; + PingResult(this.latencyInMs); +} + class VPNclientEngine { static String setTitle(int x) { switch (x) { @@ -24,14 +37,76 @@ class VPNclientEngine { return 'Hello from backend!'; } - static Future connect() async { - print('Команда на подключение отправлена'); + + static final StreamController _connectionStatusController = + StreamController.broadcast(); + static final StreamController _pingResultController = + StreamController.broadcast(); + + static List _subscriptions = []; + + + static void initialize() { + print('VPNclient Engine initialized'); + } + + static void ClearSubscriptions() { + _subscriptions.clear(); + print('All subscriptions cleared'); + } + + static void addSubscription({required String subscriptionURL}) { + _subscriptions.add(subscriptionURL); + print('Subscription added: $subscriptionURL'); + } + + static void addSubscriptions({required List subscriptionURLs}) { + _subscriptions.addAll(subscriptionURLs); + print('Subscriptions added: ${subscriptionURLs.join(", ")}'); + } + + static Future updateSubscription({required int subscriptionIndex}) async { + if (subscriptionIndex < 0 || subscriptionIndex >= _subscriptions.length) { + print('Invalid subscription index'); + return; + } + print('Updating subscription at index $subscriptionIndex'); + await Future.delayed(Duration(seconds: 3)); + print('Subscription updated successfully'); + } + + static Stream get onConnectionStatusChanged => _connectionStatusController.stream; + static Stream get onPingResult => _pingResultController.stream; + + static Future connect({required int subscriptionIndex, required int serverIndex}) async { + print('Connecting to subscription $subscriptionIndex, server $serverIndex...'); + _connectionStatusController.add(ConnectionStatus.connecting); await Future.delayed(Duration(seconds: 5)); - print('Успешное подключение'); + _connectionStatusController.add(ConnectionStatus.connected); + print('Successfully connected'); } static Future disconnect() async { - print('Успешное отключение'); + _connectionStatusController.add(ConnectionStatus.disconnected); + print('Disconnected successfully'); + } + + static void setRoutingRules({required List rules}) { + for (var rule in rules) { + if (rule.appName != null) { + print('Routing rule for app ${rule.appName}: ${rule.action}'); + } else if (rule.domain != null) { + print('Routing rule for domain ${rule.domain}: ${rule.action}'); + } + } + } + + static void pingServer({required int subscriptionIndex, required int index}) async { + print('Pinging server at subscription $subscriptionIndex, server index $index...'); + await Future.delayed(Duration(seconds: 1)); + final result = PingResult(123); + _pingResultController.add(result); + print('Ping result: ${result.latencyInMs} ms'); } } From 97f371e706885fc53e8b0fb2c71256083a92e995 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 27 Mar 2025 15:10:51 +0700 Subject: [PATCH 18/77] main functions --- lib/main.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/main.dart b/lib/main.dart index bace8729..dba11b70 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -118,7 +118,8 @@ void main() async { VPNclientEngine.ClearSubscriptions(); // Add subscription - VPNclientEngine.addSubscription(subscriptionURL: ["https://pastebin.com/raw/ZCYiJ98W"]); + VPNclientEngine.addSubscription(subscriptionURL: "https://pastebin.com/raw/ZCYiJ98W"); + //VPNclientEngine.addSubscriptions(subscriptionURLs: ["https://pastebin.com/raw/ZCYiJ98W"]); // Update subscription await VPNclientEngine.updateSubscription(subscriptionIndex: 0); From 1734d08f024ad1d5681157ee15929d668911439d Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 27 Mar 2025 15:22:07 +0700 Subject: [PATCH 19/77] main functions --- README.md | 5 ++++- lib/main.dart | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 57596d79..a14519a3 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,8 @@ flutter pub add vpnclient_engine_flutter VPNclientEngine.ClearSubscriptions(); // Add subscription - VPNclientEngine.addSubscription(subscriptionURL: ["https://pastebin.com/raw/ZCYiJ98W"]); + VPNclientEngine.addSubscription(subscriptionURL: "https://pastebin.com/raw/ZCYiJ98W"); + //VPNclientEngine.addSubscriptions(subscriptionURLs: ["https://pastebin.com/raw/ZCYiJ98W"]); // Update subscription await VPNclientEngine.updateSubscription(subscriptionIndex: 0); @@ -41,6 +42,7 @@ flutter pub add vpnclient_engine_flutter print("Connection status: $status"); }); + //Connect to server 1 await VPNclientEngine.connect(subscriptionIndex: 0, serverIndex: 1); // Set routing rules @@ -61,6 +63,7 @@ flutter pub add vpnclient_engine_flutter await Future.delayed(Duration(seconds: 10)); + //Disconnect await VPNclientEngine.disconnect(); ``` diff --git a/lib/main.dart b/lib/main.dart index dba11b70..aae6c9cd 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -129,6 +129,7 @@ void main() async { print("Connection status: $status"); }); + //Connect to server 1 await VPNclientEngine.connect(subscriptionIndex: 0, serverIndex: 1); // Set routing rules @@ -148,6 +149,7 @@ void main() async { }); await Future.delayed(Duration(seconds: 10)); - + + //Disconnect await VPNclientEngine.disconnect(); } \ No newline at end of file From 575182e1ce12aa6aab998bbeb45cfa57d73f62ab Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 27 Mar 2025 15:32:38 +0700 Subject: [PATCH 20/77] main functions --- lib/main.dart | 50 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index aae6c9cd..f3828399 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,6 @@ import 'dart:async'; +import 'package:http/http.dart' as http; + enum ConnectionStatus { connecting, connected, disconnected, error } class SessionStatistics { @@ -27,6 +29,8 @@ class PingResult { } class VPNclientEngine { + static List> _subscriptionServers = []; + static String setTitle(int x) { switch (x) { case 1: @@ -70,9 +74,47 @@ class VPNclientEngine { print('Invalid subscription index'); return; } - print('Updating subscription at index $subscriptionIndex'); - await Future.delayed(Duration(seconds: 3)); - print('Subscription updated successfully'); + + final url = _subscriptions[subscriptionIndex]; + print('Fetching subscription data from: $url'); + + try { + final response = await http.get(Uri.parse(url)); + + if (response.statusCode != 200) { + print('Failed to fetch subscription: HTTP ${response.statusCode}'); + return; + } + + final content = response.body.trim(); + + List servers = []; + + if (content.startsWith('[')) { + // JSON format + final jsonList = jsonDecode(content) as List; + for (var server in jsonList) { + servers.add(server.toString()); + } + print('Parsed JSON subscription: ${servers.length} servers loaded'); + } else { + // NEWLINE format + servers = content.split('\n').where((line) => line.trim().isNotEmpty).toList(); + print('Parsed NEWLINE subscription: ${servers.length} servers loaded'); + } + + // Ensure the servers list matches the subscriptions list size + while (_subscriptionServers.length <= subscriptionIndex) { + _subscriptionServers.add([]); + } + + // Save fetched servers to specific subscription index + _subscriptionServers[subscriptionIndex] = servers; + + print('Subscription #$subscriptionIndex servers updated successfully'); + } catch (e) { + print('Error updating subscription: $e'); + } } static Stream get onConnectionStatusChanged => _connectionStatusController.stream; @@ -149,7 +191,7 @@ void main() async { }); await Future.delayed(Duration(seconds: 10)); - + //Disconnect await VPNclientEngine.disconnect(); } \ No newline at end of file From 4099d6a0a66deaefec3b0dc976dccde65a539ff5 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 27 Mar 2025 15:39:05 +0700 Subject: [PATCH 21/77] subscriptions --- lib/main.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/main.dart b/lib/main.dart index f3828399..52c5d069 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,7 @@ import 'dart:async'; import 'package:http/http.dart' as http; +import 'dart:convert'; + enum ConnectionStatus { connecting, connected, disconnected, error } @@ -79,6 +81,7 @@ class VPNclientEngine { print('Fetching subscription data from: $url'); try { + //Сейчас при поднятом VPN обновление подписки пойдет через туннель. Позже необходимо реализовать разные механизмы обновления (только через туннель/только напрямую/комбинированный) final response = await http.get(Uri.parse(url)); if (response.statusCode != 200) { From 0f9452386ee4d0f880d90ba16114e5a68c406557 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 27 Mar 2025 15:42:54 +0700 Subject: [PATCH 22/77] ping server --- lib/main.dart | 35 ++++++++++++++++++++++++++++++----- pubspec.yaml | 3 ++- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 52c5d069..615de981 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -147,11 +147,36 @@ class VPNclientEngine { } static void pingServer({required int subscriptionIndex, required int index}) async { - print('Pinging server at subscription $subscriptionIndex, server index $index...'); - await Future.delayed(Duration(seconds: 1)); - final result = PingResult(123); - _pingResultController.add(result); - print('Ping result: ${result.latencyInMs} ms'); + if (subscriptionIndex < 0 || subscriptionIndex >= _subscriptionServers.length) { + print('Invalid subscription index'); + return; + } + + if (index < 0 || index >= _subscriptionServers[subscriptionIndex].length) { + print('Invalid server index'); + return; + } + + final serverAddress = _subscriptionServers[subscriptionIndex][index]; + print('Pinging server: $serverAddress'); + + try { + final ping = Ping(serverAddress, count: 3); + final pingData = await ping.stream.firstWhere((data) => data.response != null); + + if (pingData.response != null) { + final latency = pingData.response!.time!.inMilliseconds; + final result = PingResult(latency); + _pingResultController.add(result); + print('Ping result: ${result.latencyInMs} ms'); + } else { + print('Ping failed: No response'); + _pingResultController.add(PingResult(-1)); // Indicate error with -1 + } + } catch (e) { + print('Ping error: $e'); + _pingResultController.add(PingResult(-1)); + } } } diff --git a/pubspec.yaml b/pubspec.yaml index f10ccc9e..452f1015 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,7 +34,8 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 - + dart_ping: ^9.0.2 + dev_dependencies: flutter_test: sdk: flutter From d3fbf71122f9a0dc0f7085cd662d9aa2d37e6cba Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 27 Mar 2025 15:47:53 +0700 Subject: [PATCH 23/77] ping server --- lib/main.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/main.dart b/lib/main.dart index 615de981..31120be3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'dart:async'; -import 'package:http/http.dart' as http; import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:dart_ping/dart_ping.dart'; enum ConnectionStatus { connecting, connected, disconnected, error } From 84510abb89ef606d7393c61d885de96022ec99be Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 27 Mar 2025 15:50:55 +0700 Subject: [PATCH 24/77] sh scripts --- pubspec.lock | 8 ++++++++ pubspec.yaml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pubspec.lock b/pubspec.lock index c2c57f7f..d2eebec9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -49,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dart_ping: + dependency: "direct main" + description: + name: dart_ping + sha256: "2f5418d0a5c64e53486caaac78677b25725b1e13c33c5be834ce874ea18bd24f" + url: "https://pub.dev" + source: hosted + version: "9.0.1" fake_async: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 452f1015..12b183be 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,7 +34,7 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 - dart_ping: ^9.0.2 + dart_ping: ^9.0.1 dev_dependencies: flutter_test: From ca7e8dc804a13b99d6ad98b5e4b999df71da396f Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 27 Mar 2025 16:31:11 +0700 Subject: [PATCH 25/77] seperate files --- lib/engine.dart | 182 +++++++++++++++++++++++++++++++++++++++++ lib/main.dart | 180 +--------------------------------------- lib/ping.dart | 0 lib/subscriptions.dart | 0 4 files changed, 183 insertions(+), 179 deletions(-) create mode 100644 lib/engine.dart create mode 100644 lib/ping.dart create mode 100644 lib/subscriptions.dart diff --git a/lib/engine.dart b/lib/engine.dart new file mode 100644 index 00000000..4fda0b79 --- /dev/null +++ b/lib/engine.dart @@ -0,0 +1,182 @@ +import 'dart:async'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:dart_ping/dart_ping.dart'; + + +enum ConnectionStatus { connecting, connected, disconnected, error } + +class SessionStatistics { + final Duration sessionDuration; + final int dataInBytes; + final int dataOutBytes; + + SessionStatistics({ + required this.sessionDuration, + required this.dataInBytes, + required this.dataOutBytes, + }); +} + +class RoutingRule { + final String? appName; + final String? domain; + final String action; // proxy, direct, block + + RoutingRule({this.appName, this.domain, required this.action}); +} + +class PingResult { + final int latencyInMs; + PingResult(this.latencyInMs); +} + +class VPNclientEngine { + static List> _subscriptionServers = []; + + static String setTitle(int x) { + switch (x) { + case 1: + return 'Super HIT'; + case 2: + return 'VPNClient'; + } + return 'Hello from backend!'; + } + + + static final StreamController _connectionStatusController = + StreamController.broadcast(); + static final StreamController _pingResultController = + StreamController.broadcast(); + + static List _subscriptions = []; + + + static void initialize() { + print('VPNclient Engine initialized'); + } + + static void ClearSubscriptions() { + _subscriptions.clear(); + print('All subscriptions cleared'); + } + + static void addSubscription({required String subscriptionURL}) { + _subscriptions.add(subscriptionURL); + print('Subscription added: $subscriptionURL'); + } + + static void addSubscriptions({required List subscriptionURLs}) { + _subscriptions.addAll(subscriptionURLs); + print('Subscriptions added: ${subscriptionURLs.join(", ")}'); + } + + static Future updateSubscription({required int subscriptionIndex}) async { + if (subscriptionIndex < 0 || subscriptionIndex >= _subscriptions.length) { + print('Invalid subscription index'); + return; + } + + final url = _subscriptions[subscriptionIndex]; + print('Fetching subscription data from: $url'); + + try { + //Сейчас при поднятом VPN обновление подписки пойдет через туннель. Позже необходимо реализовать разные механизмы обновления (только через туннель/только напрямую/комбинированный) + final response = await http.get(Uri.parse(url)); + + if (response.statusCode != 200) { + print('Failed to fetch subscription: HTTP ${response.statusCode}'); + return; + } + + final content = response.body.trim(); + + List servers = []; + + if (content.startsWith('[')) { + // JSON format + final jsonList = jsonDecode(content) as List; + for (var server in jsonList) { + servers.add(server.toString()); + } + print('Parsed JSON subscription: ${servers.length} servers loaded'); + } else { + // NEWLINE format + servers = content.split('\n').where((line) => line.trim().isNotEmpty).toList(); + print('Parsed NEWLINE subscription: ${servers.length} servers loaded'); + } + + // Ensure the servers list matches the subscriptions list size + while (_subscriptionServers.length <= subscriptionIndex) { + _subscriptionServers.add([]); + } + + // Save fetched servers to specific subscription index + _subscriptionServers[subscriptionIndex] = servers; + + print('Subscription #$subscriptionIndex servers updated successfully'); + } catch (e) { + print('Error updating subscription: $e'); + } + } + + static Stream get onConnectionStatusChanged => _connectionStatusController.stream; + static Stream get onPingResult => _pingResultController.stream; + + static Future connect({required int subscriptionIndex, required int serverIndex}) async { + print('Connecting to subscription $subscriptionIndex, server $serverIndex...'); + _connectionStatusController.add(ConnectionStatus.connecting); + await Future.delayed(Duration(seconds: 5)); + _connectionStatusController.add(ConnectionStatus.connected); + print('Successfully connected'); + } + + static Future disconnect() async { + _connectionStatusController.add(ConnectionStatus.disconnected); + print('Disconnected successfully'); + } + + static void setRoutingRules({required List rules}) { + for (var rule in rules) { + if (rule.appName != null) { + print('Routing rule for app ${rule.appName}: ${rule.action}'); + } else if (rule.domain != null) { + print('Routing rule for domain ${rule.domain}: ${rule.action}'); + } + } + } + + static void pingServer({required int subscriptionIndex, required int index}) async { + if (subscriptionIndex < 0 || subscriptionIndex >= _subscriptionServers.length) { + print('Invalid subscription index'); + return; + } + + if (index < 0 || index >= _subscriptionServers[subscriptionIndex].length) { + print('Invalid server index'); + return; + } + + final serverAddress = _subscriptionServers[subscriptionIndex][index]; + print('Pinging server: $serverAddress'); + + try { + final ping = Ping(serverAddress, count: 3); + final pingData = await ping.stream.firstWhere((data) => data.response != null); + + if (pingData.response != null) { + final latency = pingData.response!.time!.inMilliseconds; + final result = PingResult(latency); + _pingResultController.add(result); + print('Ping result: ${result.latencyInMs} ms'); + } else { + print('Ping failed: No response'); + _pingResultController.add(PingResult(-1)); // Indicate error with -1 + } + } catch (e) { + print('Ping error: $e'); + _pingResultController.add(PingResult(-1)); + } + } +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 31120be3..754a2aed 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,185 +1,7 @@ import 'dart:async'; -import 'dart:convert'; -import 'package:http/http.dart' as http; -import 'package:dart_ping/dart_ping.dart'; +import 'engine/engine.dart'; -enum ConnectionStatus { connecting, connected, disconnected, error } - -class SessionStatistics { - final Duration sessionDuration; - final int dataInBytes; - final int dataOutBytes; - - SessionStatistics({ - required this.sessionDuration, - required this.dataInBytes, - required this.dataOutBytes, - }); -} - -class RoutingRule { - final String? appName; - final String? domain; - final String action; // proxy, direct, block - - RoutingRule({this.appName, this.domain, required this.action}); -} - -class PingResult { - final int latencyInMs; - PingResult(this.latencyInMs); -} - -class VPNclientEngine { - static List> _subscriptionServers = []; - - static String setTitle(int x) { - switch (x) { - case 1: - return 'Super HIT'; - case 2: - return 'VPNClient'; - } - return 'Hello from backend!'; - } - - - static final StreamController _connectionStatusController = - StreamController.broadcast(); - static final StreamController _pingResultController = - StreamController.broadcast(); - - static List _subscriptions = []; - - - static void initialize() { - print('VPNclient Engine initialized'); - } - - static void ClearSubscriptions() { - _subscriptions.clear(); - print('All subscriptions cleared'); - } - - static void addSubscription({required String subscriptionURL}) { - _subscriptions.add(subscriptionURL); - print('Subscription added: $subscriptionURL'); - } - - static void addSubscriptions({required List subscriptionURLs}) { - _subscriptions.addAll(subscriptionURLs); - print('Subscriptions added: ${subscriptionURLs.join(", ")}'); - } - - static Future updateSubscription({required int subscriptionIndex}) async { - if (subscriptionIndex < 0 || subscriptionIndex >= _subscriptions.length) { - print('Invalid subscription index'); - return; - } - - final url = _subscriptions[subscriptionIndex]; - print('Fetching subscription data from: $url'); - - try { - //Сейчас при поднятом VPN обновление подписки пойдет через туннель. Позже необходимо реализовать разные механизмы обновления (только через туннель/только напрямую/комбинированный) - final response = await http.get(Uri.parse(url)); - - if (response.statusCode != 200) { - print('Failed to fetch subscription: HTTP ${response.statusCode}'); - return; - } - - final content = response.body.trim(); - - List servers = []; - - if (content.startsWith('[')) { - // JSON format - final jsonList = jsonDecode(content) as List; - for (var server in jsonList) { - servers.add(server.toString()); - } - print('Parsed JSON subscription: ${servers.length} servers loaded'); - } else { - // NEWLINE format - servers = content.split('\n').where((line) => line.trim().isNotEmpty).toList(); - print('Parsed NEWLINE subscription: ${servers.length} servers loaded'); - } - - // Ensure the servers list matches the subscriptions list size - while (_subscriptionServers.length <= subscriptionIndex) { - _subscriptionServers.add([]); - } - - // Save fetched servers to specific subscription index - _subscriptionServers[subscriptionIndex] = servers; - - print('Subscription #$subscriptionIndex servers updated successfully'); - } catch (e) { - print('Error updating subscription: $e'); - } - } - - static Stream get onConnectionStatusChanged => _connectionStatusController.stream; - static Stream get onPingResult => _pingResultController.stream; - - static Future connect({required int subscriptionIndex, required int serverIndex}) async { - print('Connecting to subscription $subscriptionIndex, server $serverIndex...'); - _connectionStatusController.add(ConnectionStatus.connecting); - await Future.delayed(Duration(seconds: 5)); - _connectionStatusController.add(ConnectionStatus.connected); - print('Successfully connected'); - } - - static Future disconnect() async { - _connectionStatusController.add(ConnectionStatus.disconnected); - print('Disconnected successfully'); - } - - static void setRoutingRules({required List rules}) { - for (var rule in rules) { - if (rule.appName != null) { - print('Routing rule for app ${rule.appName}: ${rule.action}'); - } else if (rule.domain != null) { - print('Routing rule for domain ${rule.domain}: ${rule.action}'); - } - } - } - - static void pingServer({required int subscriptionIndex, required int index}) async { - if (subscriptionIndex < 0 || subscriptionIndex >= _subscriptionServers.length) { - print('Invalid subscription index'); - return; - } - - if (index < 0 || index >= _subscriptionServers[subscriptionIndex].length) { - print('Invalid server index'); - return; - } - - final serverAddress = _subscriptionServers[subscriptionIndex][index]; - print('Pinging server: $serverAddress'); - - try { - final ping = Ping(serverAddress, count: 3); - final pingData = await ping.stream.firstWhere((data) => data.response != null); - - if (pingData.response != null) { - final latency = pingData.response!.time!.inMilliseconds; - final result = PingResult(latency); - _pingResultController.add(result); - print('Ping result: ${result.latencyInMs} ms'); - } else { - print('Ping failed: No response'); - _pingResultController.add(PingResult(-1)); // Indicate error with -1 - } - } catch (e) { - print('Ping error: $e'); - _pingResultController.add(PingResult(-1)); - } - } -} void main() async { // Initialize the Engine diff --git a/lib/ping.dart b/lib/ping.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/subscriptions.dart b/lib/subscriptions.dart new file mode 100644 index 00000000..e69de29b From 8963ff800fc8df1e702e782ab556f1c86c46a3a8 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 27 Mar 2025 16:42:34 +0700 Subject: [PATCH 26/77] seperate files --- lib/main.dart | 2 +- lib/vpnclient_engine.dart | 3 +++ lib/{ => vpnclient_engine}/engine.dart | 0 lib/{ => vpnclient_engine}/ping.dart | 0 lib/{ => vpnclient_engine}/subscriptions.dart | 0 pubs.sh | 2 ++ 6 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 lib/vpnclient_engine.dart rename lib/{ => vpnclient_engine}/engine.dart (100%) rename lib/{ => vpnclient_engine}/ping.dart (100%) rename lib/{ => vpnclient_engine}/subscriptions.dart (100%) create mode 100755 pubs.sh diff --git a/lib/main.dart b/lib/main.dart index 754a2aed..07640d22 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,5 @@ import 'dart:async'; -import 'engine/engine.dart'; +import 'vpnclient_engine.dart'; diff --git a/lib/vpnclient_engine.dart b/lib/vpnclient_engine.dart new file mode 100644 index 00000000..f197d7c6 --- /dev/null +++ b/lib/vpnclient_engine.dart @@ -0,0 +1,3 @@ +export 'vpnclient_engine/engine.dart'; +export 'vpnclient_engine/ping.dart'; +export 'vpnclient_engine/subscriptions.dart'; \ No newline at end of file diff --git a/lib/engine.dart b/lib/vpnclient_engine/engine.dart similarity index 100% rename from lib/engine.dart rename to lib/vpnclient_engine/engine.dart diff --git a/lib/ping.dart b/lib/vpnclient_engine/ping.dart similarity index 100% rename from lib/ping.dart rename to lib/vpnclient_engine/ping.dart diff --git a/lib/subscriptions.dart b/lib/vpnclient_engine/subscriptions.dart similarity index 100% rename from lib/subscriptions.dart rename to lib/vpnclient_engine/subscriptions.dart diff --git a/pubs.sh b/pubs.sh new file mode 100755 index 00000000..67e11054 --- /dev/null +++ b/pubs.sh @@ -0,0 +1,2 @@ +flutter clean +flutter pub get From 045f3c66b55ffa1f32b3de0cd6f0d13a86950276 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 27 Mar 2025 19:55:48 +0700 Subject: [PATCH 27/77] project -> plugin --- .gitignore | 20 +- .metadata | 29 +- CHANGELOG.md | 3 + LICENSE | 1 + README.md | 12 + analysis_options.yaml | 24 - android/.gitignore | 21 +- android/build.gradle | 66 ++ android/settings.gradle | 1 + android/src/main/AndroidManifest.xml | 3 + .../VpnclientEngineFlutterPlugin.kt | 33 + .../VpnclientEngineFlutterPluginTest.kt | 27 + dev/1.txt | 1 - docs/1.txt | 1 - docs/assets/1.txt | 1 - docs/assets/vpnclient_controller.jpeg | Bin 71919 -> 0 bytes docs/controller.md | 145 ---- example/.gitignore | 45 ++ example/README.md | 16 + example/analysis_options.yaml | 28 + example/android/.gitignore | 14 + .../android}/app/build.gradle.kts | 4 +- .../app/src/debug/AndroidManifest.xml | 0 .../android}/app/src/main/AndroidManifest.xml | 2 +- .../MainActivity.kt | 2 +- .../res/drawable-v21/launch_background.xml | 0 .../main/res/drawable/launch_background.xml | 0 .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin .../app/src/main/res/values-night/styles.xml | 0 .../app/src/main/res/values/styles.xml | 0 .../app/src/profile/AndroidManifest.xml | 0 {android => example/android}/build.gradle.kts | 0 .../android}/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.properties | 0 .../android}/settings.gradle.kts | 0 .../plugin_integration_test.dart | 25 + example/ios/.gitignore | 34 + .../ios}/Flutter/AppFrameworkInfo.plist | 0 {ios => example/ios}/Flutter/Debug.xcconfig | 0 {ios => example/ios}/Flutter/Release.xcconfig | 0 .../ios}/Runner.xcodeproj/project.pbxproj | 18 +- .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 {ios => example/ios}/Runner/AppDelegate.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../Icon-App-1024x1024@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin .../Icon-App-83.5x83.5@2x.png | Bin .../LaunchImage.imageset/Contents.json | 0 .../LaunchImage.imageset/LaunchImage.png | Bin .../LaunchImage.imageset/LaunchImage@2x.png | Bin .../LaunchImage.imageset/LaunchImage@3x.png | Bin .../LaunchImage.imageset/README.md | 0 .../Runner/Base.lproj/LaunchScreen.storyboard | 0 .../ios}/Runner/Base.lproj/Main.storyboard | 13 +- {ios => example/ios}/Runner/Info.plist | 4 +- .../ios}/Runner/Runner-Bridging-Header.h | 0 example/ios/RunnerTests/RunnerTests.swift | 27 + example/lib/main.dart | 63 ++ example/linux/.gitignore | 1 + example/linux/CMakeLists.txt | 130 ++++ example/linux/flutter/CMakeLists.txt | 88 +++ example/linux/runner/CMakeLists.txt | 26 + example/linux/runner/main.cc | 6 + example/linux/runner/my_application.cc | 130 ++++ example/linux/runner/my_application.h | 18 + example/macos/.gitignore | 7 + example/macos/Flutter/Flutter-Debug.xcconfig | 1 + .../macos/Flutter/Flutter-Release.xcconfig | 1 + .../macos/Runner.xcodeproj/project.pbxproj | 705 ++++++++++++++++++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 99 +++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + example/macos/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 68 ++ .../AppIcon.appiconset/app_icon_1024.png | Bin 0 -> 102994 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 0 -> 5680 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 0 -> 520 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 0 -> 14142 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 0 -> 1066 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 0 -> 36406 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 0 -> 2218 bytes example/macos/Runner/Base.lproj/MainMenu.xib | 343 +++++++++ example/macos/Runner/Configs/AppInfo.xcconfig | 14 + example/macos/Runner/Configs/Debug.xcconfig | 2 + example/macos/Runner/Configs/Release.xcconfig | 2 + .../macos/Runner/Configs/Warnings.xcconfig | 13 + .../macos/Runner/DebugProfile.entitlements | 12 + example/macos/Runner/Info.plist | 32 + example/macos/Runner/MainFlutterWindow.swift | 15 + example/macos/Runner/Release.entitlements | 8 + example/macos/RunnerTests/RunnerTests.swift | 28 + example/pubspec.lock | 296 ++++++++ example/pubspec.yaml | 85 +++ {test => example/test}/widget_test.dart | 23 +- {web => example/web}/favicon.png | Bin {web => example/web}/icons/Icon-192.png | Bin {web => example/web}/icons/Icon-512.png | Bin .../web}/icons/Icon-maskable-192.png | Bin .../web}/icons/Icon-maskable-512.png | Bin {web => example/web}/index.html | 6 +- {web => example/web}/manifest.json | 6 +- example/windows/.gitignore | 17 + example/windows/CMakeLists.txt | 110 +++ example/windows/flutter/CMakeLists.txt | 109 +++ example/windows/runner/CMakeLists.txt | 40 + example/windows/runner/Runner.rc | 121 +++ example/windows/runner/flutter_window.cpp | 71 ++ example/windows/runner/flutter_window.h | 33 + example/windows/runner/main.cpp | 43 ++ example/windows/runner/resource.h | 16 + example/windows/runner/resources/app_icon.ico | Bin 0 -> 33772 bytes example/windows/runner/runner.exe.manifest | 14 + example/windows/runner/utils.cpp | 65 ++ example/windows/runner/utils.h | 19 + example/windows/runner/win32_window.cpp | 288 +++++++ example/windows/runner/win32_window.h | 102 +++ ios/.gitignore | 60 +- ios/Assets/.gitkeep | 0 .../VpnclientEngineFlutterPlugin.swift | 19 + ios/Resources/PrivacyInfo.xcprivacy | 14 + ios/RunnerTests/RunnerTests.swift | 12 - ios/vpnclient_engine_flutter.podspec | 29 + lib/vpnclient_engine_flutter.dart | 8 + ...nclient_engine_flutter_method_channel.dart | 17 + ...ent_engine_flutter_platform_interface.dart | 29 + lib/vpnclient_engine_flutter_web.dart | 26 + linux/CMakeLists.txt | 94 +++ .../vpnclient_engine_flutter_plugin.h | 26 + .../vpnclient_engine_flutter_plugin_test.cc | 31 + linux/vpnclient_engine_flutter_plugin.cc | 76 ++ .../vpnclient_engine_flutter_plugin_private.h | 10 + .../VpnclientEngineFlutterPlugin.swift | 19 + macos/Resources/PrivacyInfo.xcprivacy | 12 + macos/vpnclient_engine_flutter.podspec | 30 + pubs.sh | 2 - pubspec.lock | 39 +- pubspec.yaml | 92 ++- ...nt_engine_flutter_method_channel_test.dart | 27 + test/vpnclient_engine_flutter_test.dart | 29 + vpnclient_engine_flutter.iml | 17 + windows/.gitignore | 17 + windows/CMakeLists.txt | 100 +++ .../vpnclient_engine_flutter_plugin_c_api.h | 23 + .../vpnclient_engine_flutter_plugin_test.cpp | 43 ++ windows/vpnclient_engine_flutter_plugin.cpp | 59 ++ windows/vpnclient_engine_flutter_plugin.h | 31 + .../vpnclient_engine_flutter_plugin_c_api.cpp | 12 + 170 files changed, 4575 insertions(+), 360 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 android/build.gradle create mode 100644 android/settings.gradle create mode 100644 android/src/main/AndroidManifest.xml create mode 100644 android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt create mode 100644 android/src/test/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPluginTest.kt delete mode 100644 dev/1.txt delete mode 100644 docs/1.txt delete mode 100644 docs/assets/1.txt delete mode 100644 docs/assets/vpnclient_controller.jpeg delete mode 100644 docs/controller.md create mode 100644 example/.gitignore create mode 100644 example/README.md create mode 100644 example/analysis_options.yaml create mode 100644 example/android/.gitignore rename {android => example/android}/app/build.gradle.kts (88%) rename {android => example/android}/app/src/debug/AndroidManifest.xml (100%) rename {android => example/android}/app/src/main/AndroidManifest.xml (97%) rename {android/app/src/main/kotlin/com/example/vpnclient_controller_flutter => example/android/app/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter_example}/MainActivity.kt (56%) rename {android => example/android}/app/src/main/res/drawable-v21/launch_background.xml (100%) rename {android => example/android}/app/src/main/res/drawable/launch_background.xml (100%) rename {android => example/android}/app/src/main/res/mipmap-hdpi/ic_launcher.png (100%) rename {android => example/android}/app/src/main/res/mipmap-mdpi/ic_launcher.png (100%) rename {android => example/android}/app/src/main/res/mipmap-xhdpi/ic_launcher.png (100%) rename {android => example/android}/app/src/main/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {android => example/android}/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename {android => example/android}/app/src/main/res/values-night/styles.xml (100%) rename {android => example/android}/app/src/main/res/values/styles.xml (100%) rename {android => example/android}/app/src/profile/AndroidManifest.xml (100%) rename {android => example/android}/build.gradle.kts (100%) rename {android => example/android}/gradle.properties (100%) rename {android => example/android}/gradle/wrapper/gradle-wrapper.properties (100%) rename {android => example/android}/settings.gradle.kts (100%) create mode 100644 example/integration_test/plugin_integration_test.dart create mode 100644 example/ios/.gitignore rename {ios => example/ios}/Flutter/AppFrameworkInfo.plist (100%) rename {ios => example/ios}/Flutter/Debug.xcconfig (100%) rename {ios => example/ios}/Flutter/Release.xcconfig (100%) rename {ios => example/ios}/Runner.xcodeproj/project.pbxproj (97%) rename {ios => example/ios}/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata (100%) rename {ios => example/ios}/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {ios => example/ios}/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename {ios => example/ios}/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (100%) rename {ios => example/ios}/Runner.xcworkspace/contents.xcworkspacedata (100%) rename {ios => example/ios}/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {ios => example/ios}/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename {ios => example/ios}/Runner/AppDelegate.swift (100%) rename {ios => example/ios}/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename {ios => example/ios}/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png (100%) rename {ios => example/ios}/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png (100%) rename {ios => example/ios}/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png (100%) rename {ios => example/ios}/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png (100%) rename {ios => example/ios}/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png (100%) rename {ios => example/ios}/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png (100%) rename {ios => example/ios}/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png (100%) rename {ios => example/ios}/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png (100%) rename {ios => example/ios}/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png (100%) rename {ios => example/ios}/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png (100%) rename {ios => example/ios}/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png (100%) rename {ios => example/ios}/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png (100%) rename {ios => example/ios}/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png (100%) rename {ios => example/ios}/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png (100%) rename {ios => example/ios}/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png (100%) rename {ios => example/ios}/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json (100%) rename {ios => example/ios}/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png (100%) rename {ios => example/ios}/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png (100%) rename {ios => example/ios}/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png (100%) rename {ios => example/ios}/Runner/Assets.xcassets/LaunchImage.imageset/README.md (100%) rename {ios => example/ios}/Runner/Base.lproj/LaunchScreen.storyboard (100%) rename {ios => example/ios}/Runner/Base.lproj/Main.storyboard (64%) rename {ios => example/ios}/Runner/Info.plist (94%) rename {ios => example/ios}/Runner/Runner-Bridging-Header.h (100%) create mode 100644 example/ios/RunnerTests/RunnerTests.swift create mode 100644 example/lib/main.dart create mode 100644 example/linux/.gitignore create mode 100644 example/linux/CMakeLists.txt create mode 100644 example/linux/flutter/CMakeLists.txt create mode 100644 example/linux/runner/CMakeLists.txt create mode 100644 example/linux/runner/main.cc create mode 100644 example/linux/runner/my_application.cc create mode 100644 example/linux/runner/my_application.h create mode 100644 example/macos/.gitignore create mode 100644 example/macos/Flutter/Flutter-Debug.xcconfig create mode 100644 example/macos/Flutter/Flutter-Release.xcconfig create mode 100644 example/macos/Runner.xcodeproj/project.pbxproj create mode 100644 example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 example/macos/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 example/macos/Runner/AppDelegate.swift create mode 100644 example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png create mode 100644 example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png create mode 100644 example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png create mode 100644 example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png create mode 100644 example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png create mode 100644 example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png create mode 100644 example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png create mode 100644 example/macos/Runner/Base.lproj/MainMenu.xib create mode 100644 example/macos/Runner/Configs/AppInfo.xcconfig create mode 100644 example/macos/Runner/Configs/Debug.xcconfig create mode 100644 example/macos/Runner/Configs/Release.xcconfig create mode 100644 example/macos/Runner/Configs/Warnings.xcconfig create mode 100644 example/macos/Runner/DebugProfile.entitlements create mode 100644 example/macos/Runner/Info.plist create mode 100644 example/macos/Runner/MainFlutterWindow.swift create mode 100644 example/macos/Runner/Release.entitlements create mode 100644 example/macos/RunnerTests/RunnerTests.swift create mode 100644 example/pubspec.lock create mode 100644 example/pubspec.yaml rename {test => example/test}/widget_test.dart (52%) rename {web => example/web}/favicon.png (100%) rename {web => example/web}/icons/Icon-192.png (100%) rename {web => example/web}/icons/Icon-512.png (100%) rename {web => example/web}/icons/Icon-maskable-192.png (100%) rename {web => example/web}/icons/Icon-maskable-512.png (100%) rename {web => example/web}/index.html (86%) rename {web => example/web}/manifest.json (81%) create mode 100644 example/windows/.gitignore create mode 100644 example/windows/CMakeLists.txt create mode 100644 example/windows/flutter/CMakeLists.txt create mode 100644 example/windows/runner/CMakeLists.txt create mode 100644 example/windows/runner/Runner.rc create mode 100644 example/windows/runner/flutter_window.cpp create mode 100644 example/windows/runner/flutter_window.h create mode 100644 example/windows/runner/main.cpp create mode 100644 example/windows/runner/resource.h create mode 100644 example/windows/runner/resources/app_icon.ico create mode 100644 example/windows/runner/runner.exe.manifest create mode 100644 example/windows/runner/utils.cpp create mode 100644 example/windows/runner/utils.h create mode 100644 example/windows/runner/win32_window.cpp create mode 100644 example/windows/runner/win32_window.h create mode 100644 ios/Assets/.gitkeep create mode 100644 ios/Classes/VpnclientEngineFlutterPlugin.swift create mode 100644 ios/Resources/PrivacyInfo.xcprivacy delete mode 100644 ios/RunnerTests/RunnerTests.swift create mode 100644 ios/vpnclient_engine_flutter.podspec create mode 100644 lib/vpnclient_engine_flutter.dart create mode 100644 lib/vpnclient_engine_flutter_method_channel.dart create mode 100644 lib/vpnclient_engine_flutter_platform_interface.dart create mode 100644 lib/vpnclient_engine_flutter_web.dart create mode 100644 linux/CMakeLists.txt create mode 100644 linux/include/vpnclient_engine_flutter/vpnclient_engine_flutter_plugin.h create mode 100644 linux/test/vpnclient_engine_flutter_plugin_test.cc create mode 100644 linux/vpnclient_engine_flutter_plugin.cc create mode 100644 linux/vpnclient_engine_flutter_plugin_private.h create mode 100644 macos/Classes/VpnclientEngineFlutterPlugin.swift create mode 100644 macos/Resources/PrivacyInfo.xcprivacy create mode 100644 macos/vpnclient_engine_flutter.podspec delete mode 100755 pubs.sh create mode 100644 test/vpnclient_engine_flutter_method_channel_test.dart create mode 100644 test/vpnclient_engine_flutter_test.dart create mode 100644 vpnclient_engine_flutter.iml create mode 100644 windows/.gitignore create mode 100644 windows/CMakeLists.txt create mode 100644 windows/include/vpnclient_engine_flutter/vpnclient_engine_flutter_plugin_c_api.h create mode 100644 windows/test/vpnclient_engine_flutter_plugin_test.cpp create mode 100644 windows/vpnclient_engine_flutter_plugin.cpp create mode 100644 windows/vpnclient_engine_flutter_plugin.h create mode 100644 windows/vpnclient_engine_flutter_plugin_c_api.cpp diff --git a/.gitignore b/.gitignore index 79c113f9..b852846a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ migrate_working_dir/ # IntelliJ related -*.iml +#*.iml *.ipr *.iws .idea/ @@ -24,22 +24,10 @@ migrate_working_dir/ #.vscode/ # Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock **/doc/api/ -**/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies -.pub-cache/ -.pub/ -/build/ - -# Symbolication related -app.*.symbols - -# Obfuscation related -app.*.map.json - -# Android Studio will place build artifacts here -/android/app/debug -/android/app/profile -/android/app/release +build/ diff --git a/.metadata b/.metadata index b93b3bc3..2c6a77c3 100644 --- a/.metadata +++ b/.metadata @@ -4,26 +4,35 @@ # This file should be version controlled and should not be manually edited. version: - revision: "35c388afb57ef061d06a39b537336c87e0e3d1b1" + revision: "c23637390482d4cf9598c3ce3f2be31aa7332daf" channel: "stable" -project_type: app +project_type: plugin # Tracks metadata for the flutter migrate command migration: platforms: - platform: root - create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 + create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf + base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf - platform: android - create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 + create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf + base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf - platform: ios - create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 + create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf + base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf + - platform: linux + create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf + base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf + - platform: macos + create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf + base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf - platform: web - create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 - base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 + create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf + base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf + - platform: windows + create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf + base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf # User provided section diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..41cc7d81 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..ba75c69f --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/README.md b/README.md index a14519a3..acbfa273 100644 --- a/README.md +++ b/README.md @@ -180,6 +180,17 @@ Enum: `invalidCredentials`, `serverUnavailable`, `subscriptionExpired`, `unknown --- +## Flutter + +[plug-in package](https://flutter.dev/to/develop-plugins), +a specialized package that includes platform-specific implementation code for +Android and/or iOS. + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev), which offers tutorials, +samples, guidance on mobile development, and a full API reference. + + ## 🤝 Contributing Contributions are welcome! Feel free to submit issues and pull requests. @@ -188,3 +199,4 @@ Contributions are welcome! Feel free to submit issues and pull requests. This project is licensed under ... + diff --git a/analysis_options.yaml b/analysis_options.yaml index 0d290213..a5744c1c 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,28 +1,4 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. include: package:flutter_lints/flutter.yaml -linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at https://dart.dev/lints. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. - rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore index be3943c9..161bdcda 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -1,14 +1,9 @@ -gradle-wrapper.jar -/.gradle -/captures/ -/gradlew -/gradlew.bat +*.iml +.gradle /local.properties -GeneratedPluginRegistrant.java -.cxx/ - -# Remember to never publicly share your keystore. -# See https://flutter.dev/to/reference-keystore -key.properties -**/*.keystore -**/*.jks +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 00000000..0c048261 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,66 @@ +group = "click.vpnclient.engine.flutter.vpnclient_engine_flutter" +version = "1.0-SNAPSHOT" + +buildscript { + ext.kotlin_version = "1.8.22" + repositories { + google() + mavenCentral() + } + + dependencies { + classpath("com.android.tools.build:gradle:8.7.0") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: "com.android.library" +apply plugin: "kotlin-android" + +android { + namespace = "click.vpnclient.engine.flutter.vpnclient_engine_flutter" + + compileSdk = 35 + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11 + } + + sourceSets { + main.java.srcDirs += "src/main/kotlin" + test.java.srcDirs += "src/test/kotlin" + } + + defaultConfig { + minSdk = 21 + } + + dependencies { + testImplementation("org.jetbrains.kotlin:kotlin-test") + testImplementation("org.mockito:mockito-core:5.0.0") + } + + testOptions { + unitTests.all { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed", "standardOut", "standardError" + outputs.upToDateWhen {false} + showStandardStreams = true + } + } + } +} diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 00000000..1428199c --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'vpnclient_engine_flutter' diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..8473558f --- /dev/null +++ b/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt b/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt new file mode 100644 index 00000000..afdc9f55 --- /dev/null +++ b/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt @@ -0,0 +1,33 @@ +package click.vpnclient.engine.flutter.vpnclient_engine_flutter + +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result + +/** VpnclientEngineFlutterPlugin */ +class VpnclientEngineFlutterPlugin: FlutterPlugin, MethodCallHandler { + /// The MethodChannel that will the communication between Flutter and native Android + /// + /// This local reference serves to register the plugin with the Flutter Engine and unregister it + /// when the Flutter Engine is detached from the Activity + private lateinit var channel : MethodChannel + + override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "vpnclient_engine_flutter") + channel.setMethodCallHandler(this) + } + + override fun onMethodCall(call: MethodCall, result: Result) { + if (call.method == "getPlatformVersion") { + result.success("Android ${android.os.Build.VERSION.RELEASE}") + } else { + result.notImplemented() + } + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + } +} diff --git a/android/src/test/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPluginTest.kt b/android/src/test/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPluginTest.kt new file mode 100644 index 00000000..1f43e8b1 --- /dev/null +++ b/android/src/test/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPluginTest.kt @@ -0,0 +1,27 @@ +package click.vpnclient.engine.flutter.vpnclient_engine_flutter + +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import kotlin.test.Test +import org.mockito.Mockito + +/* + * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation. + * + * Once you have built the plugin's example app, you can run these tests from the command + * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or + * you can run them directly from IDEs that support JUnit such as Android Studio. + */ + +internal class VpnclientEngineFlutterPluginTest { + @Test + fun onMethodCall_getPlatformVersion_returnsExpectedValue() { + val plugin = VpnclientEngineFlutterPlugin() + + val call = MethodCall("getPlatformVersion", null) + val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java) + plugin.onMethodCall(call, mockResult) + + Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE) + } +} diff --git a/dev/1.txt b/dev/1.txt deleted file mode 100644 index 8b137891..00000000 --- a/dev/1.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/1.txt b/docs/1.txt deleted file mode 100644 index 8b137891..00000000 --- a/docs/1.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/assets/1.txt b/docs/assets/1.txt deleted file mode 100644 index 8b137891..00000000 --- a/docs/assets/1.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/assets/vpnclient_controller.jpeg b/docs/assets/vpnclient_controller.jpeg deleted file mode 100644 index 7b7a49c35abc5c2e775613bc8c92b5ef4e8623d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 71919 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 diff --git a/docs/controller.md b/docs/controller.md deleted file mode 100644 index e68b3fb0..00000000 --- a/docs/controller.md +++ /dev/null @@ -1,145 +0,0 @@ -### **Methods (API)** -1. **initialize()** - - Initializes the VPN controller and prepares it for use. - - Should be called before any other method. - -2. **connect({required Integer index})** - - Connects to the specified VPN server. - - Parameters: - - `index`: The index from from getServerList. - -3. **disconnect()** - - Disconnects the active VPN connection. - -4. **getConnectionStatus()** - - Returns the current connection status (e.g., connected, disconnected, connecting, error). - -5. **getServerList()** - - Fetches the list of available VPN servers. - - Returns a list of server addresses or server objects. - -6. **pingServer({required Integer index})** - - Pings a specific server to check latency. - - Parameters: - - `index`: The index from from getServerList. - - Returns the latency in milliseconds. - -7. **setRoutingRules({required List rules})** - - Configures routing rules for specific apps or domains. - - Parameters: - - `rules`: A list of routing rules (e.g., route YouTube traffic through VPN, block ads.com). - -8. **loadSubscription({required String subscriptionLink})** - - Loads a VPN subscription from the provided link. - - Parameters: - - `subscriptionLink`: The link to the subscription file. - -9. **getSessionStatistics()** - - Returns statistics for the current VPN session (e.g., data usage, duration). - -10. **setAutoConnect({required bool enable})** - - Enables or disables auto-connect functionality. - - Parameters: - - `enable`: Whether to enable auto-connect. - -11. **setKillSwitch({required bool enable})** - - Enables or disables the kill switch (block all traffic if VPN disconnects). - - Parameters: - - `enable`: Whether to enable the kill switch. - - ---- - -### **Events** -1. **onConnectionStatusChanged** - - Fired when the VPN connection status changes. - - Payload: `ConnectionStatus` (e.g., connected, disconnected, error). - -2. **onError** - - Fired when an error occurs (e.g., connection failed, invalid credentials). - - Payload: `ErrorCode` and `ErrorMessage`. - -3. **onServerSwitched** - - Fired when the VPN server is successfully switched. - - Может в onConnectionStatusChanged? - - Payload: `newServerAddress`. - -4. **onPingResult** - - Fired when a ping operation completes. - - Payload: `serverIndex` and `latencyInMs`. - -5. **onSubscriptionLoaded** - - Fired when a subscription is successfully loaded. - - Payload: `subscriptionDetails`. - -6. **onDataUsageUpdated** - - Fired periodically with updated data usage statistics. - - Payload: `dataUsed` and `dataRemaining`. - -7. **onRoutingRulesApplied** - - Fired when routing rules are successfully applied. - - Payload: `List`. - -8. **onKillSwitchTriggered** - - Fired when the kill switch is activated (e.g., VPN disconnects unexpectedly). - - Payload: None. - ---- - -### **Data Models** -1. **ConnectionStatus** - - Enum: `connecting`, `connected`, `disconnected`, `error`. - -2. **Server** - - Properties: `address`, `latency`, `location`, `isPreferred`. - -3. **RoutingRule** - - Properties: `appName`, `domain`, `action` (e.g., `block`, `allow`, `routeThroughVPN`). - -4. **ProxyConfig** - - Properties: `type` (e.g., `socks5`, `http`), `address`, `port`, `credentials`. - -5. **ErrorCode** - - Enum: `invalidCredentials`, `serverUnavailable`, `subscriptionExpired`, `unknownError`. - -6. **SubscriptionDetails** - - Properties: `expiryDate`, `dataLimit`, `usedData`. - ---- - -### **Example Usage** -```dart -// Initialize the controller -vpnController.initialize(); - -// load subscription -vpnController.loadSubscription( - subscriptionLink: "https://pastebin.com/raw/ZCYiJ98W" -); - -// Connect to a VPN server -vpnController.connect( - serverAddress: 1 -); - -// Listen for connection status changes -vpnController.onConnectionStatusChanged.listen((status) { - print("Connection status: $status"); -}); - -// Set routing rules -vpnController.setRoutingRules( - rules: [ - RoutingRule(appName: "YouTube", action: "routeThroughVPN"), - RoutingRule(domain: "ads.com", action: "block"), - ], -); - -// Ping a server -vpnController.pingServer(index: 1); -vpnController.onPingResult.listen((result) { - print("Ping result: ${result.latencyInMs} ms"); -}); -``` - ---- diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 00000000..79c113f9 --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/example/README.md b/example/README.md new file mode 100644 index 00000000..4c759e38 --- /dev/null +++ b/example/README.md @@ -0,0 +1,16 @@ +# vpnclient_engine_flutter_example + +Demonstrates how to use the vpnclient_engine_flutter plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 00000000..0d290213 --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/example/android/.gitignore b/example/android/.gitignore new file mode 100644 index 00000000..be3943c9 --- /dev/null +++ b/example/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle.kts b/example/android/app/build.gradle.kts similarity index 88% rename from android/app/build.gradle.kts rename to example/android/app/build.gradle.kts index aa0fcc5d..9724b9e4 100644 --- a/android/app/build.gradle.kts +++ b/example/android/app/build.gradle.kts @@ -6,7 +6,7 @@ plugins { } android { - namespace = "com.example.vpnclient_engine_flutter" + namespace = "click.vpnclient.engine.flutter.vpnclient_engine_flutter_example" compileSdk = flutter.compileSdkVersion ndkVersion = flutter.ndkVersion @@ -21,7 +21,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId = "com.example.vpnclient_engine_flutter" + applicationId = "click.vpnclient.engine.flutter.vpnclient_engine_flutter_example" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. minSdk = flutter.minSdkVersion diff --git a/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml similarity index 100% rename from android/app/src/debug/AndroidManifest.xml rename to example/android/app/src/debug/AndroidManifest.xml diff --git a/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml similarity index 97% rename from android/app/src/main/AndroidManifest.xml rename to example/android/app/src/main/AndroidManifest.xml index d2b173e4..662ecea8 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ - - + + - - + @@ -16,14 +14,13 @@ - + - + - diff --git a/ios/Runner/Info.plist b/example/ios/Runner/Info.plist similarity index 94% rename from ios/Runner/Info.plist rename to example/ios/Runner/Info.plist index fe587040..1b867269 100644 --- a/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -5,7 +5,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - VPNclient Engine Flutter + Vpnclient Engine Flutter CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -13,7 +13,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - vpnclient_engine_flutter + vpnclient_engine_flutter_example CFBundlePackageType APPL CFBundleShortVersionString diff --git a/ios/Runner/Runner-Bridging-Header.h b/example/ios/Runner/Runner-Bridging-Header.h similarity index 100% rename from ios/Runner/Runner-Bridging-Header.h rename to example/ios/Runner/Runner-Bridging-Header.h diff --git a/example/ios/RunnerTests/RunnerTests.swift b/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..a58c561f --- /dev/null +++ b/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,27 @@ +import Flutter +import UIKit +import XCTest + + +@testable import vpnclient_engine_flutter + +// This demonstrates a simple unit test of the Swift portion of this plugin's implementation. +// +// See https://developer.apple.com/documentation/xctest for more information about using XCTest. + +class RunnerTests: XCTestCase { + + func testGetPlatformVersion() { + let plugin = VpnclientEngineFlutterPlugin() + + let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: []) + + let resultExpectation = expectation(description: "result block must be called.") + plugin.handle(call) { result in + XCTAssertEqual(result as! String, "iOS " + UIDevice.current.systemVersion) + resultExpectation.fulfill() + } + waitForExpectations(timeout: 1) + } + +} diff --git a/example/lib/main.dart b/example/lib/main.dart new file mode 100644 index 00000000..3d7c3869 --- /dev/null +++ b/example/lib/main.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({super.key}); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + String _platformVersion = 'Unknown'; + final _vpnclientEngineFlutterPlugin = VpnclientEngineFlutter(); + + @override + void initState() { + super.initState(); + initPlatformState(); + } + + // Platform messages are asynchronous, so we initialize in an async method. + Future initPlatformState() async { + String platformVersion; + // Platform messages may fail, so we use a try/catch PlatformException. + // We also handle the message potentially returning null. + try { + platformVersion = + await _vpnclientEngineFlutterPlugin.getPlatformVersion() ?? 'Unknown platform version'; + } on PlatformException { + platformVersion = 'Failed to get platform version.'; + } + + // If the widget was removed from the tree while the asynchronous platform + // message was in flight, we want to discard the reply rather than calling + // setState to update our non-existent appearance. + if (!mounted) return; + + setState(() { + _platformVersion = platformVersion; + }); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: Center( + child: Text('Running on: $_platformVersion\n'), + ), + ), + ); + } +} diff --git a/example/linux/.gitignore b/example/linux/.gitignore new file mode 100644 index 00000000..d3896c98 --- /dev/null +++ b/example/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/example/linux/CMakeLists.txt b/example/linux/CMakeLists.txt new file mode 100644 index 00000000..77fbaa40 --- /dev/null +++ b/example/linux/CMakeLists.txt @@ -0,0 +1,130 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "vpnclient_engine_flutter_example") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "click.vpnclient.engine.flutter.vpnclient_engine_flutter") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + +# Enable the test target. +set(include_vpnclient_engine_flutter_tests TRUE) + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/example/linux/flutter/CMakeLists.txt b/example/linux/flutter/CMakeLists.txt new file mode 100644 index 00000000..d5bd0164 --- /dev/null +++ b/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/example/linux/runner/CMakeLists.txt b/example/linux/runner/CMakeLists.txt new file mode 100644 index 00000000..e97dabc7 --- /dev/null +++ b/example/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/example/linux/runner/main.cc b/example/linux/runner/main.cc new file mode 100644 index 00000000..e7c5c543 --- /dev/null +++ b/example/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/example/linux/runner/my_application.cc b/example/linux/runner/my_application.cc new file mode 100644 index 00000000..58da4aec --- /dev/null +++ b/example/linux/runner/my_application.cc @@ -0,0 +1,130 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "vpnclient_engine_flutter_example"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "vpnclient_engine_flutter_example"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/example/linux/runner/my_application.h b/example/linux/runner/my_application.h new file mode 100644 index 00000000..72271d5e --- /dev/null +++ b/example/linux/runner/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/example/macos/.gitignore b/example/macos/.gitignore new file mode 100644 index 00000000..746adbb6 --- /dev/null +++ b/example/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/example/macos/Flutter/Flutter-Debug.xcconfig b/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 00000000..c2efd0b6 --- /dev/null +++ b/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/example/macos/Flutter/Flutter-Release.xcconfig b/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 00000000..c2efd0b6 --- /dev/null +++ b/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..cf972738 --- /dev/null +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,705 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* vpnclient_engine_flutter_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "vpnclient_engine_flutter_example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* vpnclient_engine_flutter_example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* vpnclient_engine_flutter_example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = click.vpnclient.engine.flutter.vpnclientEngineFlutterExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/vpnclient_engine_flutter_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/vpnclient_engine_flutter_example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = click.vpnclient.engine.flutter.vpnclientEngineFlutterExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/vpnclient_engine_flutter_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/vpnclient_engine_flutter_example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = click.vpnclient.engine.flutter.vpnclientEngineFlutterExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/vpnclient_engine_flutter_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/vpnclient_engine_flutter_example"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..62ddf97b --- /dev/null +++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..1d526a16 --- /dev/null +++ b/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/macos/Runner/AppDelegate.swift b/example/macos/Runner/AppDelegate.swift new file mode 100644 index 00000000..b3c17614 --- /dev/null +++ b/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..a2ec33f1 --- /dev/null +++ b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000000000000000000000000000000000000..82b6f9d9a33e198f5747104729e1fcef999772a5 GIT binary patch literal 102994 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ⅅ8MglG&ER{i5lN315cO?EdHNLrg? zgxkP+ytd)OMWe7QvTf8yj4;V=?m172!BEt@6*TPUT4m3)yir}esnIodFGatGnsSfJ z**;;yw=1VCb2J|A7cBz-F5QFOQh2JDQFLarE>;4ZMzQ$s^)fOscIVv2-o{?ct3~Zv zy{0zU>3`+-PluS|ADraI9n~=3#Tvfx{pDr^5i$^-h5tL*CV@AeQFLxv4Y<$xI{9y< zZ}li*WIQ+XS!IK;?IVD0)C?pNBA(DMxqozMy1L#j+ba1Cd+2w&{^d-OEWSSHmNH>9 z%1Ldo(}5*>a8rjQF&@%Ka`-M|HM+m<^E#bJtVg&YM}uMb7UVJ|OVQI-zt-*BqQ zG&mq`Bn7EY;;+b%Obs9i{gC^%>kUz`{Qnc=ps7ra_UxEP$!?f&|5fHnU(rr?7?)D z$3m9e{&;Zu6yfa1ixTr;80IP7KLgkKCbgv1%f_weZK6b7tY+AS%fyjf6dR(wQa9TD zYG9`#!N4DqpMim|{uViKVf0B+Vmsr7p)Y+;*T~-2HFr!IOedrpiXXz+BDppd5BTf3 ztsg4U?0wR?9@~`iV*nwGmtYFGnq`X< zf?G%=o!t50?gk^qN#J(~!sxi=_yeg?Vio04*w<2iBT+NYX>V#CFuQGLsX^u8dPIkP zPraQK?ro`rqA4t7yUbGYk;pw6Z})Bv=!l-a5^R5Ra^TjoXI?=Qdup)rtyhwo<(c9_ zF>6P%-6Aqxb8gf?wY1z!4*hagIch)&A4treifFk=E9v@kRXyMm?V*~^LEu%Y%0u(| z52VvVF?P^D<|fG)_au(!iqo~1<5eF$Sc5?)*$4P3MAlSircZ|F+9T66-$)0VUD6>e zl2zlSl_QQ?>ULUA~H?QbWazYeh61%B!!u;c(cs`;J|l z=7?q+vo^T#kzddr>C;VZ5h*;De8^F2y{iA#9|(|5@zYh4^FZ-3r)xej=GghMN3K2Y z=(xE`TM%V8UHc4`6Cdhz4%i0OY^%DSguLUXQ?Y3LP+5x3jyN)-UDVhEC}AI5wImt; zHY|*=UW}^bS3va-@L$-fJz2P2LbCl)XybkY)p%2MjPJd-FzkdyWW~NBC@NlPJkz{v z+6k6#nif`E>>KCGaP34oY*c#nBFm#G8a0^px1S6mm6Cs+d}E8{J;DX=NEHb|{fZm0 z@Ors@ebTgbf^Jg&DzVS|h&Or)56$+;%&sh0)`&6VkS@QxQ=#6WxF5g+FWSr7Lp9uF zV#rc`yLe?f*u6oZoi3WpOkKFf^>lHb2GC6t!)dyGaQbK7&BNZ7oyP)hUX1Y(LdW-I z6LI2$i%+g!zsjT(5l}5ROLb)8`9kkldbklcq6tfLSrAyh#s(C1U2Sz9`h3#T9eX#Hryi1AU^!uv*&6I~qdM_B7-@`~8#O^jN&t7+S zTKI6;T$1@`Kky-;;$rU1*TdY;cUyg$JXalGc&3-Rh zJ&7kx=}~4lEx*%NUJA??g8eIeavDIDC7hTvojgRIT$=MlpU}ff0BTTTvjsZ0=wR)8 z?{xmc((XLburb0!&SA&fc%%46KU0e&QkA%_?9ZrZU%9Wt{*5DCUbqIBR%T#Ksp?)3 z%qL(XlnM!>F!=q@jE>x_P?EU=J!{G!BQq3k#mvFR%lJO2EU2M8egD?0r!2s*lL2Y} zdrmy`XvEarM&qTUz4c@>Zn}39Xi2h?n#)r3C4wosel_RUiL8$t;FSuga{9}-%FuOU z!R9L$Q!njtyY!^070-)|#E8My)w*~4k#hi%Y77)c5zfs6o(0zaj~nla0Vt&7bUqfD zrZmH~A50GOvk73qiyfXX6R9x3Qh)K=>#g^^D65<$5wbZjtrtWxfG4w1f<2CzsKj@e zvdsQ$$f6N=-%GJk~N7G(+-29R)Cbz8SIn_u|(VYVSAnlWZhPp8z6qm5=hvS$Y zULkbE?8HQ}vkwD!V*wW7BDBOGc|75qLVkyIWo~3<#nAT6?H_YSsvS+%l_X$}aUj7o z>A9&3f2i-`__#MiM#|ORNbK!HZ|N&jKNL<-pFkqAwuMJi=(jlv5zAN6EW`ex#;d^Z z<;gldpFcVD&mpfJ1d7><79BnCn~z8U*4qo0-{i@1$CCaw+<$T{29l1S2A|8n9ccx0!1Pyf;)aGWQ15lwEEyU35_Y zQS8y~9j9ZiByE-#BV7eknm>ba75<_d1^*% zB_xp#q`bpV1f9o6C(vbhN((A-K+f#~3EJtjWVhRm+g$1$f2scX!eZkfa%EIZd2ZVG z6sbBo@~`iwZQC4rH9w84rlHjd!|fHc9~12Il&?-FldyN50A`jzt~?_4`OWmc$qkgI zD_@7^L@cwg4WdL(sWrBYmkH;OjZGE^0*^iWZM3HBfYNw(hxh5>k@MH>AerLNqUg*Og9LiYmTgPw zX9IiqU)s?_obULF(#f~YeK#6P>;21x+cJ$KTL}|$xeG?i`zO;dAk0{Uj6GhT-p-=f zP2NJUcRJ{fZy=bbsN1Jk3q}(!&|Fkt_~GYdcBd7^JIt)Q!!7L8`3@so@|GM9b(D$+ zlD&69JhPnT>;xlr(W#x`JJvf*DPX(4^OQ%1{t@)Lkw5nc5zLVmRt|s+v zn(25v*1Z(c8RP@=3l_c6j{{=M$=*aO^ zPMUbbEKO7m2Q$4Xn>GIdwm#P_P4`or_w0+J+joK&qIP#uEiCo&RdOaP_7Z;PvfMh@ zsXUTn>ppdoEINmmq5T1BO&57*?QNLolW-8iz-jv7VAIgoV&o<<-vbD)--SD%FFOLd z>T$u+V>)4Dl6?A24xd1vgm}MovrQjf-@YH7cIk6tP^eq-xYFymnoSxcw}{lsbCP1g zE_sX|c_nq(+INR3iq+Oj^TwkjhbdOo}FmpPS2*#NGxNgl98|H0M*lu)Cu0TrA|*t=i`KIqoUl(Q7jN zb6!H-rO*!&_>-t)vG5jG>WR6z#O9O&IvA-4ho9g;as~hSnt!oF5 z6w(4pxz|WpO?HO<>sC_OB4MW)l`-E9DZJ$!=ytzO}fWXwnP>`8yWm5tYw`b1KDdg zp@oD;g===H+sj+^v6DCpEu7R?fh7>@pz>f74V5&#PvBN+95?28`mIdGR@f*L@j2%% z%;Rz5R>l#1U zYCS_5_)zUjgq#0SdO#)xEfYJ)JrHLXfe8^GK3F*CA(Y)jsSPJ{j&Ae!SeWN%Ev727 zxdd3Y0n^OBOtBSKdglEBL)i5=NdKfqK=1n~6LX`ja;#Tr!II$AAH{Z#sp%`rwNGT5 zvHT%(LJB+kD{5N}7c_Rk6}@tikIeq%@MqxX%$P!(238YD(H<_d;xxo*oMiv^1io>g zt5z&6`}cjci90q2r0hutQXr!UA~|4e*u=k81D(Cp7n{4LVCa+u0%-8Uha+sqI#Om~ z!&)KN(#Zone^~&@Ja{|l?X64Dxk)q>tLRv{=0|t$`Kdaj z#{AJr>{_BtpS|XEgTVJ4WMvBRk-(mk@ZYGdY1VwI z81;z(MBGV|2j*Cj%dvl8?b2{{B#e0B7&7wfv+>g`R2^Ai5C_WUx|CnTrHm+RFGXrt zs<~zBtk@?Niu%|o6IEL+y60Q>zJlv``ePCa07C%*O~lj?74|}&A0!uA)3V7ST8b_- z6CBP1;x+S@xTzgOY2#s%@=bhZ@i@BwmS)neQG&=9KUtRf^K=MvjC5JnqLqykCE_P0 zjf#V4SdH2#%2EuDb!>FLHK7j;nd6VLW|$3gJuegpEl3DZ`BpJU$<}}A(rW?<6OB@9 zKP9G3An?T5BztrLdlximA;{>Tr7GAeSU=^<*y;%RHj+7;v+tonyh(8d;Izn}2{oz& zW)fsZ9gHYpI?B|uekS3zHUue3mI zb7?0+&Zm>Kq(F>~%VYEn)0b32I3~O^?Wx-HI|Zu?1-OA2yfyJ;gWygLOeU;)vRm3u z5J4vDIQYztnEm=QauX2(WJO{yzI0HUFl+oO&isMf!Yh2pu@p}65)|0EdWRbg(@J6qo5_Els>#|_2a1p0&y&UP z8x#Z69q=d663NPPi>DHx3|QhJl5Ka$Cfqbvl*oRLYYXiH>g8*vriy!0XgmT~&jh3l z+!|~l=oCj<*PD>1EY*#+^a{rVk3T(66rJ^DxGt|~XTNnJf$vix1v1qdYu+d@Jn~bh z!7`a`y+IEcS#O*fSzA;I`e_T~XYzpW7alC%&?1nr);tSkNwO&J`JnX+7X1Q8fRh_d zx%)Xh_YjI3hwTCmGUeq_Z@H#ovkk_b(`osa$`aNmt`9A#t&<^jvuf z1E1DrW(%7PpAOQGwURz@luEW9-)L!`Jy*aC*4mcD?Si~mb=3Kn#M#1il9%`C0wkZ` zbpJ-qEPaOE5Y5iv_z%Wr{y4jh#U+o^KtP{pPCq-Qf&!=Uu)cEE(Iu9`uT#oHwHj+w z_R=kr7vmr~{^5sxXkj|WzNhAlXkW^oB4V)BZ{({~4ylOcM#O>DR)ZhD;RWwmf|(}y zDn)>%iwCE=*82>zP0db>I4jN#uxcYWod+<;#RtdMGPDpQW;riE;3cu``1toL|FaWa zK)MVA%ogXt3q55(Q&q+sjOG`?h=UJE9P;8i#gI*#f}@JbV(DuGEkee;La*9{p&Z?;~lE!&-kUFCtoDHY*MS zzj+S$L9+aTs(F^4ufZe6>SBg;m@>0&+kEZMFmD*~p~sx?rx=!>Ge;KYw<33y#*&77 zFZI`YE(Iz?+tH;Fq;y=MaSqT{Ayh*HFv0(z{_?Q+7@nE%p?S8%X6c!+y;!0NLXwJV8Co_}R3*7>n+oMsQpv8}8ZS-P@(Rg|gmxZHzf=nMOUAAY}AZGfWVzZjE@4$=7xkIrs8BE%606aVU%kxz_04ipig51k& z(>c9rJL2q%xvU%Zj#GR9C9)HLCR;#zQBB@x;e_9$ayn(JmSg_*0G?+wOF?&iu@}S{ zt$;TPf*Lj$3=d<}Q3o!Hq@3~lFxoiCyeEt}o3fihIn{x2s1)e2@3##&GYDq~YO|!q zUs0P-zy)+ohl-VQ`bhvUpC{-d$lkpML_M%Kl6@#_@A}w{jWCDsPa#cSbWA#C4Sf|*C*&Z{ zz?hOU7Cc`?>H$WGqITA2P~fYudnQHxB8^;0ZFKC;19F#~n_2P@{cE{Czq-#K5L_8| zc3aOEwq4%zL5>YU_mc9fc-p~{fBTWUkxTiZvxt9FOqC{s#TBp(#dWc+{Ee{dZ#B!g zHnaOJ8;KO1G;QU2ciodE+#Z$Wuz*Hc6NRO!AUMi|gov=>=cwcZeL&`>Jfn!35hV1J z;B2@0!bIR853w%T*m6)gQ?DPnQ)o6EtKaN3L;o?*q<83d&lG&U=A|6hcT?f0)4h6{ zGIZ0|!}-?*n{zr}-}cC}qWxEN%g60+{my)o^57{QEn(tSrmD7o)|r0+HVpQPopFu; z0<S}pW8W2vXzSxEqGD+qePj^x?R$e2LO&*ewsLo{+_Z)Wl|Z1K47j zsKoNRlX)h2z^ls_>IZ0!2X5t&irUs%RAO$Dr>0o$-D+$!Kb9puSgpoWza1jnX6(eG zTg-U z6|kf1atI!_>#@|=d01Ro@Rg)BD?mY3XBsG7U9%lmq>4;Gf&2k3_oyEOdEN&X6Hl5K zCz^hyt67G;IE&@w1n~%ji_{sob_ssP#Ke|qd!Xx?J&+|2K=^`WfwZ-zt|sklFouxC zXZeDgluD2a?Zd3e{MtE$gQfAY9eO@KLX;@8N`(?1-m`?AWp!a8bA%UN>QTntIcJX zvbY+C-GD&F?>E?jo$xhyKa@ps9$Dnwq>&)GB=W~2V3m)k;GNR$JoPRk%#f3#hgVdZ zhW3?cSQ*((Fog26jiEeNvum-6ID-fbfJ?q1ZU#)dgnJ^FCm`+sdP?g;d4VD$3XKx{ zs|Y4ePJp|93fpu)RL+#lIN9Ormd;<_5|oN!k5CENnpO>{60X;DN>vgHCX$QZYtgrj z*1{bEA1LKi8#U%oa!4W-4G+458~`5O4S1&tuyv>%H9DjLip7cC~RRS@HvdJ<|c z$TxEL=)r)XTfTgVxaG!gtZhLL`$#=gz1X=j|I@n~eHDUCW39r=o_ml@B z0cDx$5;3OA2l)&41kiKY^z7sO_U%1=)Ka4gV(P#(<^ z_zhThw=}tRG|2|1m4EP|p{Swfq#eNzDdi&QcVWwP+7920UQB*DpO0(tZHvLVMIGJl zdZ5;2J%a!N1lzxFwAkq05DPUg2*6SxcLRsSNI6dLiK0&JRuYAqwL}Z!YVJ$?mdnDF z82)J_t=jbY&le6Hq$Qs}@AOZGpB1}$Ah#i;&SzD1QQNwi6&1ddUf7UG0*@kX?E zDCbHypPZ9+H~KnDwBeOXZ-W-Y80wpoGB*A) z_;26Z`#s0tKrf~QBi2rl2=>;CS1w)rcD3-sB!8NI*1iQo59PJ>OLnqeV4iK7`RBi^ zFW{*6;nlD&cSunmU3v4JKj|K4xeN(q>H%;SsY8yDdw5BJ75q8>Ov)&D5OPZ`XiRHl z;)mAA0Woy6f!xCK(9H2rq?qzp83liZAIpBPl-dQ&$2=&H?Im~%g;vnIw1I+8q|kr! z36&^9}CMmR(U2rf|j12oG=vb%Ypsq8u9Kq}U*ANX*)9uK}fAi8;V_7Z;0_4*iydDxN-? zv?qJ=T*{MzL~-xUv{_Kh_q9#F{8gPV!yPUUS8pEq*=}2-#1d=sC_|U-rX~F0 zBLawgCWy#?#ax{~DAnDvh^`}wyUO`ioMK~jgh%L7^}#h?beSyvQ_g>+`2`}`-1h7# zg*?qJdm=53hwN8~B=^|LPmYtOVrQ(W{sNm4uofq=4P@dUA%$onWbw_m-KWia&n9iv zi)!9#OJ#^}eg8tE{wSb9(c0D^PS1 z9EBS5*ypSiVRS_G0v?$hyoZOS7hFWlp4qbYkf9Y&{%OzhsIdHskLptn96@k6@^K@U zszd8POehITDK+AyW#JKpnWY;ju#MC$JjB1Y*~(E6N%{p#kO+bVxG3X<34n3fW=k{A zCZt|KP%x^GQ9%mU)KE0{LA=vaZvRQbxSlK~eAkwWo2Z<{j5eS5NVTMe`m%re8%~7K zZLtU&b~YDN%~uA9wPf>x2=PI=MA6_oVe>Ek$s5&&Z=8vvF5EODP4Av(b|dlNgF1O8 zy83W0WRdzjz2iNA~t1piEqlyU&`$yZtqR`6X_PmuP>W+D|8iH;FQ zN{JuU#Tz9mV=4R_IewROL1|mK^`lLat#LcIBfggzM(iO$pQT*-c_ z94^LUWw#5B9~sp2W1p`c)Y(xfR<{O^9n4E6vDDw{#-R4UMBKo{>Hqlqn*a9rl_>+0 zS5MwJC~nCC`1X%VCyWFsiDX;bfAJQAUkU#105f_s5U-8rqO}n8fA1{b>Fr6Q|Ea(V z5B11Lo^ooWF?`^{-U#?iatokWI-e$632frzY?Yzzx(xJc@LFM4A~-eg!u|tl{)8Nx ztZLXsSC*68g%9TFu(f&J9nmc^9hgyy#uUOMJFCaifSaDcyQ&6=8e9=t zIFEAQ{EK{|73{($!a4=!wj4ABcQrUQp#+gGM?wEUp(w@+Fzi{!lt}|3`PM%&d-seeR zB$}BrFGD3R10CE>Hsb>;PrP}pd` zaY4}6+Wu(`#uAV+E5SV7VIT7ES#b(U0%%DgN1}USJH>)mm;CHPv>}B18&0F~Kj@1= z&^Jyo+z-E)GRT4U*7$8wJO1OibWg0Jw>C$%Ge|=YwV@Y1(4fR>cV#6aGtRoF@I`*w_V4;)V231NzNqb6g@jdpjmjv*<2j02yU$F8ZS$fTvCC`%|Yn#x< zXUnP&b!GLpOY-TY3d?<-Hhxom_LM9`JC9LEX2{t1P-Nj%nG+0Vq)vQwvO^}coPH-> zAo8w#s>Je^Yy*#PlK=XDxpVS~pFe-j#jN-(As&LRewOf(kN-aKF(H+s*{*!0xrlZw zchJu@XAvQWX7DI1E8?F}Wc8m46eT+C<0eXVB+Z^(g=Kl@FG-cn@u$suj)1V2(KNg_ zh29ws6&6(q~+sOAoHY^o86A<#n*?Pg2)cK$+y;cY$hJLq4)4V84=j+3ShSr##Tk5kgmxB zkW+8A1GtceEx~^Ebhwm36U?oA)h)!mt=eg0QE$D1QsLNZ_T3NH?=B&0j~#298!6iv zhc0|-{46*3`Rx&nKSXnf1&w-Rs>#PGAGuY@cBTU-j|Fxbn3z49S#6KBaP^Lx*AOXxIibr z!1ysMi(&kr!1wwQB5w`BDH2~>T4bI`T1}A2RM0zd7ikC&kuBRsB`Z2@J!Udm{AmSN zrr0k6_qCZL**=)xRW`MFu(OY=OT;3G8eF~ z2mmkXZ9X(sjuKmq+_<=LSjphB$~R1o^Yb=rO!j!(4ErIox^x55o{pXSE9X$!76^*$ zoKhlAX6y%n^U=C~@!vIlEgXQGD@>oOU=_(aXF-Sjas*$AKESfRzxQ8#3yOj|y0OCU z>6Z-0%LCcjla&7I+CXm&caKp@@jQ!5M`(_{CL=@4#JJ}cHeZw>^b6fpv269LSV?gV5Q{kk?4;;y9RIsy5vk%DIRiL(9xe1aA@4!VX zDh2}xgUd5X?6nji%&7-%QuyKSYA-Z{PwJijUQ}In+EJl|x@dF1P<5bPa5W3&&?^h$ zZCo8LepKo0a(Fsln*cHL;D(gu9MMkoiM0*n31u)jHqX5x^F95tnI&^}^yKx3YwEm@ zo8?EZ710ykx@19{=yz5IXb8w4yjdveWb{IVL6Z(Cs>!a_0X^1E27o!4e&b43+J*u2Gb(59k2uK0goLwhO{ujLS ziI9LA9`&x~Y$6JNX!aEXR``}LUI}Gr#=<^wBHmg%v<)zRWDVtq)kT$-P7iU1R)2XZ zi~bYhV@EZ`@prgK(cs{>2jn$pxg$<|KjJ7%26Km>%KcXh^bU@y@V_Lf@=j1x%R4{v zOcQn{I}!2W<~08FOVnoV>zOTH=+>v9!jFo|q)ucqIe!N4{U5_G`>>*sVD{8I~4FqyU8imZ**-Gy`~Xd z4w35GMf%7^i65HdX{Iz|f2Kg193#KhPIeR)-=eYx3Z!%RM=JjwLrdk^B#6rg!ym2w zPbFqYyO4>W_Z6PonAwiu7?!h=x%sR-T+_*xZOGh2wWhWr%}%2^$$ zQvACIB~pi=m|`hXIMvoq`TOCx=J_D2>pi6$NPy3&8#vy|oX)=kM0Z}$BR$r0G}MzOk-OqG+VmZtOZoj6x4(tLh|5h) zBv64Y{DPHsy&_H(5_l(&Y}FhVvr9m_*_Q~Zy-}V9+VmGnvndEjYW4qt4K~N&Y&6g| zfpz*V=A#^mVmuOAz)(KVI<%v5NY0%Goy!{9&o41upsPWk(yFuRP|A4q6NMnX%V~MT zi_Rb-Bno2kI+j0Cw`@ydy{e%ARS#Z%b6I%_yfo_ZKXr4BLVoHzBKJ^ZG z-2>2IzU)55@9C|?_P$ew^-7zEiAKG1XAi{!3h%1m#9s%^pGy6S9wKFYY4<$djeoJP z{GI}Vd%idY$4_fh(7NXm7#;cC!DS&-{tGr!Qze{^%bUx2jgG@-kMta^q-EwrKB}d8 z{%FT>rFk_bzW<{lc%eYlrsiYTZXGgzD1&lmRyp+c1O=0=zAX=KV62bx-a~JP{cPF4 zU$-XT#(9&T>l@bMu3nSr{)%-5lV+0t&bxip4DVJ~vlL$J2P6X~ zd{FS8vm{Lhrieul*7&(AgPuXhjpGila%6_?-+k#b)cdk#M1jB*nE>G6NGOr+Ek{`= z9b%S1`$`=g0CC$>0$Db;l_szReLYVmce*(()9%Zz1`*fNXhI*oRlerWHarD(v^W^c zuc1Vuw6Gbp7ZsoRH>QGt#&lv;5G~Ovt$%7VFd*-rN2>UjbOWBFGNGO`bru7CFB4tn zL`^?69Lj_g_TA&`9`dSI8s|)K|QM0 zybvV7!>xDY|6c6y;Q}qs`){1+WQu_5Dgd8Qe|q}}bxjH+joQQtqs1IVZn6{e7T{ia zF|=^xa%eWO%(x<7j*QZbcU_;aVaVP!arexOLOtoSNt*hvsRL%}%)jPetSich(`b-^ zMZ$PM9%s@%*jPVz0Z^W*cK_>G4f}+eEVX`HOaHg#!B`<4v;x}zDLMR*M27`kNfp!! zOfdt(>k-g>7jf^{Se@3$8<+;R*cYtw+wD_Z8Pl~!JDCUEPq{Ea*!J9`%ihyNJZ30i zmfve}S5<$Uso}_?SuI$ks|{-ddGLu9WR9`^9)Kdi@Vs;x#SY-xp}wHPU0|vEA7234 z@BN1z7OF=OOQtPF$4twn3!HTVlUVD_)ubMM7PEPoiC6lQgL2q9PK4~e8v-OuH%lie z?NgBLkIdPMG$QBq(>r^AOHB`|*1#*!2Z? zuU8H|FD`OBRu^(R?Z-Vhr0j;FLpS~a34KREnd}B=EYHS*>Hm+f%tgJt!4J8Q`qn^4 z9F=tO#JRJ}tzA`vx$nZ)O%wC?Uiv0+_nz}5Lj4ki*&=K&*#U`=rv z`Q@Q{+IhAj@6lrNK2B=8Yln!O2%zomfRehFT~;!O@(@Xy|1Jlw*uOB-M$#6K^)QBm z_7%#QVUDPwnW{iOV-grMQQU|3{=BQMh}c5(yMGdoQf*)k9-B zMQ(^GdJh+y)>qJprknS!%WxqM>HlHOP#7UVdy>%PW$!l72J`n-p7j(DBKoGxXWh(Y z>BFDZl|7knU_jg_SSbvFk8)39%2)Hu5W0}HKlh>EaqvFoXI&56Yy)3) zQkE4X^P0QnPn?iUUVHJZXzPp`s5uv?pG{K9IgGoHvcmlBxubi|iF7n{)mhenIcxGs zgr0OpQy#Y#u=5lOyiECfE_Sn?Fj1LyoRKcbTgX{p<T*v!CGkPc)pcA2D=4Ekp0Gb*wpy7S88C%Ywsbr?MI(3UdsCM?XJ1X%*hNjB)XqZ*W(qDdtSb z<3XN74ARXL3=c^bfW~F%NM^5*Zx92>Wq`&M625p~j$8mYwLbk%Kf)jbn#<2z$%vP5 zy#b>-tF-S2_AB4;R^K&^-1LJrUmi@9rB^FLF)-k&YHK8P+k@RCJ1qSTZ@=kHxA3l$ zmK_ZG)l6(nmCR1a8|;QF-B5e_ELnjJ1$m-;4UXX?WytF_wz7#&AjwZYTMVieLbq@R z3t-q|G4^BB#EpNu4uyfDebB+-uu_$9>y-dzB30Y9F=R zrW-Heqnj*InPTWHgR9v^R7~hokldh&h8=HDhMW(EFfim1*{)5Lc1-+eBVkK-2!u=N zuZKABgJs3I--NbjE;>Undg6uK`^U>AQ6V zhc!RhYgvrmeGNsftr+(C<_MtuV$`5RZTf#5r=DR?gWG->#})#=(td%C3`oO+2B7im zUqY}&a_QNTn?s+?=mNXiREN%x_=(H)L|DtYPY>SR3pQfBOel7G_jR_{!9`dSj8Up-`JgcB;=Oor)U=_EVjF3C5{Sqh8cq=~bRjoBpoc$kJCgtTyZGSpQ4= zYi$6b$-dGmuTDF&@amhV?cU05g(AZV&v2$4m&j_~GZk;&keSO(@LRESRZ&p`dV*6w z2$em~p*8yM6j;SYorw`M5K2mluJq7P5Yn$VtZj8DEs2Zk=O@4T&Q}>~f31Z{uk}`E z{Dp{KObh1kk~~MfLUod72{Pk6G@T$_0_N??lOrdR=Z;VV#m0l)&@hz{Z?)@sgImi-&i1@95g53rON83v!yVPDHRU*Mzc4yZ(-Fr z{8{WXmIJf7jeswk$;6s~Qac6QyM3W&`}m#gRt=rr95A+Ad&wSAgvXZ|F))rBJVJ5W1CsjN`QaOzct2ocq#0!v zmj#075)C!3oS>&N;aHS@<+c>RHL)8j^p)k(8#7$LEx!1g_1^02!4_qA=;uhKW=+ix zGX%+vBMiRiF^^jm{mdO(?GdWJ#unO#_F^7mhT8)s(z_WlwFyJ#Xh)k5+RG2f;LC*K**1dr`#}~6A=0B=I&V;%zDA1)d@G!X#Rng)7G*2k8Kg447r0ox> z5NK`d(H-afBwo9feDOUi>;BbPsu!2|=@g=3j*PY}@YrOb+SX6?#Yb2xaaK!?>SX1J z_!VsB`2n1=wwSftkydm!39|-1?c%Epx?TO<(#GO~I&{f4+)XwRk<7RQ1~5>QcKH|D z?!}j1ueO0Lk;FZ{k4FA_(S`Ot0w~tl&m0duID*f6RY#bkw||o;kZ# zISYNTb|{~|X$m$Q-Jv#uxyw)eM0gIv`V#wOAp&Vv@>X4_tSZ&L#juM@$S9 zx_X_tLh<_^-F;LAQ09s@sPb%PMTrcw*HUV0P=RYSlM&AXEOI&&R&YCm_S<7DRBx^L zA^R^iwW+LMk(r*$Pq-fKU5X@=mQ=`ErO30H@@&qqnI7zJcrbSh+H<V ze&7Uli0xj@WrW#&-9%*FP~kPYF_YYM_hs5~|ExMynQ%qvq`leRB6W0yhC@pCb8>_P zlf=F~WMv_u*-DV=UaVu#2rlzK{q8D95VwZrfV?gj@rSNWXFvktUq)V5+YrlxwX302ae(;aG4e>L-M@3J+-f3IT{b9l!kg*2M zC1+ND9}6m^()LE87Mt+^Q|)!y#suc&v26C=0W88%a{?)E8Yvo@kM&KNMaOst#|-_CbUTm}WS@-c>nRb;&z^ zYr)+IE$1=jov(CZ%3uR+`~NI>1&Gs6W(jaamjcN$a`2!*nO}l|b%?)Q%%UWzw>A`C zR@px(P*7j$TK?jbv*%x)e^|jcLsv}aF(Z0=7(%Oa7+1wY>{B>d+i&ZA$}k(qgZPZY z;VkW~8eWnU&HPIAbco?&tc2O1$6=7n{u|^Y*nXoac{o1W-6aXfy~KlNbJfLoq~6;+ zDYmnv--Fhqrl+UV#k@_(1=gWNtqhyVKN=9CZ-{Ohi>e=~bm4IKbhM%%W zW8oXE!rGpV7Wt(_^4nndH1_imheaWzDi|I})9ZVZ9>pN+P%dVc5wG`Ze*4`@rjn1^ z`ln(;vPBHQUb}y8S>=8q__r7g+=z$>!pReVB0@XKchAvyGjLQs-u>+w%`frV4FeIG zj=7n~hGrwx*&5aHy(7X$bDZ7YhcP%(*>G^lAYMK;qG~V8Jz@b7oNg;IA1z$9@TbzW z;@I51@Ekef#qbxnG$Y8Z%bm~ibZ=4#%yKr%#b)CDrfKN`ujIY?tA4h9)i~dZ4E;ZM znvb$n2)zn$Wx&zlW%mJZDh28ox$@%`w3i7YFepXUChw}$UXKI=-TM51`M#FH=tdr*mQ!c=aB1296Lu>iTTKZWss0f z5~ihdImPN$aTle_AdbYC^31}_^EK|9R&l#%3hbx;8vJ+Gp^tm{9JDILu*1PW!rh^Dn9p<)h#Sl4kKM%nm<+!ESSk* zC;lLNT$fgr-!+{aBsSx$41b}yy6o>r3F#1&iv3cfY2N<+`0qJ+>=&Qxs}JOEkD?^l-F5i`t5+zNuvJf z3Fh4$mNqiFXL-aq4U4K@Ae$fq-TDT`rvrx;gqx96w^*@s=mcthCaIyPe(w)6kI{EqV10tcShHU9eeAPs)s?6#vrq}>y3FeTJu$Udha+z zs7}rmA@yR(L&>35sNjQqrw}o^)UitMU!5g6nnG)(tgst!^`FKJEzI1(d@j_w@;^hr zgYxlIRYjho4U$bhczfq&YySCqCE(5_d>l(4tk1v9!V7PB%Vx{QO=G2NC@c1%3rEzw zN<6i?h;CJX>h)kn49Sr)g#Em6km6ESP`1qc5C3ZHizN>r>V-fSS=X1nT{+Thh@kC! z(H=PlqDt7V6gOYezXUK-dretz!1?IUD6&eL2b!4=9h+HUO&DYZKMM>|YhlEEg?q?S z^XT4$2Fd|zT=x3U#L1|F;-#`to-Y6hiYkWdO=rRC)meY72pIfl`3zEGDU8($iWR^K zI$nq80aSJII<;#W5Pj>^_T&013BJ*O89Uoq z5>;Paa^E}xar^r=!pexg&OTM8wluk4R~Ru=)Hgk`Y#i_$jk{jc8hx}?(dW*X!l4vs z6_%$s#duJJFmaFc-5#>v6Yea=I~)s_pXGS>Tkz?s+WS}>Qp<9MappMLXpkXpSM~SmH6u)`Z5>o02kJs;w@KhdiZ3}29y*xr|6tMo zBHzGic+b+dTd!xOJ;p{Rguh^corJ;K?R6daayQKm+0rf7|AXg0qs!R9eS7t4{G=fs z1$=?kK1Ih=gEkI>@jgXDWHZt*C7FUEWs|u^pE3Z``^K|1KEC^sbN*4nQUfRc_AyE0 zn)?RrGjgPkzfE~_s!rDB!fDsV+*|kEX4+DyS#8%!cshn;s8svwBXSsDGX2ZRa0={* z=`p1F{zD17*Rk>Uk_cw3t5j=9-d6$}MoM~z{v{t^M!g75-+o8_XkP@CZWUQ2z!^26 zCNOu~hgrrK)y>bgqb{`Q_1^zrG4;cGarP!nb4E~(ZKWc`LVeEq;IewVneLp^ZU2+% z95PgN*M5v7Q;ZlGvM#`&u2NdHm%&gZ{bZM5wBCp&?HeZhwU87wyT_z!n4z+1?=RvXZ^72d*%+R1s1$KbAFtR|= zw;MEq=O7pMIKpFwKH6$OOszJAf<_Z<1)36cB>D>|Z6$gJL~jH`n3MMou$#Si%rDAu z4pSkJspG|^CJ86vg6kkfXsA_`8@8iOryOe!Qhn8SV6}mPlof3=WJRVqAr_b;e->`Z zMR(p|K|$L0^6;u~USxg#B6-ZNc%E1dv*^P=|2k*^NOBni#G%9Y?##{=)8KZwh85OL zSBG9|gb|hdmY^gn(ziY&O5#@I?W)W;361Yb^VQNpz0A7&^(7HRAsUvw#)fvhocvja zLxV65J0_$>&cVRctJFsn^qLos^tG`+B0_gQ{NeOwKt-!C^gGFufdtPT*Vi>l#X1|V z2XxsAcixN)Ekq=a##_^=k_^BFH5_zpvPDRP>u6+3$}i&b zy0@FdzAHw?i9OqnlTts_w5D@Nd#eM)KKEuN#m{|AJyscxa}(eA?z4&4yvXo{OBS65 z-?gW;<+;+ntM}U_yTmHm6*2zj0Imj<&ZgE9Wj|gfsXhrVH-c0p$7HXnR8bxDYOi z=_r3FA~u`L&2;Vir8}P3)k|@c?sK1U@&iWo{HEXcoy>6wQSuJ+b4l%aTBuigs&k@Y<2c=S3Ef?p zH>ki4yDuXdo_eu>X1{E$g(Q-u#zVXN^&%70guoizo7x(kQ0OZ}H$O9UB}(FaX8Ct1 zFpx~}EbHf2r6V;x=@8GH$C2|6*?K~?LrtMYd^bw*WYXhA z_))@RMH;nZedW3+qfWbv<|_#BYOxX^rhbN+!za)|!|8K*LRs(R$O*2SDM{g9k7e{u zN4VIdi}e#0&h?sBxu$>Yy%)j(k1V2fuhp8r!}gfF@b;F?U`6}YnnMh1&sSU&lR^?# zu!61+lGsuFEfDraX3+$QZibCbKzc{75G^T7@WZSQ)j5898G1AOXB*H*TSd`f<`IK# zm1%&t?i|2Z-a&r!pJehzg@!awNp)R)aa?q_SqGrxE5u+T#f?K2;GAHV?O&>!W@Q*k)7=g2vDW+7K zbyY9i{|nOF*SbMYoRQSAbSH2y$bE5(@d6xKxcF#@TE~X#3o=;`0sc!RupdRmQsML? z&>SCwS{FOpSr+@6Uuz3m`hj}(^g`Jz|6?({!%WVJn$H|ugxW+x-GEA?J&U^ugj3Nb z;65~)W<}iH2PJ@st8LtLfSOLXYgj=9<;?ih7rq$bXW9J#!B8!Wu6#U`A$wlcoC*&` z_9Js~7%m79#+edeT&P`@_Ng@e&5J+pqpx%31tAF71)pcz~-yJ>P5yX(nuM4;bUHDa8E(~~l{j~JeCGkX>nHJDpgSf&bTHEf)qw8{Q~CBPEVen|MW2P3vmf`8X9-g|>>ddp zcgfjbl~(?3Wa*NzQH>4nsM$3}Ul>pX1xC0oF3TZXe7=V!9!n?WgvH|R zpbruczmB%z=zkZ>=1R|gXwGThLELqD5KCUhtiRGT*JwKIvzbzV%ZU!e!VcNHSSX3> zObH|oohc8nvQZ2}q??C}@>!fe3gH+HF@4(qWqi>;ag~md#D;cl8&gQb^?2a@5cikT z=7r78@&5gV3Ggc9f=<<8v~yz`NcEGvbX1V_`IL(&+Z>LB zM~$ok2qXzod@1$TEl*U~H$V5g$er{Uj^($sWb7Nr{gsIbE(`$LRGECTOraXiU%=uq z0zvpi1S%)RxTjzoVcR4#10)fs()4Mtsa@e?9j)Bk!LsYyXIZga2q7d%`vQE!V@<1Y zmkpH3LeXJNO9f7l>F84g;huc=4nk(UnU}RLZmYk2TtB#lv34K(?8~gyx-mN%g=U44 zOPdr_!j-;IEbe|l9-buuKEy^Q9MLjSKG$S6dz)!U_32{1)N}L)3+COmlg=nY1@od$ zJ<0z-B%sisAR1yh>z-RfQQb6M4i-d#vxvb~f69M{JLPZv1JSCh1$gQ*LxOF-tH9!k zbQ0ZW)S7)qCSF|=2`q_A3}OHBNBueZwTTz^ar~gz#2KA74&&D)KHt~m4F_nK<^*7_ z!!pN@xiGkq%>1N(rNxw$zu-=1t*IpAy$ z4~dD0w%9;E?(greVWZ3(o9ux`elM>Rek#0 zO=#-(4p5B+wFzlEU7^k{3EdL6sIp|K*>xrriI`}E8ze|z-$YpN`^_teL_7P`%e>IN z7tNiH619P+0Q1hBR|W#POOta)1|LkIRtgz zMJ9VOxXN#o)mlXS=u%`Q>~PBuKEmOWsIuQRp{y%!ty{fEyL0gV)$LQeL#pqX3L@SR zJ2Gb^E9+KVd?;joVOXlGie3?z6>(>u(i!(qGz(W( ze~^xj&IRF<98ypEis{Y_FoHn%C0bW(XeF#Lj=2WUEBqKNPPFppEH?_a3}-h906X}C zSYKcZFU`Om5YlWhh@ogzCn3NvuM~F9jOX|xe-X*!YL+#ceh_tJoHXz`aTnvSrOAZ| zOtdGz?QdT!oAJr3(XL2G(p%2X4{xEohU&vd_zQ(U%ihHOlKPWnb$&YYhx48?|R++>`5?sxvM?!;ru|9 zZ#nwuTK^S%ce<+ggdJBE&fRrXN7O!{nu`%q`M{2Ef_+IRad2cf01P9pST9AOK>y75c!9}~)Et^6$`&Nm{wzWcm4c0j9DF!xJTpGrMp3esI4D_iiDe`sswXSu{dQZE_`^A11 z?Z@Hw=65mVu^%X`>;$mciK}XiZ{xw7I_!t)S00^JuxdCXhIRO~S*lPS(S^je`DH4E zxbKNs8RL`N?gCQ@YSOU=>0FE#Ku#DRO7JA&fu-X8b;3!^#{=7`WsDXUxfUsE(FKSQ z&=N`A7IwLq%+vt(F;z+T=uZNl=@K4|E%p{p^o5(BGjsE|WOR`%8+XgGW8xJTFJc4L zVY#L`OdnSM{HyS$fX1)3_JuNNH1aDsDqi>CzCT5=kY5zV<~29bX)c^I8R5n&ymHkx zj(QC4t#mDK;2xi8O%V;C{HqDQeM64=b4@sa*N_K0a&ro4+8LY6cFHz< ze|!g}zF|tDrP=`+U7KwKl20gdW1%!iN>1=uxA|NZJ2peruBOj?RBPb~8G;s6xIi6- z?_odhafsxoxiBf zwZZ)c*)FLc0#wE~bXw0TPBYl+h9hs|DYr_B4LR_YL@S1hQs=p zNEh%_fUvWZCbJtaF#kP5=(O#{8|g&Kmz1&8{@Lufw^DhtvKx955~aqxi2C=)Z-!Kd z+m-u+#^U4(HYn6a1w652kO0bYBt&goyx(n?MR^kI+{Q?0Y{G~W2) z0dS3fuJ?SU(6ZDp=kUley%PK}K_;YQyK|U|?7t9SHiyIfpT4a_kUVIhH4PSaj@3mo z`z}|mHhx1Pq?@(3vTBb5HTXuFAzFZEt0D-fw_kd=XvwIUh3VXTm{wbDA~cESd5cI1 zd>6=&AvG3yu+)`9oxmfrDQ(1fzv(_0l?bp{a364dXLRRBI8kBv!KsL;brY)#E3`o{ z3TlWUsS0{Voci?6MejccG9x_KiqN>So*1{25r6BSl9jUyR}1TgXBLL7Pr6Wv~Nu47;fbiU7TbL}>qmtl36YSZ() zVf@nqW(As~#`@bIC+AxSw!O5Pocf&rYaCFm?Jd?XR)p#@{!|5^Ws@wd855)mI^8y{ zws+VvGXW6%xoj@JkGb=~%oJ~7m6+uhOv?bH+jJJ~eFgp+}~*^C+3>R-MY!IZQoabCh( zN(T+z@Oyc^C)WqQESmh{d!!T8zS(!wX=R#hEKxMXy(eg zZ+Cwm1a%?;RH$h2_ws|nRjn8ZY!>3gn+6Ep4xT|AeFox7!rac2Lw?jsz}JqPE?5JG zok0}q1P;cuzs%Yrze|&d$oTr<`Lx{fbq2OV=!3v-ODq(n?|WxuhtmwJBIoW^^FB+D z-?Ok9HBKc5@)L(W&vmI{prL?4^OE9TR)bELS=<>*w%&aKjzi*@;5#P3moG@dm{Eke zhE#Is;&=o|{2GWai}7LYEI+gmc^Kj4K7w7n)+9godg?yB2?xs}pF1<*!Sv?D~Uvbkgs9xx9s#6zBv9l@ox>d#H6eqw^KZO;Vg}h!q zI33^$4}yF*q+q{DsJsa(SsV!YQ#zi^IF9MQV6i{SiN4dWWCi%YQ+hNc1r!^+<(YnB zG62-D`M3w3Q2;@X{S`n`{QO>migDpz0FK`->sYDOESs6u>-~<}_XN_6><2g7U#XC{ z$#Ig;n{_yEMnlvx-lP*;ts#DHV0r8j518>~33?Ak#jocW>uk>6V||p7{4rov#RS9c zdPD6r`qF1om9r!zS4Jk1>7fn#GCnmD=JIt1Na`X)=*LP7R!3XATgk`;&U*P<(0d z9p<0T&eYqQ9jot39FxpfuPSPYlfQ$s-*;+c1KL+cHIVcG5`H~^Ryu1Hk7%Nf$TCwR!SzG31@NHpm`mcp8v!wyWM49TjTxASJ-8JP*MTHLC}hF==PUOh8kaaXeGFGd<|e29vSDaS ztPeu&zv0^wN}Hahi`$pcDs~FVt2F;K!q}q*Y@{7i#stWfU`u2La4aerBKhV`^zG~j zJWvtZpcHIP7x*tfLSQcng6D(`HVp4=LWp_0Xt=2wEHjK)!DSz_Z?5J@>awRyk?azj zU-kdSs~cp))*pfJ_q7u`IsCq8F|OShB~D56S(Mwwlt?{yURE7#eI&WcpVq(@9Fd~g zeUiD!a4w51Nj(YzLnau+O3MDub|?loF0=<#jLztAM>PruE7yNDD0L}y=Ayuc?^?Ni zf~%GK=iEhn2}xKp7GonJx!JpDmDsco$|$XtRdUDwbM9$9s7x9-of2nKNj~?b@UOKz z9{`=Irz^ba-c&1vSQxSh;I2`cKc8-4)aCy%#bam;3_8vSJ-jw`_}lyukEC~z00EbC zI*dU3F21A)dSZr{qA5QF+{a%D`h#?8o%M?)*hWxuqnQD(TpcmfNq&UN$BmB)0!r8) zxno@Q?$_D&*4(rW6b+?-Y^5|*P`DHmJ%pI<6*yP)o}2^?>d7P#bd2j=vvx2mfLW@R zQLD`%buR*}nzNYNf%68w-D$7%v|=bXg1mYrdZy~}(@RRZ-U+Gx=nmCjVxr5Ag# zLw3R29-MHJl|`mRxj#sv@EfyR#-q>BE-XFEENbV$#dWM?!VjU8~kKZsd@G=HPrI{HiqN&j<92*-3$^M*;n@rG*i! zvi#?j;lc5w>@+r!6*CVUrN9as=S3?(ZBT979$5R#ZpPm?2VjIyQcEFp9orGR>f;G? zK<~FiYY6ow-&}|v7k?+03TC++so$)2~rN``u z>N%j$AbNQLX_!evzG8abf=15260vIXdz7K^a$YS)iw{@x5<|Rr#ii|ov=LJ{eu>dZYe_ip$ZuzvRu1dpjQK1BvP zH~m#t=2_wy>9+YkdNF-z` zQ*#7=^r%R*pIi2AI`>n9>(QJVE1k8?Ilav<)NUjW^O$}^yZZ{_Uwn!4Fq1`aslX;Y zj`XDIm`E1sz|wShA=?a@ZGKDSMU#Z3$E!1nZ)g^Eg3ZDoSN6@RXrGVCHvMIauS7d> zuJltXf9)LdTWdF!n%-iA9b#2$W#i??K)zYho^((ZqluvhAr@{H{diy0%@-~VW zKYC|2Ma)2^=skdLT@ZVqJfiCDqS@~qIGexL(BKy6Aw9ch0hoHN&E+m3*uka9+AIh3gTWdSe~W({-&^oFw`!j7$DcsF$7`pO?kRMK<9h=SV?cmyJIe`$4|zoI(6u9#qY9zM?#zNe^!Dl2>Z^dH`>`wSY# ztU;V*+g0R0DH6EnJA$U{QL&T~&s{`smeC2I-5mzv=v$l@iF;yN0hMibU=CG^e>J;+9k`Si9PzLaj$>}QKI6lWmO_o+_( zmhxA*0|-Na`+*J1qEMIXZf9rb#;pcOw>EDeDjb!|GumQ2!1ac;YqU|X;F@l1_lemzTN0J|U zFJF(kO21aHg)*KfuKT=BA{VDkOvlx(b{f|A9D69_BHUm#S$F>~`Mt@GesjLp3;reY zP~q>6Tt;`XkjqV?i7lqPbWGh`y<7dq<}pDHl-dDA4QG6`QDq)+vq_&HfW!}P6Cp4d zt>Qnli5ri*I1ILEOGD~3Y!@2^Jmcy1xDXmKolC?at}_6;neEfca0rLHT}NLpoUYh` zDbCtfZnYN&>}m-(F{5d1=)bBuZ?OcP`GmsQV@kn%JMJUIep`Avon#8=ATpEo-@hg& z12f-)R=HCD%pUjvbWa|P!}u)=wInpZG*LHKrZDMeC>Qils^IyY)x;kDRs4c3!DDOG zAptSsf#1X>kSli|Qka@S)6O4un-2aKL?bcV;$*>KSxHovjrfZ^-+c#>;(42yj71K| zzRyFiLrwv$rPcNA{mtv=o(*JDA0kS93>OE0D{KMJzLk$cc_5dCLWnJcFJd6_>BpE< z?aW9;^!;arQcIjloW&YL+~MkNO&a>N=pmhg>{SM<@`a&VeUA`ay*P@R$_+WS2%r?_ zs&Z%c`>ie+%!I=Lz>$9$7a`-`hoc&*dl60^whsaQ;~9~@JYn1Oc_bmgVVyAzUOYgZ z#j{`#D_YZ)(wa5;qzR#zo4a|-ANJjBB90r4Iun3*BkMxw_Ti>SjhktsmR|BPCLt>9 zZ_3eQjweI*-8+HNt)$9^s|+10w@sU!PY{`#BnF!ULS=#{k0Zr5`yOS?p8PfWbKT`6 z@T+PeRJ4`fj5t8bMs)0>o9|C>mBTlfQ*nFG#Rri-Q7}E}+eaz`LmO!`Y_pHkoAruu z`&!5VNnA3IG$}Pz)V&pt&AF!$E{J-;or3vWv3&Sl&9KzG+ae73Zf}=aP*SCI1{?0T z9SAC)W(?DSKOkcmW$(K5Bl?c@(5#>J#j@eq#ctX~$TIjkl>Wrfv%Ey+bl1Z-v?NxJ zwZ9!ae-MsHPUx&_W22?9$mCE%&~lzVG?hDXM%~gXGk+Q!Jf0BspkMWxy;^!n<6JIrSYjv z6F%~$8)0^qbUho9Sdf97b_n({$;|XH9-RHrohHuPcro@03KEPFejN&q?&nJFoIQY; zSI#uL6>2^^yOR!51OLO65xGas55dPG;3=uQ35ZYW04#+~byXQf^7Vq`G z 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 Pe7HEdAEg|=c=LY&YVNkY literal 0 HcmV?d00001 diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000000000000000000000000000000000000..13b35eba55c6dabc3aac36f33d859266c18fa0d0 GIT binary patch literal 5680 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 literal 0 HcmV?d00001 diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000000000000000000000000000000000000..0a3f5fa40fb3d1e0710331a48de5d256da3f275d GIT binary patch literal 520 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 literal 0 HcmV?d00001 diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000000000000000000000000000000000000..2f1632cfddf3d9dade342351e627a0a75609fb46 GIT binary patch literal 2218 zcmV;b2vzrqP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91K%fHv1ONa40RR91KmY&$07g+lumAuE6iGxuRCodHTWf3-RTMruyW6Fu zQYeUM04eX6D5c0FCjKKPrco1(K`<0SL=crI{PC3-^hZU0kQie$gh-5!7z6SH6Q0J% zqot*`H1q{R5fHFYS}dje@;kG=v$L0(yY0?wY2%*c?A&{2?!D*x?m71{of2gv!$5|C z3>qG_BW}7K_yUcT3A5C6QD<+{aq?x;MAUyAiJn#Jv8_zZtQ{P zTRzbL3U9!qVuZzS$xKU10KiW~Bgdcv1-!uAhQxf3a7q+dU6lj?yoO4Lq4TUN4}h{N z*fIM=SS8|C2$(T>w$`t@3Tka!(r!7W`x z-isCVgQD^mG-MJ;XtJuK3V{Vy72GQ83KRWsHU?e*wrhKk=ApIYeDqLi;JI1e zuvv}5^Dc=k7F7?nm3nIw$NVmU-+R>> zyqOR$-2SDpJ}Pt;^RkJytDVXNTsu|mI1`~G7yw`EJR?VkGfNdqK9^^8P`JdtTV&tX4CNcV4 z&N06nZa??Fw1AgQOUSE2AmPE@WO(Fvo`%m`cDgiv(fAeRA%3AGXUbsGw{7Q`cY;1BI#ac3iN$$Hw z0LT0;xc%=q)me?Y*$xI@GRAw?+}>=9D+KTk??-HJ4=A>`V&vKFS75@MKdSF1JTq{S zc1!^8?YA|t+uKigaq!sT;Z!&0F2=k7F0PIU;F$leJLaw2UI6FL^w}OG&!;+b%ya1c z1n+6-inU<0VM-Y_s5iTElq)ThyF?StVcebpGI znw#+zLx2@ah{$_2jn+@}(zJZ{+}_N9BM;z)0yr|gF-4=Iyu@hI*Lk=-A8f#bAzc9f z`Kd6K--x@t04swJVC3JK1cHY-Hq+=|PN-VO;?^_C#;coU6TDP7Bt`;{JTG;!+jj(` zw5cLQ-(Cz-Tlb`A^w7|R56Ce;Wmr0)$KWOUZ6ai0PhzPeHwdl0H(etP zUV`va_i0s-4#DkNM8lUlqI7>YQLf)(lz9Q3Uw`)nc(z3{m5ZE77Ul$V%m)E}3&8L0 z-XaU|eB~Is08eORPk;=<>!1w)Kf}FOVS2l&9~A+@R#koFJ$Czd%Y(ENTV&A~U(IPI z;UY+gf+&6ioZ=roly<0Yst8ck>(M=S?B-ys3mLdM&)ex!hbt+ol|T6CTS+Sc0jv(& z7ijdvFwBq;0a{%3GGwkDKTeG`b+lyj0jjS1OMkYnepCdoosNY`*zmBIo*981BU%%U z@~$z0V`OVtIbEx5pa|Tct|Lg#ZQf5OYMUMRD>Wdxm5SAqV2}3!ceE-M2 z@O~lQ0OiKQp}o9I;?uxCgYVV?FH|?Riri*U$Zi_`V2eiA>l zdSm6;SEm6#T+SpcE8Ro_f2AwxzI z44hfe^WE3!h@W3RDyA_H440cpmYkv*)6m1XazTqw%=E5Xv7^@^^T7Q2wxr+Z2kVYr + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/macos/Runner/Configs/AppInfo.xcconfig b/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 00000000..7b3ea9f2 --- /dev/null +++ b/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = vpnclient_engine_flutter_example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = click.vpnclient.engine.flutter.vpnclientEngineFlutterExample + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2025 click.vpnclient.engine.flutter. All rights reserved. diff --git a/example/macos/Runner/Configs/Debug.xcconfig b/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 00000000..36b0fd94 --- /dev/null +++ b/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/example/macos/Runner/Configs/Release.xcconfig b/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 00000000..dff4f495 --- /dev/null +++ b/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/example/macos/Runner/Configs/Warnings.xcconfig b/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 00000000..42bcbf47 --- /dev/null +++ b/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/example/macos/Runner/DebugProfile.entitlements b/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 00000000..dddb8a30 --- /dev/null +++ b/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/example/macos/Runner/Info.plist b/example/macos/Runner/Info.plist new file mode 100644 index 00000000..4789daa6 --- /dev/null +++ b/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/example/macos/Runner/MainFlutterWindow.swift b/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 00000000..3cc05eb2 --- /dev/null +++ b/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/example/macos/Runner/Release.entitlements b/example/macos/Runner/Release.entitlements new file mode 100644 index 00000000..852fa1a4 --- /dev/null +++ b/example/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/example/macos/RunnerTests/RunnerTests.swift b/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..6acf8663 --- /dev/null +++ b/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,28 @@ +import Cocoa +import FlutterMacOS +import XCTest + + +@testable import vpnclient_engine_flutter + +// This demonstrates a simple unit test of the Swift portion of this plugin's implementation. +// +// See https://developer.apple.com/documentation/xctest for more information about using XCTest. + +class RunnerTests: XCTestCase { + + func testGetPlatformVersion() { + let plugin = VpnclientEngineFlutterPlugin() + + let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: []) + + let resultExpectation = expectation(description: "result block must be called.") + plugin.handle(call) { result in + XCTAssertEqual(result as! String, + "macOS " + ProcessInfo.processInfo.operatingSystemVersionString) + resultExpectation.fulfill() + } + waitForExpectations(timeout: 1) + } + +} diff --git a/example/pubspec.lock b/example/pubspec.lock new file mode 100644 index 00000000..785375a9 --- /dev/null +++ b/example/pubspec.lock @@ -0,0 +1,296 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + url: "https://pub.dev" + source: hosted + version: "2.12.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + url: "https://pub.dev" + source: hosted + version: "1.3.2" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + url: "https://pub.dev" + source: hosted + version: "10.0.8" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.dev" + source: hosted + version: "5.1.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + process: + dependency: transitive + description: + name: process + sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d" + url: "https://pub.dev" + source: hosted + version: "5.0.3" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + sync_http: + dependency: transitive + description: + name: sync_http + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.dev" + source: hosted + version: "0.3.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + url: "https://pub.dev" + source: hosted + version: "14.3.1" + vpnclient_engine_flutter: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "0.0.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + webdriver: + dependency: transitive + description: + name: webdriver + sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" + url: "https://pub.dev" + source: hosted + version: "3.0.4" +sdks: + dart: ">=3.7.2 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/example/pubspec.yaml b/example/pubspec.yaml new file mode 100644 index 00000000..9410f84e --- /dev/null +++ b/example/pubspec.yaml @@ -0,0 +1,85 @@ +name: vpnclient_engine_flutter_example +description: "Demonstrates how to use the vpnclient_engine_flutter plugin." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +environment: + sdk: ^3.7.2 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + vpnclient_engine_flutter: + # When depending on this package from a real application you should use: + # vpnclient_engine_flutter: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + +dev_dependencies: + integration_test: + sdk: flutter + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^5.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/test/widget_test.dart b/example/test/widget_test.dart similarity index 52% rename from test/widget_test.dart rename to example/test/widget_test.dart index a1cf4798..6774e59b 100644 --- a/test/widget_test.dart +++ b/example/test/widget_test.dart @@ -8,23 +8,20 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:vpnclient_engine_flutter/main.dart'; +import 'package:vpnclient_engine_flutter_example/main.dart'; void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { + testWidgets('Verify Platform version', (WidgetTester tester) async { // Build our app and trigger a frame. await tester.pumpWidget(const MyApp()); - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); + // Verify that platform version is retrieved. + expect( + find.byWidgetPredicate( + (Widget widget) => widget is Text && + widget.data!.startsWith('Running on:'), + ), + findsOneWidget, + ); }); } diff --git a/web/favicon.png b/example/web/favicon.png similarity index 100% rename from web/favicon.png rename to example/web/favicon.png diff --git a/web/icons/Icon-192.png b/example/web/icons/Icon-192.png similarity index 100% rename from web/icons/Icon-192.png rename to example/web/icons/Icon-192.png diff --git a/web/icons/Icon-512.png b/example/web/icons/Icon-512.png similarity index 100% rename from web/icons/Icon-512.png rename to example/web/icons/Icon-512.png diff --git a/web/icons/Icon-maskable-192.png b/example/web/icons/Icon-maskable-192.png similarity index 100% rename from web/icons/Icon-maskable-192.png rename to example/web/icons/Icon-maskable-192.png diff --git a/web/icons/Icon-maskable-512.png b/example/web/icons/Icon-maskable-512.png similarity index 100% rename from web/icons/Icon-maskable-512.png rename to example/web/icons/Icon-maskable-512.png diff --git a/web/index.html b/example/web/index.html similarity index 86% rename from web/index.html rename to example/web/index.html index 55e5e310..0b894c0a 100644 --- a/web/index.html +++ b/example/web/index.html @@ -18,18 +18,18 @@ - + - + - vpnclient_engine_flutter + vpnclient_engine_flutter_example diff --git a/web/manifest.json b/example/web/manifest.json similarity index 81% rename from web/manifest.json rename to example/web/manifest.json index 17f2c0f6..8f034496 100644 --- a/web/manifest.json +++ b/example/web/manifest.json @@ -1,11 +1,11 @@ { - "name": "vpnclient_engine_flutter", - "short_name": "vpnclient_engine_flutter", + "name": "vpnclient_engine_flutter_example", + "short_name": "vpnclient_engine_flutter_example", "start_url": ".", "display": "standalone", "background_color": "#0175C2", "theme_color": "#0175C2", - "description": "A new Flutter project.", + "description": "Demonstrates how to use the vpnclient_engine_flutter plugin.", "orientation": "portrait-primary", "prefer_related_applications": false, "icons": [ diff --git a/example/windows/.gitignore b/example/windows/.gitignore new file mode 100644 index 00000000..d492d0d9 --- /dev/null +++ b/example/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/example/windows/CMakeLists.txt b/example/windows/CMakeLists.txt new file mode 100644 index 00000000..9a324f4f --- /dev/null +++ b/example/windows/CMakeLists.txt @@ -0,0 +1,110 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(vpnclient_engine_flutter_example LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "vpnclient_engine_flutter_example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Enable the test target. +set(include_vpnclient_engine_flutter_tests TRUE) + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/example/windows/flutter/CMakeLists.txt b/example/windows/flutter/CMakeLists.txt new file mode 100644 index 00000000..903f4899 --- /dev/null +++ b/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/example/windows/runner/CMakeLists.txt b/example/windows/runner/CMakeLists.txt new file mode 100644 index 00000000..394917c0 --- /dev/null +++ b/example/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/example/windows/runner/Runner.rc b/example/windows/runner/Runner.rc new file mode 100644 index 00000000..438a5e25 --- /dev/null +++ b/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "click.vpnclient.engine.flutter" "\0" + VALUE "FileDescription", "vpnclient_engine_flutter_example" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "vpnclient_engine_flutter_example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2025 click.vpnclient.engine.flutter. All rights reserved." "\0" + VALUE "OriginalFilename", "vpnclient_engine_flutter_example.exe" "\0" + VALUE "ProductName", "vpnclient_engine_flutter_example" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/example/windows/runner/flutter_window.cpp b/example/windows/runner/flutter_window.cpp new file mode 100644 index 00000000..955ee303 --- /dev/null +++ b/example/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/example/windows/runner/flutter_window.h b/example/windows/runner/flutter_window.h new file mode 100644 index 00000000..6da0652f --- /dev/null +++ b/example/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/example/windows/runner/main.cpp b/example/windows/runner/main.cpp new file mode 100644 index 00000000..f1809b08 --- /dev/null +++ b/example/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"vpnclient_engine_flutter_example", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/example/windows/runner/resource.h b/example/windows/runner/resource.h new file mode 100644 index 00000000..66a65d1e --- /dev/null +++ b/example/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/example/windows/runner/resources/app_icon.ico b/example/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c04e20caf6370ebb9253ad831cc31de4a9c965f6 GIT binary patch literal 33772 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 literal 0 HcmV?d00001 diff --git a/example/windows/runner/runner.exe.manifest b/example/windows/runner/runner.exe.manifest new file mode 100644 index 00000000..153653e8 --- /dev/null +++ b/example/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/example/windows/runner/utils.cpp b/example/windows/runner/utils.cpp new file mode 100644 index 00000000..3a0b4651 --- /dev/null +++ b/example/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/example/windows/runner/utils.h b/example/windows/runner/utils.h new file mode 100644 index 00000000..3879d547 --- /dev/null +++ b/example/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/example/windows/runner/win32_window.cpp b/example/windows/runner/win32_window.cpp new file mode 100644 index 00000000..60608d0f --- /dev/null +++ b/example/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/example/windows/runner/win32_window.h b/example/windows/runner/win32_window.h new file mode 100644 index 00000000..e901dde6 --- /dev/null +++ b/example/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/ios/.gitignore b/ios/.gitignore index 7a7f9873..034771fc 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -1,34 +1,38 @@ -**/dgph +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ + +*.pbxuser *.mode1v3 *.mode2v3 -*.moved-aside -*.pbxuser *.perspectivev3 -**/*sync/ -.sconsign.dblite -.tags* -**/.vagrant/ -**/DerivedData/ -Icon? -**/Pods/ -**/.symlinks/ -profile -xcuserdata -**/.generated/ -Flutter/App.framework -Flutter/Flutter.framework -Flutter/Flutter.podspec -Flutter/Generated.xcconfig -Flutter/ephemeral/ -Flutter/app.flx -Flutter/app.zip -Flutter/flutter_assets/ -Flutter/flutter_export_environment.sh -ServiceDefinitions.json -Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. + +!default.pbxuser !default.mode1v3 !default.mode2v3 -!default.pbxuser !default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/Generated.xcconfig +/Flutter/ephemeral/ +/Flutter/flutter_export_environment.sh diff --git a/ios/Assets/.gitkeep b/ios/Assets/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/ios/Classes/VpnclientEngineFlutterPlugin.swift b/ios/Classes/VpnclientEngineFlutterPlugin.swift new file mode 100644 index 00000000..7763e479 --- /dev/null +++ b/ios/Classes/VpnclientEngineFlutterPlugin.swift @@ -0,0 +1,19 @@ +import Flutter +import UIKit + +public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "vpnclient_engine_flutter", binaryMessenger: registrar.messenger()) + let instance = VpnclientEngineFlutterPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "getPlatformVersion": + result("iOS " + UIDevice.current.systemVersion) + default: + result(FlutterMethodNotImplemented) + } + } +} diff --git a/ios/Resources/PrivacyInfo.xcprivacy b/ios/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..a34b7e2e --- /dev/null +++ b/ios/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,14 @@ + + + + + NSPrivacyTrackingDomains + + NSPrivacyAccessedAPITypes + + NSPrivacyCollectedDataTypes + + NSPrivacyTracking + + + diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift deleted file mode 100644 index 86a7c3b1..00000000 --- a/ios/RunnerTests/RunnerTests.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Flutter -import UIKit -import XCTest - -class RunnerTests: XCTestCase { - - func testExample() { - // If you add code to the Runner application, consider adding tests here. - // See https://developer.apple.com/documentation/xctest for more information about using XCTest. - } - -} diff --git a/ios/vpnclient_engine_flutter.podspec b/ios/vpnclient_engine_flutter.podspec new file mode 100644 index 00000000..3a82f206 --- /dev/null +++ b/ios/vpnclient_engine_flutter.podspec @@ -0,0 +1,29 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint vpnclient_engine_flutter.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'vpnclient_engine_flutter' + s.version = '0.0.1' + s.summary = 'A new Flutter plugin project.' + s.description = <<-DESC +A new Flutter plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'Flutter' + s.platform = :ios, '12.0' + + # Flutter.framework does not contain a i386 slice. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } + s.swift_version = '5.0' + + # If your plugin requires a privacy manifest, for example if it uses any + # required reason APIs, update the PrivacyInfo.xcprivacy file to describe your + # plugin's privacy impact, and then uncomment this line. For more information, + # see https://developer.apple.com/documentation/bundleresources/privacy_manifest_files + # s.resource_bundles = {'vpnclient_engine_flutter_privacy' => ['Resources/PrivacyInfo.xcprivacy']} +end diff --git a/lib/vpnclient_engine_flutter.dart b/lib/vpnclient_engine_flutter.dart new file mode 100644 index 00000000..81ce3750 --- /dev/null +++ b/lib/vpnclient_engine_flutter.dart @@ -0,0 +1,8 @@ + +import 'vpnclient_engine_flutter_platform_interface.dart'; + +class VpnclientEngineFlutter { + Future getPlatformVersion() { + return VpnclientEngineFlutterPlatform.instance.getPlatformVersion(); + } +} diff --git a/lib/vpnclient_engine_flutter_method_channel.dart b/lib/vpnclient_engine_flutter_method_channel.dart new file mode 100644 index 00000000..b1563b3f --- /dev/null +++ b/lib/vpnclient_engine_flutter_method_channel.dart @@ -0,0 +1,17 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +import 'vpnclient_engine_flutter_platform_interface.dart'; + +/// An implementation of [VpnclientEngineFlutterPlatform] that uses method channels. +class MethodChannelVpnclientEngineFlutter extends VpnclientEngineFlutterPlatform { + /// The method channel used to interact with the native platform. + @visibleForTesting + final methodChannel = const MethodChannel('vpnclient_engine_flutter'); + + @override + Future getPlatformVersion() async { + final version = await methodChannel.invokeMethod('getPlatformVersion'); + return version; + } +} diff --git a/lib/vpnclient_engine_flutter_platform_interface.dart b/lib/vpnclient_engine_flutter_platform_interface.dart new file mode 100644 index 00000000..f8a6549a --- /dev/null +++ b/lib/vpnclient_engine_flutter_platform_interface.dart @@ -0,0 +1,29 @@ +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'vpnclient_engine_flutter_method_channel.dart'; + +abstract class VpnclientEngineFlutterPlatform extends PlatformInterface { + /// Constructs a VpnclientEngineFlutterPlatform. + VpnclientEngineFlutterPlatform() : super(token: _token); + + static final Object _token = Object(); + + static VpnclientEngineFlutterPlatform _instance = MethodChannelVpnclientEngineFlutter(); + + /// The default instance of [VpnclientEngineFlutterPlatform] to use. + /// + /// Defaults to [MethodChannelVpnclientEngineFlutter]. + static VpnclientEngineFlutterPlatform get instance => _instance; + + /// Platform-specific implementations should set this with their own + /// platform-specific class that extends [VpnclientEngineFlutterPlatform] when + /// they register themselves. + static set instance(VpnclientEngineFlutterPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + Future getPlatformVersion() { + throw UnimplementedError('platformVersion() has not been implemented.'); + } +} diff --git a/lib/vpnclient_engine_flutter_web.dart b/lib/vpnclient_engine_flutter_web.dart new file mode 100644 index 00000000..47044070 --- /dev/null +++ b/lib/vpnclient_engine_flutter_web.dart @@ -0,0 +1,26 @@ +// In order to *not* need this ignore, consider extracting the "web" version +// of your plugin as a separate package, instead of inlining it in the same +// package as the core of your plugin. +// ignore: avoid_web_libraries_in_flutter + +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; +import 'package:web/web.dart' as web; + +import 'vpnclient_engine_flutter_platform_interface.dart'; + +/// A web implementation of the VpnclientEngineFlutterPlatform of the VpnclientEngineFlutter plugin. +class VpnclientEngineFlutterWeb extends VpnclientEngineFlutterPlatform { + /// Constructs a VpnclientEngineFlutterWeb + VpnclientEngineFlutterWeb(); + + static void registerWith(Registrar registrar) { + VpnclientEngineFlutterPlatform.instance = VpnclientEngineFlutterWeb(); + } + + /// Returns a [String] containing the version of the platform. + @override + Future getPlatformVersion() async { + final version = web.window.navigator.userAgent; + return version; + } +} diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt new file mode 100644 index 00000000..f40672b4 --- /dev/null +++ b/linux/CMakeLists.txt @@ -0,0 +1,94 @@ +# The Flutter tooling requires that developers have CMake 3.10 or later +# installed. You should not increase this version, as doing so will cause +# the plugin to fail to compile for some customers of the plugin. +cmake_minimum_required(VERSION 3.10) + +# Project-level configuration. +set(PROJECT_NAME "vpnclient_engine_flutter") +project(${PROJECT_NAME} LANGUAGES CXX) + +# This value is used when generating builds using this plugin, so it must +# not be changed. +set(PLUGIN_NAME "vpnclient_engine_flutter_plugin") + +# Any new source files that you add to the plugin should be added here. +list(APPEND PLUGIN_SOURCES + "vpnclient_engine_flutter_plugin.cc" +) + +# Define the plugin library target. Its name must not be changed (see comment +# on PLUGIN_NAME above). +add_library(${PLUGIN_NAME} SHARED + ${PLUGIN_SOURCES} +) + +# Apply a standard set of build settings that are configured in the +# application-level CMakeLists.txt. This can be removed for plugins that want +# full control over build settings. +apply_standard_settings(${PLUGIN_NAME}) + +# Symbols are hidden by default to reduce the chance of accidental conflicts +# between plugins. This should not be removed; any symbols that should be +# exported should be explicitly exported with the FLUTTER_PLUGIN_EXPORT macro. +set_target_properties(${PLUGIN_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) + +# Source include directories and library dependencies. Add any plugin-specific +# dependencies here. +target_include_directories(${PLUGIN_NAME} INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(${PLUGIN_NAME} PRIVATE flutter) +target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK) + +# List of absolute paths to libraries that should be bundled with the plugin. +# This list could contain prebuilt libraries, or libraries created by an +# external build triggered from this build file. +set(vpnclient_engine_flutter_bundled_libraries + "" + PARENT_SCOPE +) + +# === Tests === +# These unit tests can be run from a terminal after building the example. + +# Only enable test builds when building the example (which sets this variable) +# so that plugin clients aren't building the tests. +if (${include_${PROJECT_NAME}_tests}) +if(${CMAKE_VERSION} VERSION_LESS "3.11.0") +message("Unit tests require CMake 3.11.0 or later") +else() +set(TEST_RUNNER "${PROJECT_NAME}_test") +enable_testing() + +# Add the Google Test dependency. +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/release-1.11.0.zip +) +# Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +# Disable install commands for gtest so it doesn't end up in the bundle. +set(INSTALL_GTEST OFF CACHE BOOL "Disable installation of googletest" FORCE) + +FetchContent_MakeAvailable(googletest) + +# The plugin's exported API is not very useful for unit testing, so build the +# sources directly into the test binary rather than using the shared library. +add_executable(${TEST_RUNNER} + test/vpnclient_engine_flutter_plugin_test.cc + ${PLUGIN_SOURCES} +) +apply_standard_settings(${TEST_RUNNER}) +target_include_directories(${TEST_RUNNER} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") +target_link_libraries(${TEST_RUNNER} PRIVATE flutter) +target_link_libraries(${TEST_RUNNER} PRIVATE PkgConfig::GTK) +target_link_libraries(${TEST_RUNNER} PRIVATE gtest_main gmock) + +# Enable automatic test discovery. +include(GoogleTest) +gtest_discover_tests(${TEST_RUNNER}) + +endif() # CMake version check +endif() # include_${PROJECT_NAME}_tests \ No newline at end of file diff --git a/linux/include/vpnclient_engine_flutter/vpnclient_engine_flutter_plugin.h b/linux/include/vpnclient_engine_flutter/vpnclient_engine_flutter_plugin.h new file mode 100644 index 00000000..323f8ac0 --- /dev/null +++ b/linux/include/vpnclient_engine_flutter/vpnclient_engine_flutter_plugin.h @@ -0,0 +1,26 @@ +#ifndef FLUTTER_PLUGIN_VPNCLIENT_ENGINE_FLUTTER_PLUGIN_H_ +#define FLUTTER_PLUGIN_VPNCLIENT_ENGINE_FLUTTER_PLUGIN_H_ + +#include + +G_BEGIN_DECLS + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default"))) +#else +#define FLUTTER_PLUGIN_EXPORT +#endif + +typedef struct _VpnclientEngineFlutterPlugin VpnclientEngineFlutterPlugin; +typedef struct { + GObjectClass parent_class; +} VpnclientEngineFlutterPluginClass; + +FLUTTER_PLUGIN_EXPORT GType vpnclient_engine_flutter_plugin_get_type(); + +FLUTTER_PLUGIN_EXPORT void vpnclient_engine_flutter_plugin_register_with_registrar( + FlPluginRegistrar* registrar); + +G_END_DECLS + +#endif // FLUTTER_PLUGIN_VPNCLIENT_ENGINE_FLUTTER_PLUGIN_H_ diff --git a/linux/test/vpnclient_engine_flutter_plugin_test.cc b/linux/test/vpnclient_engine_flutter_plugin_test.cc new file mode 100644 index 00000000..00bf82f3 --- /dev/null +++ b/linux/test/vpnclient_engine_flutter_plugin_test.cc @@ -0,0 +1,31 @@ +#include +#include +#include + +#include "include/vpnclient_engine_flutter/vpnclient_engine_flutter_plugin.h" +#include "vpnclient_engine_flutter_plugin_private.h" + +// This demonstrates a simple unit test of the C portion of this plugin's +// implementation. +// +// Once you have built the plugin's example app, you can run these tests +// from the command line. For instance, for a plugin called my_plugin +// built for x64 debug, run: +// $ build/linux/x64/debug/plugins/my_plugin/my_plugin_test + +namespace vpnclient_engine_flutter { +namespace test { + +TEST(VpnclientEngineFlutterPlugin, GetPlatformVersion) { + g_autoptr(FlMethodResponse) response = get_platform_version(); + ASSERT_NE(response, nullptr); + ASSERT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); + FlValue* result = fl_method_success_response_get_result( + FL_METHOD_SUCCESS_RESPONSE(response)); + ASSERT_EQ(fl_value_get_type(result), FL_VALUE_TYPE_STRING); + // The full string varies, so just validate that it has the right format. + EXPECT_THAT(fl_value_get_string(result), testing::StartsWith("Linux ")); +} + +} // namespace test +} // namespace vpnclient_engine_flutter diff --git a/linux/vpnclient_engine_flutter_plugin.cc b/linux/vpnclient_engine_flutter_plugin.cc new file mode 100644 index 00000000..65f26e98 --- /dev/null +++ b/linux/vpnclient_engine_flutter_plugin.cc @@ -0,0 +1,76 @@ +#include "include/vpnclient_engine_flutter/vpnclient_engine_flutter_plugin.h" + +#include +#include +#include + +#include + +#include "vpnclient_engine_flutter_plugin_private.h" + +#define VPNCLIENT_ENGINE_FLUTTER_PLUGIN(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), vpnclient_engine_flutter_plugin_get_type(), \ + VpnclientEngineFlutterPlugin)) + +struct _VpnclientEngineFlutterPlugin { + GObject parent_instance; +}; + +G_DEFINE_TYPE(VpnclientEngineFlutterPlugin, vpnclient_engine_flutter_plugin, g_object_get_type()) + +// Called when a method call is received from Flutter. +static void vpnclient_engine_flutter_plugin_handle_method_call( + VpnclientEngineFlutterPlugin* self, + FlMethodCall* method_call) { + g_autoptr(FlMethodResponse) response = nullptr; + + const gchar* method = fl_method_call_get_name(method_call); + + if (strcmp(method, "getPlatformVersion") == 0) { + response = get_platform_version(); + } else { + response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); + } + + fl_method_call_respond(method_call, response, nullptr); +} + +FlMethodResponse* get_platform_version() { + struct utsname uname_data = {}; + uname(&uname_data); + g_autofree gchar *version = g_strdup_printf("Linux %s", uname_data.version); + g_autoptr(FlValue) result = fl_value_new_string(version); + return FL_METHOD_RESPONSE(fl_method_success_response_new(result)); +} + +static void vpnclient_engine_flutter_plugin_dispose(GObject* object) { + G_OBJECT_CLASS(vpnclient_engine_flutter_plugin_parent_class)->dispose(object); +} + +static void vpnclient_engine_flutter_plugin_class_init(VpnclientEngineFlutterPluginClass* klass) { + G_OBJECT_CLASS(klass)->dispose = vpnclient_engine_flutter_plugin_dispose; +} + +static void vpnclient_engine_flutter_plugin_init(VpnclientEngineFlutterPlugin* self) {} + +static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call, + gpointer user_data) { + VpnclientEngineFlutterPlugin* plugin = VPNCLIENT_ENGINE_FLUTTER_PLUGIN(user_data); + vpnclient_engine_flutter_plugin_handle_method_call(plugin, method_call); +} + +void vpnclient_engine_flutter_plugin_register_with_registrar(FlPluginRegistrar* registrar) { + VpnclientEngineFlutterPlugin* plugin = VPNCLIENT_ENGINE_FLUTTER_PLUGIN( + g_object_new(vpnclient_engine_flutter_plugin_get_type(), nullptr)); + + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(FlMethodChannel) channel = + fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar), + "vpnclient_engine_flutter", + FL_METHOD_CODEC(codec)); + fl_method_channel_set_method_call_handler(channel, method_call_cb, + g_object_ref(plugin), + g_object_unref); + + g_object_unref(plugin); +} diff --git a/linux/vpnclient_engine_flutter_plugin_private.h b/linux/vpnclient_engine_flutter_plugin_private.h new file mode 100644 index 00000000..022bd358 --- /dev/null +++ b/linux/vpnclient_engine_flutter_plugin_private.h @@ -0,0 +1,10 @@ +#include + +#include "include/vpnclient_engine_flutter/vpnclient_engine_flutter_plugin.h" + +// This file exposes some plugin internals for unit testing. See +// https://github.com/flutter/flutter/issues/88724 for current limitations +// in the unit-testable API. + +// Handles the getPlatformVersion method call. +FlMethodResponse *get_platform_version(); diff --git a/macos/Classes/VpnclientEngineFlutterPlugin.swift b/macos/Classes/VpnclientEngineFlutterPlugin.swift new file mode 100644 index 00000000..cd608ecc --- /dev/null +++ b/macos/Classes/VpnclientEngineFlutterPlugin.swift @@ -0,0 +1,19 @@ +import Cocoa +import FlutterMacOS + +public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "vpnclient_engine_flutter", binaryMessenger: registrar.messenger) + let instance = VpnclientEngineFlutterPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "getPlatformVersion": + result("macOS " + ProcessInfo.processInfo.operatingSystemVersionString) + default: + result(FlutterMethodNotImplemented) + } + } +} diff --git a/macos/Resources/PrivacyInfo.xcprivacy b/macos/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..918d80be --- /dev/null +++ b/macos/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,12 @@ + + + + + NSPrivacyTrackingDomains + + NSPrivacyCollectedDataTypes + + NSPrivacyTracking + + + diff --git a/macos/vpnclient_engine_flutter.podspec b/macos/vpnclient_engine_flutter.podspec new file mode 100644 index 00000000..a034289d --- /dev/null +++ b/macos/vpnclient_engine_flutter.podspec @@ -0,0 +1,30 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint vpnclient_engine_flutter.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'vpnclient_engine_flutter' + s.version = '0.0.1' + s.summary = 'A new Flutter plugin project.' + s.description = <<-DESC +A new Flutter plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + + # If your plugin requires a privacy manifest, for example if it collects user + # data, update the PrivacyInfo.xcprivacy file to describe your plugin's + # privacy impact, and then uncomment this line. For more information, + # see https://developer.apple.com/documentation/bundleresources/privacy_manifest_files + # s.resource_bundles = {'vpnclient_engine_flutter_privacy' => ['Resources/PrivacyInfo.xcprivacy']} + + s.dependency 'FlutterMacOS' + + s.platform = :osx, '10.11' + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } + s.swift_version = '5.0' +end diff --git a/pubs.sh b/pubs.sh deleted file mode 100755 index 67e11054..00000000 --- a/pubs.sh +++ /dev/null @@ -1,2 +0,0 @@ -flutter clean -flutter pub get diff --git a/pubspec.lock b/pubspec.lock index d2eebec9..063b37b2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -41,22 +41,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 - url: "https://pub.dev" - source: hosted - version: "1.0.8" - dart_ping: - dependency: "direct main" - description: - name: dart_ping - sha256: "2f5418d0a5c64e53486caaac78677b25725b1e13c33c5be834ce874ea18bd24f" - url: "https://pub.dev" - source: hosted - version: "9.0.1" fake_async: dependency: transitive description: @@ -83,6 +67,11 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" leak_tracker: dependency: transitive description: @@ -147,6 +136,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + plugin_platform_interface: + dependency: "direct main" + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" sky_engine: dependency: transitive description: flutter @@ -216,6 +213,14 @@ packages: url: "https://pub.dev" source: hosted version: "14.3.1" + web: + dependency: "direct main" + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" sdks: - dart: ">=3.7.0 <4.0.0" + dart: ">=3.7.2 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.yaml b/pubspec.yaml index 12b183be..66529bde 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,50 +1,25 @@ name: vpnclient_engine_flutter -description: "VPNclient Engine Flutter" -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev - -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -# In Windows, build-name is used as the major, minor, and patch parts -# of the product and file versions while build-number is used as the build suffix. -version: 1.0.0+1 +description: "VPN Client Engine Flutter plugin" +version: 0.0.1 +homepage: "https://vpnclient.click" environment: - sdk: ^3.7.0 + sdk: ^3.7.2 + flutter: '>=3.3.0' -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. dependencies: flutter: sdk: flutter - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. + flutter_web_plugins: + sdk: flutter + web: ^1.0.0 + plugin_platform_interface: ^2.0.2 cupertino_icons: ^1.0.8 dart_ping: ^9.0.1 - + dev_dependencies: flutter_test: sdk: flutter - - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. flutter_lints: ^5.0.0 # For information on the generic Dart part of this file, see the @@ -52,24 +27,45 @@ dev_dependencies: # The following section is specific to Flutter packages. flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) + # which should be registered in the plugin registry. This is required for + # using method channels. + # The Android 'package' specifies package in which the registered class is. + # This is required for using method channels on Android. + # The 'ffiPlugin' specifies that native code should be built and bundled. + # This is required for using `dart:ffi`. + # All these are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + android: + package: click.vpnclient.engine.flutter.vpnclient_engine_flutter + pluginClass: VpnclientEngineFlutterPlugin + ios: + pluginClass: VpnclientEngineFlutterPlugin + linux: + pluginClass: VpnclientEngineFlutterPlugin + macos: + pluginClass: VpnclientEngineFlutterPlugin + windows: + pluginClass: VpnclientEngineFlutterPluginCApi + web: + pluginClass: VpnclientEngineFlutterWeb + fileName: vpnclient_engine_flutter_web.dart - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. - uses-material-design: true - - # To add assets to your application, add an assets section, like this: + # To add assets to your plugin package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg - + # + # For details regarding assets in packages, see + # https://flutter.dev/to/asset-from-package + # # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/to/asset-from-package - - # To add custom fonts to your application, add a fonts section here, + # To add custom fonts to your plugin package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For @@ -86,5 +82,5 @@ flutter: # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/to/font-from-package + # For details regarding fonts in packages, see + # https://flutter.dev/to/font-from-package diff --git a/test/vpnclient_engine_flutter_method_channel_test.dart b/test/vpnclient_engine_flutter_method_channel_test.dart new file mode 100644 index 00000000..b565a11b --- /dev/null +++ b/test/vpnclient_engine_flutter_method_channel_test.dart @@ -0,0 +1,27 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter_method_channel.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + MethodChannelVpnclientEngineFlutter platform = MethodChannelVpnclientEngineFlutter(); + const MethodChannel channel = MethodChannel('vpnclient_engine_flutter'); + + setUp(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + channel, + (MethodCall methodCall) async { + return '42'; + }, + ); + }); + + tearDown(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null); + }); + + test('getPlatformVersion', () async { + expect(await platform.getPlatformVersion(), '42'); + }); +} diff --git a/test/vpnclient_engine_flutter_test.dart b/test/vpnclient_engine_flutter_test.dart new file mode 100644 index 00000000..f4b85307 --- /dev/null +++ b/test/vpnclient_engine_flutter_test.dart @@ -0,0 +1,29 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter.dart'; +import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter_platform_interface.dart'; +import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter_method_channel.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +class MockVpnclientEngineFlutterPlatform + with MockPlatformInterfaceMixin + implements VpnclientEngineFlutterPlatform { + + @override + Future getPlatformVersion() => Future.value('42'); +} + +void main() { + final VpnclientEngineFlutterPlatform initialPlatform = VpnclientEngineFlutterPlatform.instance; + + test('$MethodChannelVpnclientEngineFlutter is the default instance', () { + expect(initialPlatform, isInstanceOf()); + }); + + test('getPlatformVersion', () async { + VpnclientEngineFlutter vpnclientEngineFlutterPlugin = VpnclientEngineFlutter(); + MockVpnclientEngineFlutterPlatform fakePlatform = MockVpnclientEngineFlutterPlatform(); + VpnclientEngineFlutterPlatform.instance = fakePlatform; + + expect(await vpnclientEngineFlutterPlugin.getPlatformVersion(), '42'); + }); +} diff --git a/vpnclient_engine_flutter.iml b/vpnclient_engine_flutter.iml new file mode 100644 index 00000000..27686dd4 --- /dev/null +++ b/vpnclient_engine_flutter.iml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 00000000..b3eb2be1 --- /dev/null +++ b/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt new file mode 100644 index 00000000..537c0bec --- /dev/null +++ b/windows/CMakeLists.txt @@ -0,0 +1,100 @@ +# The Flutter tooling requires that developers have a version of Visual Studio +# installed that includes CMake 3.14 or later. You should not increase this +# version, as doing so will cause the plugin to fail to compile for some +# customers of the plugin. +cmake_minimum_required(VERSION 3.14) + +# Project-level configuration. +set(PROJECT_NAME "vpnclient_engine_flutter") +project(${PROJECT_NAME} LANGUAGES CXX) + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# This value is used when generating builds using this plugin, so it must +# not be changed +set(PLUGIN_NAME "vpnclient_engine_flutter_plugin") + +# Any new source files that you add to the plugin should be added here. +list(APPEND PLUGIN_SOURCES + "vpnclient_engine_flutter_plugin.cpp" + "vpnclient_engine_flutter_plugin.h" +) + +# Define the plugin library target. Its name must not be changed (see comment +# on PLUGIN_NAME above). +add_library(${PLUGIN_NAME} SHARED + "include/vpnclient_engine_flutter/vpnclient_engine_flutter_plugin_c_api.h" + "vpnclient_engine_flutter_plugin_c_api.cpp" + ${PLUGIN_SOURCES} +) + +# Apply a standard set of build settings that are configured in the +# application-level CMakeLists.txt. This can be removed for plugins that want +# full control over build settings. +apply_standard_settings(${PLUGIN_NAME}) + +# Symbols are hidden by default to reduce the chance of accidental conflicts +# between plugins. This should not be removed; any symbols that should be +# exported should be explicitly exported with the FLUTTER_PLUGIN_EXPORT macro. +set_target_properties(${PLUGIN_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) + +# Source include directories and library dependencies. Add any plugin-specific +# dependencies here. +target_include_directories(${PLUGIN_NAME} INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) + +# List of absolute paths to libraries that should be bundled with the plugin. +# This list could contain prebuilt libraries, or libraries created by an +# external build triggered from this build file. +set(vpnclient_engine_flutter_bundled_libraries + "" + PARENT_SCOPE +) + +# === Tests === +# These unit tests can be run from a terminal after building the example, or +# from Visual Studio after opening the generated solution file. + +# Only enable test builds when building the example (which sets this variable) +# so that plugin clients aren't building the tests. +if (${include_${PROJECT_NAME}_tests}) +set(TEST_RUNNER "${PROJECT_NAME}_test") +enable_testing() + +# Add the Google Test dependency. +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/release-1.11.0.zip +) +# Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +# Disable install commands for gtest so it doesn't end up in the bundle. +set(INSTALL_GTEST OFF CACHE BOOL "Disable installation of googletest" FORCE) +FetchContent_MakeAvailable(googletest) + +# The plugin's C API is not very useful for unit testing, so build the sources +# directly into the test binary rather than using the DLL. +add_executable(${TEST_RUNNER} + test/vpnclient_engine_flutter_plugin_test.cpp + ${PLUGIN_SOURCES} +) +apply_standard_settings(${TEST_RUNNER}) +target_include_directories(${TEST_RUNNER} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") +target_link_libraries(${TEST_RUNNER} PRIVATE flutter_wrapper_plugin) +target_link_libraries(${TEST_RUNNER} PRIVATE gtest_main gmock) +# flutter_wrapper_plugin has link dependencies on the Flutter DLL. +add_custom_command(TARGET ${TEST_RUNNER} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${FLUTTER_LIBRARY}" $ +) + +# Enable automatic test discovery. +include(GoogleTest) +gtest_discover_tests(${TEST_RUNNER}) +endif() diff --git a/windows/include/vpnclient_engine_flutter/vpnclient_engine_flutter_plugin_c_api.h b/windows/include/vpnclient_engine_flutter/vpnclient_engine_flutter_plugin_c_api.h new file mode 100644 index 00000000..6f2c51c8 --- /dev/null +++ b/windows/include/vpnclient_engine_flutter/vpnclient_engine_flutter_plugin_c_api.h @@ -0,0 +1,23 @@ +#ifndef FLUTTER_PLUGIN_VPNCLIENT_ENGINE_FLUTTER_PLUGIN_C_API_H_ +#define FLUTTER_PLUGIN_VPNCLIENT_ENGINE_FLUTTER_PLUGIN_C_API_H_ + +#include + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __declspec(dllexport) +#else +#define FLUTTER_PLUGIN_EXPORT __declspec(dllimport) +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +FLUTTER_PLUGIN_EXPORT void VpnclientEngineFlutterPluginCApiRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar); + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif // FLUTTER_PLUGIN_VPNCLIENT_ENGINE_FLUTTER_PLUGIN_C_API_H_ diff --git a/windows/test/vpnclient_engine_flutter_plugin_test.cpp b/windows/test/vpnclient_engine_flutter_plugin_test.cpp new file mode 100644 index 00000000..fcb1c412 --- /dev/null +++ b/windows/test/vpnclient_engine_flutter_plugin_test.cpp @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "vpnclient_engine_flutter_plugin.h" + +namespace vpnclient_engine_flutter { +namespace test { + +namespace { + +using flutter::EncodableMap; +using flutter::EncodableValue; +using flutter::MethodCall; +using flutter::MethodResultFunctions; + +} // namespace + +TEST(VpnclientEngineFlutterPlugin, GetPlatformVersion) { + VpnclientEngineFlutterPlugin plugin; + // Save the reply value from the success callback. + std::string result_string; + plugin.HandleMethodCall( + MethodCall("getPlatformVersion", std::make_unique()), + std::make_unique>( + [&result_string](const EncodableValue* result) { + result_string = std::get(*result); + }, + nullptr, nullptr)); + + // Since the exact string varies by host, just ensure that it's a string + // with the expected format. + EXPECT_TRUE(result_string.rfind("Windows ", 0) == 0); +} + +} // namespace test +} // namespace vpnclient_engine_flutter diff --git a/windows/vpnclient_engine_flutter_plugin.cpp b/windows/vpnclient_engine_flutter_plugin.cpp new file mode 100644 index 00000000..897d16a9 --- /dev/null +++ b/windows/vpnclient_engine_flutter_plugin.cpp @@ -0,0 +1,59 @@ +#include "vpnclient_engine_flutter_plugin.h" + +// This must be included before many other Windows headers. +#include + +// For getPlatformVersion; remove unless needed for your plugin implementation. +#include + +#include +#include +#include + +#include +#include + +namespace vpnclient_engine_flutter { + +// static +void VpnclientEngineFlutterPlugin::RegisterWithRegistrar( + flutter::PluginRegistrarWindows *registrar) { + auto channel = + std::make_unique>( + registrar->messenger(), "vpnclient_engine_flutter", + &flutter::StandardMethodCodec::GetInstance()); + + auto plugin = std::make_unique(); + + channel->SetMethodCallHandler( + [plugin_pointer = plugin.get()](const auto &call, auto result) { + plugin_pointer->HandleMethodCall(call, std::move(result)); + }); + + registrar->AddPlugin(std::move(plugin)); +} + +VpnclientEngineFlutterPlugin::VpnclientEngineFlutterPlugin() {} + +VpnclientEngineFlutterPlugin::~VpnclientEngineFlutterPlugin() {} + +void VpnclientEngineFlutterPlugin::HandleMethodCall( + const flutter::MethodCall &method_call, + std::unique_ptr> result) { + if (method_call.method_name().compare("getPlatformVersion") == 0) { + std::ostringstream version_stream; + version_stream << "Windows "; + if (IsWindows10OrGreater()) { + version_stream << "10+"; + } else if (IsWindows8OrGreater()) { + version_stream << "8"; + } else if (IsWindows7OrGreater()) { + version_stream << "7"; + } + result->Success(flutter::EncodableValue(version_stream.str())); + } else { + result->NotImplemented(); + } +} + +} // namespace vpnclient_engine_flutter diff --git a/windows/vpnclient_engine_flutter_plugin.h b/windows/vpnclient_engine_flutter_plugin.h new file mode 100644 index 00000000..7a0a9c3f --- /dev/null +++ b/windows/vpnclient_engine_flutter_plugin.h @@ -0,0 +1,31 @@ +#ifndef FLUTTER_PLUGIN_VPNCLIENT_ENGINE_FLUTTER_PLUGIN_H_ +#define FLUTTER_PLUGIN_VPNCLIENT_ENGINE_FLUTTER_PLUGIN_H_ + +#include +#include + +#include + +namespace vpnclient_engine_flutter { + +class VpnclientEngineFlutterPlugin : public flutter::Plugin { + public: + static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar); + + VpnclientEngineFlutterPlugin(); + + virtual ~VpnclientEngineFlutterPlugin(); + + // Disallow copy and assign. + VpnclientEngineFlutterPlugin(const VpnclientEngineFlutterPlugin&) = delete; + VpnclientEngineFlutterPlugin& operator=(const VpnclientEngineFlutterPlugin&) = delete; + + // Called when a method is called on this plugin's channel from Dart. + void HandleMethodCall( + const flutter::MethodCall &method_call, + std::unique_ptr> result); +}; + +} // namespace vpnclient_engine_flutter + +#endif // FLUTTER_PLUGIN_VPNCLIENT_ENGINE_FLUTTER_PLUGIN_H_ diff --git a/windows/vpnclient_engine_flutter_plugin_c_api.cpp b/windows/vpnclient_engine_flutter_plugin_c_api.cpp new file mode 100644 index 00000000..238c3c11 --- /dev/null +++ b/windows/vpnclient_engine_flutter_plugin_c_api.cpp @@ -0,0 +1,12 @@ +#include "include/vpnclient_engine_flutter/vpnclient_engine_flutter_plugin_c_api.h" + +#include + +#include "vpnclient_engine_flutter_plugin.h" + +void VpnclientEngineFlutterPluginCApiRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar) { + vpnclient_engine_flutter::VpnclientEngineFlutterPlugin::RegisterWithRegistrar( + flutter::PluginRegistrarManager::GetInstance() + ->GetRegistrar(registrar)); +} From 750f97b5b8355af9f064a003f3f8689d3754e5d0 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 27 Mar 2025 20:00:26 +0700 Subject: [PATCH 28/77] project -> plugin --- ios/vpnclient_engine_flutter.podspec | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ios/vpnclient_engine_flutter.podspec b/ios/vpnclient_engine_flutter.podspec index 3a82f206..a826e62c 100644 --- a/ios/vpnclient_engine_flutter.podspec +++ b/ios/vpnclient_engine_flutter.podspec @@ -5,16 +5,17 @@ Pod::Spec.new do |s| s.name = 'vpnclient_engine_flutter' s.version = '0.0.1' - s.summary = 'A new Flutter plugin project.' + s.summary = 'VPNclient Engine Flutter plugin project.' s.description = <<-DESC -A new Flutter plugin project. +VPNclient Engine Flutter plugin project. DESC - s.homepage = 'http://example.com' + s.homepage = 'http://vpnclient.click' s.license = { :file => '../LICENSE' } - s.author = { 'Your Company' => 'email@example.com' } + s.author = { 'Your Company' => 'admin@nativemind.net' } s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' + s.dependency 'VPNclientEngineIOS', :path => '../VPNcleint-engine-ios' s.platform = :ios, '12.0' # Flutter.framework does not contain a i386 slice. From 6ab20a40a32440d584f31743e07cdb0e9043401a Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 27 Mar 2025 20:18:01 +0700 Subject: [PATCH 29/77] pods --- ios/Classes/VpnclientEngineFlutterPlugin.swift | 1 + ios/Podfile | 4 ++++ lib/vpnclient_engine_flutter.dart | 2 ++ macos/Classes/VpnclientEngineFlutterPlugin.swift | 1 + macos/Podfile | 4 ++++ macos/vpnclient_engine_flutter.podspec | 11 ++++++----- pods.sh | 2 ++ pubs.sh | 2 ++ 8 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 ios/Podfile create mode 100644 macos/Podfile create mode 100755 pods.sh create mode 100755 pubs.sh diff --git a/ios/Classes/VpnclientEngineFlutterPlugin.swift b/ios/Classes/VpnclientEngineFlutterPlugin.swift index 7763e479..8e0d9f2b 100644 --- a/ios/Classes/VpnclientEngineFlutterPlugin.swift +++ b/ios/Classes/VpnclientEngineFlutterPlugin.swift @@ -1,5 +1,6 @@ import Flutter import UIKit +import VPNclientEngineIOS public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 00000000..c7aa983d --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,4 @@ +target 'VPNclientEngineFlutter' do + use_frameworks! + pod 'VPNclientEngineIOS', :path => '../VPNcleint-engine-ios' +end diff --git a/lib/vpnclient_engine_flutter.dart b/lib/vpnclient_engine_flutter.dart index 81ce3750..27b92f4f 100644 --- a/lib/vpnclient_engine_flutter.dart +++ b/lib/vpnclient_engine_flutter.dart @@ -1,6 +1,8 @@ import 'vpnclient_engine_flutter_platform_interface.dart'; +export 'vpnclient_engine/engine.dart'; + class VpnclientEngineFlutter { Future getPlatformVersion() { return VpnclientEngineFlutterPlatform.instance.getPlatformVersion(); diff --git a/macos/Classes/VpnclientEngineFlutterPlugin.swift b/macos/Classes/VpnclientEngineFlutterPlugin.swift index cd608ecc..e16e1424 100644 --- a/macos/Classes/VpnclientEngineFlutterPlugin.swift +++ b/macos/Classes/VpnclientEngineFlutterPlugin.swift @@ -1,5 +1,6 @@ import Cocoa import FlutterMacOS +import VPNclientEngineIOS public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { diff --git a/macos/Podfile b/macos/Podfile new file mode 100644 index 00000000..c7aa983d --- /dev/null +++ b/macos/Podfile @@ -0,0 +1,4 @@ +target 'VPNclientEngineFlutter' do + use_frameworks! + pod 'VPNclientEngineIOS', :path => '../VPNcleint-engine-ios' +end diff --git a/macos/vpnclient_engine_flutter.podspec b/macos/vpnclient_engine_flutter.podspec index a034289d..1b9ff50f 100644 --- a/macos/vpnclient_engine_flutter.podspec +++ b/macos/vpnclient_engine_flutter.podspec @@ -5,13 +5,13 @@ Pod::Spec.new do |s| s.name = 'vpnclient_engine_flutter' s.version = '0.0.1' - s.summary = 'A new Flutter plugin project.' + s.summary = 'VPNclient Engine Flutter plugin project.' s.description = <<-DESC -A new Flutter plugin project. +VPNclient Engine Flutter plugin project. DESC - s.homepage = 'http://example.com' + s.homepage = 'http://vpnclient.click' s.license = { :file => '../LICENSE' } - s.author = { 'Your Company' => 'email@example.com' } + s.author = { 'Your Company' => 'admin@nativemind.net' } s.source = { :path => '.' } s.source_files = 'Classes/**/*' @@ -23,7 +23,8 @@ A new Flutter plugin project. # s.resource_bundles = {'vpnclient_engine_flutter_privacy' => ['Resources/PrivacyInfo.xcprivacy']} s.dependency 'FlutterMacOS' - + s.dependency 'VPNclientEngineIOS', :path => '../VPNcleint-engine-ios' + s.platform = :osx, '10.11' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.0' diff --git a/pods.sh b/pods.sh new file mode 100755 index 00000000..3c1d1f8e --- /dev/null +++ b/pods.sh @@ -0,0 +1,2 @@ +cd ios +pod install \ No newline at end of file diff --git a/pubs.sh b/pubs.sh new file mode 100755 index 00000000..67e11054 --- /dev/null +++ b/pubs.sh @@ -0,0 +1,2 @@ +flutter clean +flutter pub get From 3f72da615155575ac3f0c4886df67868b698d872 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 27 Mar 2025 20:24:42 +0700 Subject: [PATCH 30/77] pods --- ios/Podfile | 7 ++++++- ios/vpnclient_engine_flutter.podspec | 2 +- macos/Podfile | 4 +++- macos/vpnclient_engine_flutter.podspec | 2 +- pods.sh | 6 +++++- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/ios/Podfile b/ios/Podfile index c7aa983d..4441b764 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,4 +1,9 @@ +project '../../VPNclient-engine-ios/cntrlr.xcodeproj' +platform :ios, '12.0' +use_frameworks! + + target 'VPNclientEngineFlutter' do use_frameworks! - pod 'VPNclientEngineIOS', :path => '../VPNcleint-engine-ios' + pod 'VPNclientEngineIOS', :path => '../../VPNclient-engine-ios' end diff --git a/ios/vpnclient_engine_flutter.podspec b/ios/vpnclient_engine_flutter.podspec index a826e62c..847cb866 100644 --- a/ios/vpnclient_engine_flutter.podspec +++ b/ios/vpnclient_engine_flutter.podspec @@ -15,7 +15,7 @@ VPNclient Engine Flutter plugin project. s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.dependency 'VPNclientEngineIOS', :path => '../VPNcleint-engine-ios' + s.dependency 'VPNclientEngineIOS', :path => '../VPNclient-engine-ios' s.platform = :ios, '12.0' # Flutter.framework does not contain a i386 slice. diff --git a/macos/Podfile b/macos/Podfile index c7aa983d..bd54dfaf 100644 --- a/macos/Podfile +++ b/macos/Podfile @@ -1,4 +1,6 @@ +project '../../VPNclient-engine-ios/cntrlr.xcodeproj' + target 'VPNclientEngineFlutter' do use_frameworks! - pod 'VPNclientEngineIOS', :path => '../VPNcleint-engine-ios' + pod 'VPNclientEngineIOS', :path => '../../VPNclient-engine-ios' end diff --git a/macos/vpnclient_engine_flutter.podspec b/macos/vpnclient_engine_flutter.podspec index 1b9ff50f..b5007c5b 100644 --- a/macos/vpnclient_engine_flutter.podspec +++ b/macos/vpnclient_engine_flutter.podspec @@ -23,7 +23,7 @@ VPNclient Engine Flutter plugin project. # s.resource_bundles = {'vpnclient_engine_flutter_privacy' => ['Resources/PrivacyInfo.xcprivacy']} s.dependency 'FlutterMacOS' - s.dependency 'VPNclientEngineIOS', :path => '../VPNcleint-engine-ios' + s.dependency 'VPNclientEngineIOS', :path => '../VPNclient-engine-ios' s.platform = :osx, '10.11' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } diff --git a/pods.sh b/pods.sh index 3c1d1f8e..66ae2812 100755 --- a/pods.sh +++ b/pods.sh @@ -1,2 +1,6 @@ +cd macos +pod install +cd .. cd ios -pod install \ No newline at end of file +pod install +cd .. From 4d188b5edecbbbce31843b44ee39e6990f4c3fca Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 27 Mar 2025 20:38:37 +0700 Subject: [PATCH 31/77] pods --- macos/Podfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macos/Podfile b/macos/Podfile index bd54dfaf..b2beab69 100644 --- a/macos/Podfile +++ b/macos/Podfile @@ -1,6 +1,6 @@ project '../../VPNclient-engine-ios/cntrlr.xcodeproj' -target 'VPNclientEngineFlutter' do +target 'Runner' do use_frameworks! pod 'VPNclientEngineIOS', :path => '../../VPNclient-engine-ios' end From 08f999c47953d4b0081ff17506331958379e9cdc Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Fri, 28 Mar 2025 12:47:24 +0700 Subject: [PATCH 32/77] actions --- .github/workflows/flutter_integration.yml | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/flutter_integration.yml diff --git a/.github/workflows/flutter_integration.yml b/.github/workflows/flutter_integration.yml new file mode 100644 index 00000000..40f15446 --- /dev/null +++ b/.github/workflows/flutter_integration.yml @@ -0,0 +1,28 @@ +name: Flutter Integration Test + +on: + pull_request: + branches: [ main ] + push: + branches: [ main ] + +jobs: + integration: + runs-on: macos-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: 'stable' + + - name: Run Flutter doctor + run: flutter doctor -v + + - name: Test Flutter integration + run: | + cd example + flutter pub get + flutter test integration_test From 0488b84c92ddb1177e42ffa943193b463fd4bca2 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Fri, 28 Mar 2025 18:32:01 +0700 Subject: [PATCH 33/77] android --- android/build.gradle | 2 ++ android/settings.gradle | 3 +++ lib/platforms/android.dart | 0 lib/platforms/ios.dart | 0 lib/vpnclient_engine.dart | 3 --- lib/vpnclient_engine_flutter.dart | 1 + ...ent_engine_flutter_platform_interface.dart | 21 +++++++++++++++++++ pubspec.yaml | 16 ++++++++++++++ 8 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 lib/platforms/android.dart create mode 100644 lib/platforms/ios.dart delete mode 100644 lib/vpnclient_engine.dart diff --git a/android/build.gradle b/android/build.gradle index 0c048261..c6c19205 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -11,6 +11,7 @@ buildscript { dependencies { classpath("com.android.tools.build:gradle:8.7.0") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") + } } @@ -50,6 +51,7 @@ android { dependencies { testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.mockito:mockito-core:5.0.0") + implementation 'com.github.VPNclient:VPNclient-engine-android:main-SNAPSHOT' } testOptions { diff --git a/android/settings.gradle b/android/settings.gradle index 1428199c..2a239e2b 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1 +1,4 @@ rootProject.name = 'vpnclient_engine_flutter' + +include ':vpnclient_engine_android' +project(':vpnclient_engine_android').projectDir = new File('../vpnclient_engine_android') \ No newline at end of file diff --git a/lib/platforms/android.dart b/lib/platforms/android.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/platforms/ios.dart b/lib/platforms/ios.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/vpnclient_engine.dart b/lib/vpnclient_engine.dart deleted file mode 100644 index f197d7c6..00000000 --- a/lib/vpnclient_engine.dart +++ /dev/null @@ -1,3 +0,0 @@ -export 'vpnclient_engine/engine.dart'; -export 'vpnclient_engine/ping.dart'; -export 'vpnclient_engine/subscriptions.dart'; \ No newline at end of file diff --git a/lib/vpnclient_engine_flutter.dart b/lib/vpnclient_engine_flutter.dart index 27b92f4f..f060813f 100644 --- a/lib/vpnclient_engine_flutter.dart +++ b/lib/vpnclient_engine_flutter.dart @@ -4,6 +4,7 @@ import 'vpnclient_engine_flutter_platform_interface.dart'; export 'vpnclient_engine/engine.dart'; class VpnclientEngineFlutter { + Future getPlatformVersion() { return VpnclientEngineFlutterPlatform.instance.getPlatformVersion(); } diff --git a/lib/vpnclient_engine_flutter_platform_interface.dart b/lib/vpnclient_engine_flutter_platform_interface.dart index f8a6549a..7626419d 100644 --- a/lib/vpnclient_engine_flutter_platform_interface.dart +++ b/lib/vpnclient_engine_flutter_platform_interface.dart @@ -26,4 +26,25 @@ abstract class VpnclientEngineFlutterPlatform extends PlatformInterface { Future getPlatformVersion() { throw UnimplementedError('platformVersion() has not been implemented.'); } + +/* + static dynamic _getPlatformImpl() { + if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) { + return AndroidNativeImpl(); + } else { + return DefaultImpl(); + } + } + + // В отдельном файле platforms/android.dart + class AndroidNativeImpl { + // Реализация для Android + } + + // В отдельном файле platforms/default.dart + class DefaultImpl { + // Заглушка для других платформ + } +*/ + } diff --git a/pubspec.yaml b/pubspec.yaml index 66529bde..1a59d2d1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,6 +16,22 @@ dependencies: plugin_platform_interface: ^2.0.2 cupertino_icons: ^1.0.8 dart_ping: ^9.0.1 + +platforms: + android: + dependencies: + vpnclient_engine_android: + git: + url: https://github.com/VPNclient/VPNclient-engine-android.git + ref: main + #path: path/to/module + ios: + dependencies: + vpnclient_engine_ios: + git: + url: https://github.com/VPNclient/VPNclient-engine-ios.git + ref: main + #path: path/to/module dev_dependencies: flutter_test: From 0499305e6e3ccb194b6764b4083359d8d1d2ec69 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Fri, 28 Mar 2025 18:36:13 +0700 Subject: [PATCH 34/77] android --- pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 1a59d2d1..c111e33a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,14 +24,14 @@ platforms: git: url: https://github.com/VPNclient/VPNclient-engine-android.git ref: main - #path: path/to/module + path: . ios: dependencies: vpnclient_engine_ios: git: url: https://github.com/VPNclient/VPNclient-engine-ios.git ref: main - #path: path/to/module + path: . dev_dependencies: flutter_test: From b7595c87e11dae579f26c00c2b1491371b0203ce Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Fri, 28 Mar 2025 18:41:51 +0700 Subject: [PATCH 35/77] android --- android/build.gradle | 2 +- android/settings.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index c6c19205..6ae9de25 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -51,7 +51,7 @@ android { dependencies { testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.mockito:mockito-core:5.0.0") - implementation 'com.github.VPNclient:VPNclient-engine-android:main-SNAPSHOT' + implementation project(':vpnclient-engine-android') } testOptions { diff --git a/android/settings.gradle b/android/settings.gradle index 2a239e2b..7adea516 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,4 +1,4 @@ rootProject.name = 'vpnclient_engine_flutter' include ':vpnclient_engine_android' -project(':vpnclient_engine_android').projectDir = new File('../vpnclient_engine_android') \ No newline at end of file +project(':vpnclient_engine_android').projectDir = new File('../VPNclient_engine_android') \ No newline at end of file From 5c87c1f12f427cea2a18e440a39bdf6c9bd11748 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Fri, 28 Mar 2025 18:45:27 +0700 Subject: [PATCH 36/77] android --- android/settings.gradle | 2 +- example/ios/Flutter/Debug.xcconfig | 1 + example/ios/Flutter/Release.xcconfig | 1 + example/macos/Flutter/Flutter-Debug.xcconfig | 1 + example/macos/Flutter/Flutter-Release.xcconfig | 1 + example/pubspec.lock | 8 ++++++++ pubspec.lock | 16 ++++++++++++++++ 7 files changed, 29 insertions(+), 1 deletion(-) diff --git a/android/settings.gradle b/android/settings.gradle index 7adea516..d8385ac9 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,4 +1,4 @@ rootProject.name = 'vpnclient_engine_flutter' include ':vpnclient_engine_android' -project(':vpnclient_engine_android').projectDir = new File('../VPNclient_engine_android') \ No newline at end of file +project(':vpnclient_engine_android').projectDir = new File('../../VPNclient_engine_android') \ No newline at end of file diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig index 592ceee8..ec97fc6f 100644 --- a/example/ios/Flutter/Debug.xcconfig +++ b/example/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig index 592ceee8..c4855bfe 100644 --- a/example/ios/Flutter/Release.xcconfig +++ b/example/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/example/macos/Flutter/Flutter-Debug.xcconfig b/example/macos/Flutter/Flutter-Debug.xcconfig index c2efd0b6..4b81f9b2 100644 --- a/example/macos/Flutter/Flutter-Debug.xcconfig +++ b/example/macos/Flutter/Flutter-Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/example/macos/Flutter/Flutter-Release.xcconfig b/example/macos/Flutter/Flutter-Release.xcconfig index c2efd0b6..5caa9d15 100644 --- a/example/macos/Flutter/Flutter-Release.xcconfig +++ b/example/macos/Flutter/Flutter-Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/example/pubspec.lock b/example/pubspec.lock index 785375a9..14089dea 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -49,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dart_ping: + dependency: transitive + description: + name: dart_ping + sha256: "2f5418d0a5c64e53486caaac78677b25725b1e13c33c5be834ce874ea18bd24f" + url: "https://pub.dev" + source: hosted + version: "9.0.1" fake_async: dependency: transitive description: diff --git a/pubspec.lock b/pubspec.lock index 063b37b2..798edf57 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -41,6 +41,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + dart_ping: + dependency: "direct main" + description: + name: dart_ping + sha256: "2f5418d0a5c64e53486caaac78677b25725b1e13c33c5be834ce874ea18bd24f" + url: "https://pub.dev" + source: hosted + version: "9.0.1" fake_async: dependency: transitive description: From aa31bea52dc92091018c9147c2c04987a29603ac Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Sat, 29 Mar 2025 18:59:42 +0700 Subject: [PATCH 37/77] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index acbfa273..ac91762d 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ VPN Client Engine Flutter is a Flutter wrapper for managing VPN connections with ### ✅ Supported Platforms - iOS 15+ (iPhone, iPad, MacOS M) - Android -- 🏗️ MacOS Intel (coming soon) -- 🏗️ Windows (coming soon) -- 🏗️ Ubuntu (coming soon) +- 🏗️ MacOS Intel +- 🏗️ Windows +- 🏗️ Ubuntu ## 📥 Getting Started From 3992b9738b5b5697d617fdca77cfd067dae3ae7e Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Mon, 31 Mar 2025 18:52:17 +0700 Subject: [PATCH 38/77] Update README.md --- README.md | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index ac91762d..b760ec6a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## 🌍 Overview -VPN Client Engine Flutter is a Flutter wrapper for managing VPN connections with an intuitive API. It provides seamless integration with various platforms, allowing developers to control VPN connections efficiently. +VPN Client Engine Flutter is a Flutter Plugin for managing VPN connections with an intuitive API. It provides seamless integration with various platforms, allowing developers to control VPN connections efficiently. ![VPN Client Engine](https://raw.githubusercontent.com/VPNclient/.github/refs/heads/main/assets/vpnclient_scheme2.png) ### ✅ Supported Platforms @@ -180,23 +180,15 @@ Enum: `invalidCredentials`, `serverUnavailable`, `subscriptionExpired`, `unknown --- -## Flutter - -[plug-in package](https://flutter.dev/to/develop-plugins), -a specialized package that includes platform-specific implementation code for -Android and/or iOS. - -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev), which offers tutorials, -samples, guidance on mobile development, and a full API reference. - - ## 🤝 Contributing - -Contributions are welcome! Feel free to submit issues and pull requests. +We welcome contributions! Please fork the repository and submit pull requests. ## 📜 License -This project is licensed under ... +This project is licensed under the **VPNclient Extended GNU General Public License v3 (GPL v3)**. See [LICENSE.md](LICENSE.md) for details. + +⚠️ **Note:** By using this software, you agree to comply with additional conditions outlined in the [VPNсlient Extended GNU General Public License v3 (GPL v3)](LICENSE.md) +## 💬 Support +For issues or questions, please open an issue on our GitHub repository. From 6975a2aada3eeccd84978868df7bd4fa4f22d321 Mon Sep 17 00:00:00 2001 From: ginterloper Date: Mon, 31 Mar 2025 23:36:05 +0300 Subject: [PATCH 39/77] errors fix --- example/ios/Podfile | 43 +++++++++++++++++++ .../flutter/generated_plugin_registrant.cc | 15 +++++++ .../flutter/generated_plugin_registrant.h | 15 +++++++ example/linux/flutter/generated_plugins.cmake | 24 +++++++++++ .../Flutter/GeneratedPluginRegistrant.swift | 12 ++++++ example/macos/Podfile | 42 ++++++++++++++++++ example/pubspec.lock | 24 +++++++++++ .../flutter/generated_plugin_registrant.cc | 14 ++++++ .../flutter/generated_plugin_registrant.h | 15 +++++++ .../windows/flutter/generated_plugins.cmake | 24 +++++++++++ lib/main.dart | 2 +- pubspec.lock | 24 +++++++++++ pubspec.yaml | 1 + 13 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 example/ios/Podfile create mode 100644 example/linux/flutter/generated_plugin_registrant.cc create mode 100644 example/linux/flutter/generated_plugin_registrant.h create mode 100644 example/linux/flutter/generated_plugins.cmake create mode 100644 example/macos/Flutter/GeneratedPluginRegistrant.swift create mode 100644 example/macos/Podfile create mode 100644 example/windows/flutter/generated_plugin_registrant.cc create mode 100644 example/windows/flutter/generated_plugin_registrant.h create mode 100644 example/windows/flutter/generated_plugins.cmake diff --git a/example/ios/Podfile b/example/ios/Podfile new file mode 100644 index 00000000..e549ee22 --- /dev/null +++ b/example/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/example/linux/flutter/generated_plugin_registrant.cc b/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..5999de01 --- /dev/null +++ b/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) vpnclient_engine_flutter_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "VpnclientEngineFlutterPlugin"); + vpnclient_engine_flutter_plugin_register_with_registrar(vpnclient_engine_flutter_registrar); +} diff --git a/example/linux/flutter/generated_plugin_registrant.h b/example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..e0f0a47b --- /dev/null +++ b/example/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/example/linux/flutter/generated_plugins.cmake b/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 00000000..8e500216 --- /dev/null +++ b/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + vpnclient_engine_flutter +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 00000000..8366bf1b --- /dev/null +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import vpnclient_engine_flutter + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + VpnclientEngineFlutterPlugin.register(with: registry.registrar(forPlugin: "VpnclientEngineFlutterPlugin")) +} diff --git a/example/macos/Podfile b/example/macos/Podfile new file mode 100644 index 00000000..29c8eb32 --- /dev/null +++ b/example/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/example/pubspec.lock b/example/pubspec.lock index 14089dea..10ad335c 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -106,6 +106,22 @@ packages: description: flutter source: sdk version: "0.0.0" + http: + dependency: transitive + description: + name: http + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + url: "https://pub.dev" + source: hosted + version: "1.3.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" integration_test: dependency: "direct dev" description: flutter @@ -260,6 +276,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.4" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" vector_math: dependency: transitive description: diff --git a/example/windows/flutter/generated_plugin_registrant.cc b/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..4bf49e71 --- /dev/null +++ b/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + VpnclientEngineFlutterPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("VpnclientEngineFlutterPluginCApi")); +} diff --git a/example/windows/flutter/generated_plugin_registrant.h b/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..dc139d85 --- /dev/null +++ b/example/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/example/windows/flutter/generated_plugins.cmake b/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 00000000..f9fea320 --- /dev/null +++ b/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + vpnclient_engine_flutter +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/lib/main.dart b/lib/main.dart index 07640d22..b40c3ebb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,5 @@ import 'dart:async'; -import 'vpnclient_engine.dart'; +import 'vpnclient_engine/engine.dart'; diff --git a/pubspec.lock b/pubspec.lock index 798edf57..ef1ef3ad 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -88,6 +88,22 @@ packages: description: flutter source: sdk version: "0.0.0" + http: + dependency: "direct main" + description: + name: http + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + url: "https://pub.dev" + source: hosted + version: "1.3.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" leak_tracker: dependency: transitive description: @@ -213,6 +229,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.4" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c111e33a..e85e5203 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: plugin_platform_interface: ^2.0.2 cupertino_icons: ^1.0.8 dart_ping: ^9.0.1 + http: ^1.3.0 platforms: android: From 1a2051bc312afe257cf571b61e06c1607c141cc9 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Tue, 1 Apr 2025 10:22:33 +0700 Subject: [PATCH 40/77] os --- pubspec.yaml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index c111e33a..567b35c9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,9 +22,9 @@ platforms: dependencies: vpnclient_engine_android: git: - url: https://github.com/VPNclient/VPNclient-engine-android.git + url: https://github.com/VPNclient/VPNclient-engine.git ref: main - path: . + path: android/ ios: dependencies: vpnclient_engine_ios: @@ -32,6 +32,13 @@ platforms: url: https://github.com/VPNclient/VPNclient-engine-ios.git ref: main path: . + windows: + dependencies: + vpnclient_engine_windows: + git: + url: https://github.com/VPNclient/VPNclient-engine.git + ref: main + path: windows/ dev_dependencies: flutter_test: From 7b2824cb1fca403864a1b5ebf54e6072d5c946aa Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Tue, 1 Apr 2025 10:25:50 +0700 Subject: [PATCH 41/77] LICENSE --- LEGAL_ru.md | 12 + LICENSE.md | 629 ++++++++++++++++++++++++++++++++++++++++++++++++++ LICENSE_ru.md | 69 ++++++ LICENSE_th.md | 70 ++++++ 4 files changed, 780 insertions(+) create mode 100644 LEGAL_ru.md create mode 100644 LICENSE.md create mode 100644 LICENSE_ru.md create mode 100644 LICENSE_th.md diff --git a/LEGAL_ru.md b/LEGAL_ru.md new file mode 100644 index 00000000..8c0038d1 --- /dev/null +++ b/LEGAL_ru.md @@ -0,0 +1,12 @@ +# Юридическое уведомление + +Программное обеспечение VPNclient представляется исключительно в целях улучшения пользовательского опыта при работе с зарубежными интернет-магистралями и обеспечения стабильного доступа к международным ресурсам а так же доступ к локальным ресурсам из зарубежа. + +**Запрещается использование VPNclient в следующих целях:** +- Обход установленных законодательством Российской Федерации блокировок сайтов, ресурсов или сервисов. +- Получение доступа к ресурсам, запрещенным или ограниченным в соответствии с законодательством Российской Федерации. +- Осуществление иной противоправной деятельности, предусмотренной законодательством Российской Федерации и международным правом. + +Пользователи и компании, использующие программное обеспечение VPNclient, несут личную и корпоративную ответственность за любое неправомерное использование данного программного продукта. Разработчики програмного продукта не несут ответственности за действия пользователей и компаний, совершённые с использованием продукта, которые нарушают законодательство Российской Федерации и других стран. + +Используя программное обеспечение VPNclient, пользователь подтверждает, что полностью ознакомлен с данным юридическим уведомлением и обязуется использовать продукт исключительно в рамках действующего законодательства. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..fc41f00f --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,629 @@ +# VPNсlient Extended GNU General Public License v3 (GPL v3) + +The VPNсlient project is licensed under GPL v3, with the following additional conditions in accordance with GPL v3 Section 7. Failure to comply with these terms may result in requests to app stores to remove or restrict access to your app. + +## Additional Conditions to GPL v3: + +1. **Copyright Notice: VPNclient License** +Copyright (c) 2010-2025 NativeMind Inc. All rights reserved. +"VPNclient" is a trademark of NativeMind Inc. + +2. **Open Source Software Bundling Notice** +The VPNclient is bundled with other open source software components, some of which fall under different licenses. By using VPNclient or any of the bundled components, you agree to be bound by the conditions of the license for each respective component. A copy of the LICENSE is also distributed in the file LICENSE.md. + +3. **Source Code Availability** +If you use any part of this code, you must publish your source code on GitHub as a fork of the VPNclient repository and keep it up-to-date with any published app releases. Your repository should visibly indicate that it is a fork of [https://github.com/VPNclient](https://github.com/VPNclient). + +4. **Automated Release** +All releases must be made using GitHub Actions. + +5. **Attribution** +You must give appropriate credit to VPNclient , link to [https://github.com/VPNclient](https://github.com/VPNclient), link to the original license, and document any changes you have made in your repository’s README. + +6. **No Malware** +Adding any malware or malicious code to the app is strictly prohibited. + +7. **Naming and Interface Restrictions** +You are not permitted to publish the app on any app store (e.g., AppStore, Google Play, F-Droid, Microsoft) with a name or user interface that closely resembles VPNclient (e.g., names like VPNclient or similar UI are prohibited). + +8. **NonCommercial Use Only** +You may not use this material for commercial purposes, including selling and advertising, without prior written consent. + +9. **ShareAlike Requirement** +If you remix, transform, or build upon the material, you must distribute your contributions under this same license as an open-source fork of [https://github.com/VPNclient](https://github.com/VPNclient). + +GNU General Public License +========================== + +_Version 3, 29 June 2007_ +_Copyright © 2007 Free Software Foundation, Inc. <>_ + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +## Preamble + +The GNU General Public License is a free, copyleft license for software and other +kinds of works. + +The licenses for most software and other practical works are designed to take away +your freedom to share and change the works. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change all versions of a +program--to make sure it remains free software for all its users. We, the Free +Software Foundation, use the GNU General Public License for most of our software; it +applies also to any other work released this way by its authors. You can apply it to +your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General +Public Licenses are designed to make sure that you have the freedom to distribute +copies of free software (and charge for them if you wish), that you receive source +code or can get it if you want it, that you can change the software or use pieces of +it in new free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you these rights or +asking you to surrender the rights. Therefore, you have certain responsibilities if +you distribute copies of the software, or if you modify it: responsibilities to +respect the freedom of others. + +For example, if you distribute copies of such a program, whether gratis or for a fee, +you must pass on to the recipients the same freedoms that you received. You must make +sure that they, too, receive or can get the source code. And you must show them these +terms so they know their rights. + +Developers that use the GNU GPL protect your rights with two steps: **(1)** assert +copyright on the software, and **(2)** offer you this License giving you legal permission +to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains that there is +no warranty for this free software. For both users' and authors' sake, the GPL +requires that modified versions be marked as changed, so that their problems will not +be attributed erroneously to authors of previous versions. + +Some devices are designed to deny users access to install or run modified versions of +the software inside them, although the manufacturer can do so. This is fundamentally +incompatible with the aim of protecting users' freedom to change the software. The +systematic pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we have designed +this version of the GPL to prohibit the practice for those products. If such problems +arise substantially in other domains, we stand ready to extend this provision to +those domains in future versions of the GPL, as needed to protect the freedom of +users. + +Finally, every program is threatened constantly by software patents. States should +not allow patents to restrict development and use of software on general-purpose +computers, but in those that do, we wish to avoid the special danger that patents +applied to a free program could make it effectively proprietary. To prevent this, the +GPL assures that patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and modification follow. + +## TERMS AND CONDITIONS + +### 0. Definitions + +“This License” refers to version 3 of the GNU General Public License. + +“Copyright” also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + +“The Program” refers to any copyrightable work licensed under this +License. Each licensee is addressed as “you”. “Licensees” and +“recipients” may be individuals or organizations. + +To “modify” a work means to copy from or adapt all or part of the work in +a fashion requiring copyright permission, other than the making of an exact copy. The +resulting work is called a “modified version” of the earlier work or a +work “based on” the earlier work. + +A “covered work” means either the unmodified Program or a work based on +the Program. + +To “propagate” a work means to do anything with it that, without +permission, would make you directly or secondarily liable for infringement under +applicable copyright law, except executing it on a computer or modifying a private +copy. Propagation includes copying, distribution (with or without modification), +making available to the public, and in some countries other activities as well. + +To “convey” a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through a computer +network, with no transfer of a copy, is not conveying. + +An interactive user interface displays “Appropriate Legal Notices” to the +extent that it includes a convenient and prominently visible feature that **(1)** +displays an appropriate copyright notice, and **(2)** tells the user that there is no +warranty for the work (except to the extent that warranties are provided), that +licensees may convey the work under this License, and how to view a copy of this +License. If the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +### 1. Source Code + +The “source code” for a work means the preferred form of the work for +making modifications to it. “Object code” means any non-source form of a +work. + +A “Standard Interface” means an interface that either is an official +standard defined by a recognized standards body, or, in the case of interfaces +specified for a particular programming language, one that is widely used among +developers working in that language. + +The “System Libraries” of an executable work include anything, other than +the work as a whole, that **(a)** is included in the normal form of packaging a Major +Component, but which is not part of that Major Component, and **(b)** serves only to +enable use of the work with that Major Component, or to implement a Standard +Interface for which an implementation is available to the public in source code form. +A “Major Component”, in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system (if any) on which +the executable work runs, or a compiler used to produce the work, or an object code +interpreter used to run it. + +The “Corresponding Source” for a work in object code form means all the +source code needed to generate, install, and (for an executable work) run the object +code and to modify the work, including scripts to control those activities. However, +it does not include the work's System Libraries, or general-purpose tools or +generally available free programs which are used unmodified in performing those +activities but which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for the work, and +the source code for shared libraries and dynamically linked subprograms that the work +is specifically designed to require, such as by intimate data communication or +control flow between those subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can regenerate +automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. + +### 2. Basic Permissions + +All rights granted under this License are granted for the term of copyright on the +Program, and are irrevocable provided the stated conditions are met. This License +explicitly affirms your unlimited permission to run the unmodified Program. The +output from running a covered work is covered by this License only if the output, +given its content, constitutes a covered work. This License acknowledges your rights +of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, without +conditions so long as your license otherwise remains in force. You may convey covered +works to others for the sole purpose of having them make modifications exclusively +for you, or provide you with facilities for running those works, provided that you +comply with the terms of this License in conveying all material for which you do not +control copyright. Those thus making or running the covered works for you must do so +exclusively on your behalf, under your direction and control, on terms that prohibit +them from making any copies of your copyrighted material outside their relationship +with you. + +Conveying under any other circumstances is permitted solely under the conditions +stated below. Sublicensing is not allowed; section 10 makes it unnecessary. + +### 3. Protecting Users' Legal Rights From Anti-Circumvention Law + +No covered work shall be deemed part of an effective technological measure under any +applicable law fulfilling obligations under article 11 of the WIPO copyright treaty +adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention +of such measures. + +When you convey a covered work, you waive any legal power to forbid circumvention of +technological measures to the extent such circumvention is effected by exercising +rights under this License with respect to the covered work, and you disclaim any +intention to limit operation or modification of the work as a means of enforcing, +against the work's users, your or third parties' legal rights to forbid circumvention +of technological measures. + +### 4. Conveying Verbatim Copies + +You may convey verbatim copies of the Program's source code as you receive it, in any +medium, provided that you conspicuously and appropriately publish on each copy an +appropriate copyright notice; keep intact all notices stating that this License and +any non-permissive terms added in accord with section 7 apply to the code; keep +intact all notices of the absence of any warranty; and give all recipients a copy of +this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may offer +support or warranty protection for a fee. + +### 5. Conveying Modified Source Versions + +You may convey a work based on the Program, or the modifications to produce it from +the Program, in the form of source code under the terms of section 4, provided that +you also meet all of these conditions: + +* **a)** The work must carry prominent notices stating that you modified it, and giving a +relevant date. +* **b)** The work must carry prominent notices stating that it is released under this +License and any conditions added under section 7. This requirement modifies the +requirement in section 4 to “keep intact all notices”. +* **c)** You must license the entire work, as a whole, under this License to anyone who +comes into possession of a copy. This License will therefore apply, along with any +applicable section 7 additional terms, to the whole of the work, and all its parts, +regardless of how they are packaged. This License gives no permission to license the +work in any other way, but it does not invalidate such permission if you have +separately received it. +* **d)** If the work has interactive user interfaces, each must display Appropriate Legal +Notices; however, if the Program has interactive interfaces that do not display +Appropriate Legal Notices, your work need not make them do so. + +A compilation of a covered work with other separate and independent works, which are +not by their nature extensions of the covered work, and which are not combined with +it such as to form a larger program, in or on a volume of a storage or distribution +medium, is called an “aggregate” if the compilation and its resulting +copyright are not used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work in an aggregate +does not cause this License to apply to the other parts of the aggregate. + +### 6. Conveying Non-Source Forms + +You may convey a covered work in object code form under the terms of sections 4 and +5, provided that you also convey the machine-readable Corresponding Source under the +terms of this License, in one of these ways: + +* **a)** Convey the object code in, or embodied in, a physical product (including a +physical distribution medium), accompanied by the Corresponding Source fixed on a +durable physical medium customarily used for software interchange. +* **b)** Convey the object code in, or embodied in, a physical product (including a +physical distribution medium), accompanied by a written offer, valid for at least +three years and valid for as long as you offer spare parts or customer support for +that product model, to give anyone who possesses the object code either **(1)** a copy of +the Corresponding Source for all the software in the product that is covered by this +License, on a durable physical medium customarily used for software interchange, for +a price no more than your reasonable cost of physically performing this conveying of +source, or **(2)** access to copy the Corresponding Source from a network server at no +charge. +* **c)** Convey individual copies of the object code with a copy of the written offer to +provide the Corresponding Source. This alternative is allowed only occasionally and +noncommercially, and only if you received the object code with such an offer, in +accord with subsection 6b. +* **d)** Convey the object code by offering access from a designated place (gratis or for +a charge), and offer equivalent access to the Corresponding Source in the same way +through the same place at no further charge. You need not require recipients to copy +the Corresponding Source along with the object code. If the place to copy the object +code is a network server, the Corresponding Source may be on a different server +(operated by you or a third party) that supports equivalent copying facilities, +provided you maintain clear directions next to the object code saying where to find +the Corresponding Source. Regardless of what server hosts the Corresponding Source, +you remain obligated to ensure that it is available for as long as needed to satisfy +these requirements. +* **e)** Convey the object code using peer-to-peer transmission, provided you inform +other peers where the object code and Corresponding Source of the work are being +offered to the general public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from the +Corresponding Source as a System Library, need not be included in conveying the +object code work. + +A “User Product” is either **(1)** a “consumer product”, which +means any tangible personal property which is normally used for personal, family, or +household purposes, or **(2)** anything designed or sold for incorporation into a +dwelling. In determining whether a product is a consumer product, doubtful cases +shall be resolved in favor of coverage. For a particular product received by a +particular user, “normally used” refers to a typical or common use of +that class of product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected to use, the +product. A product is a consumer product regardless of whether the product has +substantial commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + +“Installation Information” for a User Product means any methods, +procedures, authorization keys, or other information required to install and execute +modified versions of a covered work in that User Product from a modified version of +its Corresponding Source. The information must suffice to ensure that the continued +functioning of the modified object code is in no case prevented or interfered with +solely because modification has been made. + +If you convey an object code work under this section in, or with, or specifically for +use in, a User Product, and the conveying occurs as part of a transaction in which +the right of possession and use of the User Product is transferred to the recipient +in perpetuity or for a fixed term (regardless of how the transaction is +characterized), the Corresponding Source conveyed under this section must be +accompanied by the Installation Information. But this requirement does not apply if +neither you nor any third party retains the ability to install modified object code +on the User Product (for example, the work has been installed in ROM). + +The requirement to provide Installation Information does not include a requirement to +continue to provide support service, warranty, or updates for a work that has been +modified or installed by the recipient, or for the User Product in which it has been +modified or installed. Access to a network may be denied when the modification itself +materially and adversely affects the operation of the network or violates the rules +and protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord with +this section must be in a format that is publicly documented (and with an +implementation available to the public in source code form), and must require no +special password or key for unpacking, reading or copying. + +### 7. Additional Terms + +“Additional permissions” are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. Additional +permissions that are applicable to the entire Program shall be treated as though they +were included in this License, to the extent that they are valid under applicable +law. If additional permissions apply only to part of the Program, that part may be +used separately under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any +additional permissions from that copy, or from any part of it. (Additional +permissions may be written to require their own removal in certain cases when you +modify the work.) You may place additional permissions on material, added by you to a +covered work, for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you add to a +covered work, you may (if authorized by the copyright holders of that material) +supplement the terms of this License with terms: + +* **a)** Disclaiming warranty or limiting liability differently from the terms of +sections 15 and 16 of this License; or +* **b)** Requiring preservation of specified reasonable legal notices or author +attributions in that material or in the Appropriate Legal Notices displayed by works +containing it; or +* **c)** Prohibiting misrepresentation of the origin of that material, or requiring that +modified versions of such material be marked in reasonable ways as different from the +original version; or +* **d)** Limiting the use for publicity purposes of names of licensors or authors of the +material; or +* **e)** Declining to grant rights under trademark law for use of some trade names, +trademarks, or service marks; or +* **f)** Requiring indemnification of licensors and authors of that material by anyone +who conveys the material (or modified versions of it) with contractual assumptions of +liability to the recipient, for any liability that these contractual assumptions +directly impose on those licensors and authors. + +All other non-permissive additional terms are considered “further +restrictions” within the meaning of section 10. If the Program as you received +it, or any part of it, contains a notice stating that it is governed by this License +along with a term that is a further restriction, you may remove that term. If a +license document contains a further restriction but permits relicensing or conveying +under this License, you may add to a covered work material governed by the terms of +that license document, provided that the further restriction does not survive such +relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, in +the relevant source files, a statement of the additional terms that apply to those +files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a +separately written license, or stated as exceptions; the above requirements apply +either way. + +### 8. Termination + +You may not propagate or modify a covered work except as expressly provided under +this License. Any attempt otherwise to propagate or modify it is void, and will +automatically terminate your rights under this License (including any patent licenses +granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a +particular copyright holder is reinstated **(a)** provisionally, unless and until the +copyright holder explicitly and finally terminates your license, and **(b)** permanently, +if the copyright holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently +if the copyright holder notifies you of the violation by some reasonable means, this +is the first time you have received notice of violation of this License (for any +work) from that copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses of +parties who have received copies or rights from you under this License. If your +rights have been terminated and not permanently reinstated, you do not qualify to +receive new licenses for the same material under section 10. + +### 9. Acceptance Not Required for Having Copies + +You are not required to accept this License in order to receive or run a copy of the +Program. Ancillary propagation of a covered work occurring solely as a consequence of +using peer-to-peer transmission to receive a copy likewise does not require +acceptance. However, nothing other than this License grants you permission to +propagate or modify any covered work. These actions infringe copyright if you do not +accept this License. Therefore, by modifying or propagating a covered work, you +indicate your acceptance of this License to do so. + +### 10. Automatic Licensing of Downstream Recipients + +Each time you convey a covered work, the recipient automatically receives a license +from the original licensors, to run, modify and propagate that work, subject to this +License. You are not responsible for enforcing compliance by third parties with this +License. + +An “entity transaction” is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an organization, or +merging organizations. If propagation of a covered work results from an entity +transaction, each party to that transaction who receives a copy of the work also +receives whatever licenses to the work the party's predecessor in interest had or +could give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if the predecessor +has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights granted or +affirmed under this License. For example, you may not impose a license fee, royalty, +or other charge for exercise of rights granted under this License, and you may not +initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging +that any patent claim is infringed by making, using, selling, offering for sale, or +importing the Program or any portion of it. + +### 11. Patents + +A “contributor” is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The work thus +licensed is called the contributor's “contributor version”. + +A contributor's “essential patent claims” are all patent claims owned or +controlled by the contributor, whether already acquired or hereafter acquired, that +would be infringed by some manner, permitted by this License, of making, using, or +selling its contributor version, but do not include claims that would be infringed +only as a consequence of further modification of the contributor version. For +purposes of this definition, “control” includes the right to grant patent +sublicenses in a manner consistent with the requirements of this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent license +under the contributor's essential patent claims, to make, use, sell, offer for sale, +import and otherwise run, modify and propagate the contents of its contributor +version. + +In the following three paragraphs, a “patent license” is any express +agreement or commitment, however denominated, not to enforce a patent (such as an +express permission to practice a patent or covenant not to sue for patent +infringement). To “grant” such a patent license to a party means to make +such an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the +Corresponding Source of the work is not available for anyone to copy, free of charge +and under the terms of this License, through a publicly available network server or +other readily accessible means, then you must either **(1)** cause the Corresponding +Source to be so available, or **(2)** arrange to deprive yourself of the benefit of the +patent license for this particular work, or **(3)** arrange, in a manner consistent with +the requirements of this License, to extend the patent license to downstream +recipients. “Knowingly relying” means you have actual knowledge that, but +for the patent license, your conveying the covered work in a country, or your +recipient's use of the covered work in a country, would infringe one or more +identifiable patents in that country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you +convey, or propagate by procuring conveyance of, a covered work, and grant a patent +license to some of the parties receiving the covered work authorizing them to use, +propagate, modify or convey a specific copy of the covered work, then the patent +license you grant is automatically extended to all recipients of the covered work and +works based on it. + +A patent license is “discriminatory” if it does not include within the +scope of its coverage, prohibits the exercise of, or is conditioned on the +non-exercise of one or more of the rights that are specifically granted under this +License. You may not convey a covered work if you are a party to an arrangement with +a third party that is in the business of distributing software, under which you make +payment to the third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties who would receive +the covered work from you, a discriminatory patent license **(a)** in connection with +copies of the covered work conveyed by you (or copies made from those copies), or **(b)** +primarily for and in connection with specific products or compilations that contain +the covered work, unless you entered into that arrangement, or that patent license +was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied +license or other defenses to infringement that may otherwise be available to you +under applicable patent law. + +### 12. No Surrender of Others' Freedom + +If conditions are imposed on you (whether by court order, agreement or otherwise) +that contradict the conditions of this License, they do not excuse you from the +conditions of this License. If you cannot convey a covered work so as to satisfy +simultaneously your obligations under this License and any other pertinent +obligations, then as a consequence you may not convey it at all. For example, if you +agree to terms that obligate you to collect a royalty for further conveying from +those to whom you convey the Program, the only way you could satisfy both those terms +and this License would be to refrain entirely from conveying the Program. + +### 13. Use with the GNU Affero General Public License + +Notwithstanding any other provision of this License, you have permission to link or +combine any covered work with a work licensed under version 3 of the GNU Affero +General Public License into a single combined work, and to convey the resulting work. +The terms of this License will continue to apply to the part which is the covered +work, but the special requirements of the GNU Affero General Public License, section +13, concerning interaction through a network will apply to the combination as such. + +### 14. Revised Versions of this License + +The Free Software Foundation may publish revised and/or new versions of the GNU +General Public License from time to time. Such new versions will be similar in spirit +to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies that +a certain numbered version of the GNU General Public License “or any later +version” applies to it, you have the option of following the terms and +conditions either of that numbered version or of any later version published by the +Free Software Foundation. If the Program does not specify a version number of the GNU +General Public License, you may choose any version ever published by the Free +Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the GNU +General Public License can be used, that proxy's public statement of acceptance of a +version permanently authorizes you to choose that version for the Program. + +Later license versions may give you additional or different permissions. However, no +additional obligations are imposed on any author or copyright holder as a result of +your choosing to follow a later version. + +### 15. Disclaimer of Warranty + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER +EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE +QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +### 16. Limitation of Liability + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY +COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS +PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, +INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE +OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE +WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +### 17. Interpretation of Sections 15 and 16 + +If the disclaimer of warranty and limitation of liability provided above cannot be +given local legal effect according to their terms, reviewing courts shall apply local +law that most closely approximates an absolute waiver of all civil liability in +connection with the Program, unless a warranty or assumption of liability accompanies +a copy of the Program in return for a fee. + +_END OF TERMS AND CONDITIONS_ + +## How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to +the public, the best way to achieve this is to make it free software which everyone +can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them +to the start of each source file to most effectively state the exclusion of warranty; +and each file should have at least the “copyright” line and a pointer to +where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program does terminal interaction, make it output a short notice like this +when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type 'show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type 'show c' for details. + +The hypothetical commands `show w` and `show c` should show the appropriate parts of +the General Public License. Of course, your program's commands might be different; +for a GUI interface, you would use an “about box”. + +You should also get your employer (if you work as a programmer) or school, if any, to +sign a “copyright disclaimer” for the program, if necessary. For more +information on this, and how to apply and follow the GNU GPL, see +<>. + +The GNU General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may consider it +more useful to permit linking proprietary applications with the library. If this is +what you want to do, use the GNU Lesser General Public License instead of this +License. But first, please read +<>. \ No newline at end of file diff --git a/LICENSE_ru.md b/LICENSE_ru.md new file mode 100644 index 00000000..48658111 --- /dev/null +++ b/LICENSE_ru.md @@ -0,0 +1,69 @@ +# Лицензионное соглашение VPNclient (Российская Федерация) + +## 1. Общие положения +Настоящая лицензия регулируется нормами Гражданского кодекса Российской Федерации (часть IV, статьи 1225-1301) и иных применимых законодательных актов РФ. + +Правообладатель: ООО «НейтивМайнд» (NativeMind Inc.) + +Товарный знак «VPNclient» зарегистрирован и принадлежит правообладателю. + +© 2010-2025 ООО «НейтивМайнд». Все права защищены. + +## 2. Условия использования + +Используя программный продукт VPNclient («Продукт»), вы подтверждаете согласие с условиями настоящей лицензии. В случае несогласия вы обязаны немедленно прекратить использование и распространение Продукта. + +### 2.1. Доступность исходного кода + +При использовании исходного кода Продукта вы обязаны: + +- Опубликовать производный код исключительно в виде публичного репозитория на платформе GitHub в виде форка официального репозитория VPNclient (https://github.com/VPNclient). +- Своевременно обновлять свой репозиторий, отражая изменения в официальных релизах. +- Указывать в репозитории, что он является форком официального репозитория VPNclient. + +### 2.2. Автоматизация релизов + +Все релизы Продукта должны осуществляться исключительно через GitHub Actions. + +### 2.3. Обязательная атрибуция + +При распространении производного Продукта вы обязаны: + +- Указывать автора и давать соответствующую ссылку на официальный репозиторий VPNclient: https://github.com/VPNclient. +- Размещать ссылку на оригинальную лицензию. +- Документировать изменения, сделанные вами в README вашего репозитория. + +### 2.4. Запрет вредоносного ПО + +Категорически запрещается встраивать любое вредоносное программное обеспечение (вирусы, трояны и т.п.). + +### 2.5. Ограничения на использование имени и интерфейса + +Запрещается публикация производных продуктов под названием или с пользовательским интерфейсом, сходным до степени смешения с оригинальным продуктом VPNclient, на любых площадках распространения программного обеспечения (например, Google Play, App Store, RuStore, NashStore, AppGallery, VK Play и других площадках). + +### 2.6. Некоммерческое использование + +Использование Продукта в коммерческих целях (продажа, реклама, иные коммерческие операции) допускается исключительно после письменного согласия правообладателя. + +### 2.7. Условие аналогичного распространения (ShareAlike) + +Любое изменение, адаптация или создание производных работ на основе Продукта должно распространяться исключительно на условиях настоящей лицензии и с открытым исходным кодом через форк официального репозитория VPNclient. + +## 3. Использование компонентов с открытым исходным кодом + +VPNclient содержит компоненты с открытым исходным кодом, лицензируемые отдельно. Пользуясь VPNclient, вы принимаете условия лицензий каждого компонента. Подробности указаны в файле LICENSE_ru.md, входящем в комплект поставки. + +## 4. Ответственность сторон + +Продукт предоставляется «как есть», без каких-либо гарантий, явных или подразумеваемых, в том числе гарантий пригодности для определённых целей и отсутствия нарушений прав третьих лиц. Правообладатель не несёт ответственности за убытки, возникающие вследствие использования или невозможности использования Продукта. + +## 5. Нарушение условий лицензии + +При нарушении условий настоящей лицензии правообладатель вправе требовать прекращения распространения Продукта, а также обратиться в суд или обратиться с жалобой в соответствующие площадки распространения программного обеспечения для удаления или ограничения доступа к нарушающим продуктам. + +Споры разрешаются в порядке, установленном законодательством Российской Федерации. + +--- + +Используя VPNclient, вы подтверждаете, что прочитали, поняли и полностью принимаете условия настоящей лицензии. + diff --git a/LICENSE_th.md b/LICENSE_th.md new file mode 100644 index 00000000..b29def88 --- /dev/null +++ b/LICENSE_th.md @@ -0,0 +1,70 @@ +# ข้อตกลงสิทธิการใช้งาน VPNclient (ประเทศไทย) + +## 1. ข้อกำหนดทั่วไป + +ข้อตกลงนี้อยู่ภายใต้บังคับของพระราชบัญญัติลิขสิทธิ์ พ.ศ. 2537 และแก้ไขเพิ่มเติม รวมถึงประมวลกฎหมายแพ่งและพาณิชย์แห่งราชอาณาจักรไทย + +ผู้ถือลิขสิทธิ์: บริษัท เนทีฟมายด์ จำกัด (NativeMind Inc.) + +เครื่องหมายการค้า "VPNclient" ได้รับการจดทะเบียนและถือครองโดยผู้ถือลิขสิทธิ์ + +ลิขสิทธิ์ © 2010-2025 NativeMind Inc. สงวนลิขสิทธิ์ + +## 2. เงื่อนไขการใช้งาน + +การใช้งานโปรแกรม VPNclient ("ผลิตภัณฑ์") ถือว่าท่านยอมรับข้อกำหนดและเงื่อนไขตามข้อตกลงฉบับนี้ หากท่านไม่ยอมรับ โปรดหยุดใช้งานผลิตภัณฑ์โดยทันที + +### 2.1. การเผยแพร่ซอร์สโค้ด + +เมื่อท่านใช้งานซอร์สโค้ดของผลิตภัณฑ์ ท่านมีหน้าที่: + +- เผยแพร่ซอร์สโค้ดของท่านผ่าน GitHub โดยทำเป็น Fork ของ Repository อย่างเป็นทางการที่ https://github.com/VPNclient +- ปรับปรุงให้เป็นปัจจุบันอยู่เสมอเพื่อสะท้อนการเปลี่ยนแปลงจากต้นฉบับ +- ระบุให้ชัดเจนว่าซอร์สโค้ดของท่านเป็น Fork ของ Repository ดั้งเดิม + +### 2.2. การเผยแพร่อัตโนมัติ + +การเผยแพร่ทุกครั้งต้องดำเนินการผ่าน GitHub Actions เท่านั้น + +### 2.3. การให้เครดิต (Attribution) + +เมื่อเผยแพร่ผลิตภัณฑ์ที่ดัดแปลงจาก VPNclient ท่านต้อง: + +- ระบุเครดิตของผู้ถือลิขสิทธิ์พร้อมลิงก์ไปที่ https://github.com/VPNclient +- แสดงลิงก์ไปยังข้อตกลงสิทธิ์ดั้งเดิม +- บันทึกการเปลี่ยนแปลงใน README ของ Repository ของท่าน + +### 2.4. ห้ามการใส่มัลแวร์ + +ห้ามอย่างเด็ดขาดที่จะเพิ่มมัลแวร์หรือรหัสอันตรายใด ๆ ลงในผลิตภัณฑ์ + +### 2.5. ข้อจำกัดการใช้ชื่อและอินเทอร์เฟซ + +ห้ามเผยแพร่แอปพลิเคชันในร้านค้าแอปใดๆ เช่น Google Play, App Store, F-Droid, Microsoft Store, Huawei AppGallery หรือแพลตฟอร์มอื่นใดด้วยชื่อหรือหน้าตาที่คล้ายกับ VPNclient จนอาจทำให้เกิดความเข้าใจผิด + +### 2.6. การใช้งานเชิงพาณิชย์ + +ห้ามนำผลิตภัณฑ์นี้ไปใช้งานเพื่อวัตถุประสงค์ทางการค้า (ขายหรือโฆษณา) โดยไม่ได้รับอนุญาตเป็นลายลักษณ์อักษรจากผู้ถือลิขสิทธิ์ก่อน + +### 2.7. การเผยแพร่ภายใต้เงื่อนไขเดียวกัน (ShareAlike) + +ผลงานที่สร้างจากผลิตภัณฑ์นี้ต้องถูกเผยแพร่ภายใต้ข้อตกลงสิทธิ์เดียวกันและในรูปแบบ Open Source ผ่านการ Fork จาก Repository ดั้งเดิมของ VPNclient + +## 3. การใช้งานส่วนประกอบ Open Source + +VPNclient ประกอบด้วยส่วนประกอบ Open Source ที่ได้รับอนุญาตตามเงื่อนไขอื่นๆ การใช้งาน VPNclient หมายถึงท่านยอมรับเงื่อนไขของแต่ละส่วนประกอบด้วย รายละเอียดระบุไว้ในไฟล์ LICENSE.md ที่แนบมาพร้อมผลิตภัณฑ์ + +## 4. การปฏิเสธความรับผิด + +ผลิตภัณฑ์นี้ให้บริการ "ตามสภาพ" โดยไม่มีการรับประกันใดๆ ทั้งสิ้น ไม่ว่าจะแจ้งชัดหรือโดยนัย รวมถึงการรับประกันด้านความเหมาะสมกับวัตถุประสงค์เฉพาะใดๆ และผู้ถือลิขสิทธิ์จะไม่รับผิดชอบต่อความเสียหายใดๆ อันเกิดจากการใช้งานผลิตภัณฑ์นี้ + +## 5. การละเมิดข้อตกลง + +ในกรณีที่มีการละเมิดข้อตกลงนี้ ผู้ถือลิขสิทธิ์มีสิทธิ์ขอให้หยุดเผยแพร่ผลิตภัณฑ์ และอาจดำเนินการทางกฎหมายหรือแจ้งไปยังร้านค้าแอปพลิเคชันเพื่อให้มีการลบหรือระงับการเผยแพร่ผลิตภัณฑ์ที่ละเมิด + +ข้อพิพาทใดๆ จะต้องได้รับการตัดสินตามกฎหมายแห่งราชอาณาจักรไทย + +--- + +การใช้งาน VPNclient หมายถึงท่านได้อ่าน เข้าใจ และยอมรับข้อกำหนดและเงื่อนไขทั้งหมดตามข้อตกลงนี้แล้ว + From 8ddfec25698a46990279ad521c06594109f1cf11 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Tue, 1 Apr 2025 16:35:26 +0700 Subject: [PATCH 42/77] README --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index b760ec6a..99781391 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,23 @@ VPN Client Engine Flutter is a Flutter Plugin for managing VPN connections with an intuitive API. It provides seamless integration with various platforms, allowing developers to control VPN connections efficiently. ![VPN Client Engine](https://raw.githubusercontent.com/VPNclient/.github/refs/heads/main/assets/vpnclient_scheme2.png) + + +## 🏗️ Architecture Overview + +```mermaid +graph TD + style A fill:#f9d5e5 + A[VPNclient App] --> B[VPNclient Engine Flutter Plugin] + style B fill:#eeac99 + B --> C[VPNclient Engine] + C --> D[iOS] + C --> E[Android] + C --> F[macOS] + C --> G[Windows] + C --> H[Linux] +``` + ### ✅ Supported Platforms - iOS 15+ (iPhone, iPad, MacOS M) - Android From 6195e502c75a36ce1f521e56c73e5f3ebcbb886b Mon Sep 17 00:00:00 2001 From: ginterloper Date: Wed, 2 Apr 2025 01:12:12 +0300 Subject: [PATCH 43/77] Update build.gradle --- android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index 6ae9de25..59f84d7b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -51,7 +51,7 @@ android { dependencies { testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.mockito:mockito-core:5.0.0") - implementation project(':vpnclient-engine-android') + //implementation project(':vpnclient-app') } testOptions { From 1a7882b6900b32150e7bf9e726ed0d45e45b76f3 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 3 Apr 2025 10:13:04 +0700 Subject: [PATCH 44/77] android --- android/build.gradle | 2 +- ios/Classes/VpnclientEngineFlutterPlugin.swift | 2 +- ios/Podfile | 4 ++-- ios/vpnclient_engine_flutter.podspec | 2 +- macos/Classes/VpnclientEngineFlutterPlugin.swift | 2 +- macos/Podfile | 1 + macos/vpnclient_engine_flutter.podspec | 2 +- 7 files changed, 8 insertions(+), 7 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 6ae9de25..7cb5671e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -51,7 +51,7 @@ android { dependencies { testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.mockito:mockito-core:5.0.0") - implementation project(':vpnclient-engine-android') + //implementation project(':vpnclient-engine-android') } testOptions { diff --git a/ios/Classes/VpnclientEngineFlutterPlugin.swift b/ios/Classes/VpnclientEngineFlutterPlugin.swift index 8e0d9f2b..74297bf0 100644 --- a/ios/Classes/VpnclientEngineFlutterPlugin.swift +++ b/ios/Classes/VpnclientEngineFlutterPlugin.swift @@ -1,6 +1,6 @@ import Flutter import UIKit -import VPNclientEngineIOS +// import VPNclientEngineIOS public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { diff --git a/ios/Podfile b/ios/Podfile index 4441b764..78564ab9 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -4,6 +4,6 @@ use_frameworks! target 'VPNclientEngineFlutter' do - use_frameworks! - pod 'VPNclientEngineIOS', :path => '../../VPNclient-engine-ios' +# use_frameworks! +# pod 'VPNclientEngineIOS', :path => '../../VPNclient-engine-ios' end diff --git a/ios/vpnclient_engine_flutter.podspec b/ios/vpnclient_engine_flutter.podspec index 847cb866..d32f6070 100644 --- a/ios/vpnclient_engine_flutter.podspec +++ b/ios/vpnclient_engine_flutter.podspec @@ -15,7 +15,7 @@ VPNclient Engine Flutter plugin project. s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - s.dependency 'VPNclientEngineIOS', :path => '../VPNclient-engine-ios' + #s.dependency 'VPNclientEngineIOS', :path => '../1/VPNclient-engine-ios' s.platform = :ios, '12.0' # Flutter.framework does not contain a i386 slice. diff --git a/macos/Classes/VpnclientEngineFlutterPlugin.swift b/macos/Classes/VpnclientEngineFlutterPlugin.swift index e16e1424..dc7adf4d 100644 --- a/macos/Classes/VpnclientEngineFlutterPlugin.swift +++ b/macos/Classes/VpnclientEngineFlutterPlugin.swift @@ -1,6 +1,6 @@ import Cocoa import FlutterMacOS -import VPNclientEngineIOS +// import VPNclientEngineIOS public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { diff --git a/macos/Podfile b/macos/Podfile index b2beab69..76502ffc 100644 --- a/macos/Podfile +++ b/macos/Podfile @@ -3,4 +3,5 @@ project '../../VPNclient-engine-ios/cntrlr.xcodeproj' target 'Runner' do use_frameworks! pod 'VPNclientEngineIOS', :path => '../../VPNclient-engine-ios' +# pod 'cntrlr', :path => '../../VPNclient-engine-ios' end diff --git a/macos/vpnclient_engine_flutter.podspec b/macos/vpnclient_engine_flutter.podspec index b5007c5b..fd4297cb 100644 --- a/macos/vpnclient_engine_flutter.podspec +++ b/macos/vpnclient_engine_flutter.podspec @@ -23,7 +23,7 @@ VPNclient Engine Flutter plugin project. # s.resource_bundles = {'vpnclient_engine_flutter_privacy' => ['Resources/PrivacyInfo.xcprivacy']} s.dependency 'FlutterMacOS' - s.dependency 'VPNclientEngineIOS', :path => '../VPNclient-engine-ios' + #s.dependency 'VPNclientEngineIOS', :path => '../../VPNclient-engine-ios' s.platform = :osx, '10.11' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } From 203bbf671cf585c1b598c7f2429cd7e6948fd5ce Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 3 Apr 2025 10:14:00 +0700 Subject: [PATCH 45/77] android --- example/ios/Podfile | 43 +++++++++++++++++++ .../flutter/generated_plugin_registrant.cc | 15 +++++++ .../flutter/generated_plugin_registrant.h | 15 +++++++ example/linux/flutter/generated_plugins.cmake | 24 +++++++++++ .../Flutter/GeneratedPluginRegistrant.swift | 12 ++++++ example/macos/Podfile | 42 ++++++++++++++++++ .../flutter/generated_plugin_registrant.cc | 14 ++++++ .../flutter/generated_plugin_registrant.h | 15 +++++++ .../windows/flutter/generated_plugins.cmake | 24 +++++++++++ 9 files changed, 204 insertions(+) create mode 100644 example/ios/Podfile create mode 100644 example/linux/flutter/generated_plugin_registrant.cc create mode 100644 example/linux/flutter/generated_plugin_registrant.h create mode 100644 example/linux/flutter/generated_plugins.cmake create mode 100644 example/macos/Flutter/GeneratedPluginRegistrant.swift create mode 100644 example/macos/Podfile create mode 100644 example/windows/flutter/generated_plugin_registrant.cc create mode 100644 example/windows/flutter/generated_plugin_registrant.h create mode 100644 example/windows/flutter/generated_plugins.cmake diff --git a/example/ios/Podfile b/example/ios/Podfile new file mode 100644 index 00000000..e549ee22 --- /dev/null +++ b/example/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/example/linux/flutter/generated_plugin_registrant.cc b/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..5999de01 --- /dev/null +++ b/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) vpnclient_engine_flutter_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "VpnclientEngineFlutterPlugin"); + vpnclient_engine_flutter_plugin_register_with_registrar(vpnclient_engine_flutter_registrar); +} diff --git a/example/linux/flutter/generated_plugin_registrant.h b/example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..e0f0a47b --- /dev/null +++ b/example/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/example/linux/flutter/generated_plugins.cmake b/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 00000000..8e500216 --- /dev/null +++ b/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + vpnclient_engine_flutter +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 00000000..8366bf1b --- /dev/null +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import vpnclient_engine_flutter + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + VpnclientEngineFlutterPlugin.register(with: registry.registrar(forPlugin: "VpnclientEngineFlutterPlugin")) +} diff --git a/example/macos/Podfile b/example/macos/Podfile new file mode 100644 index 00000000..29c8eb32 --- /dev/null +++ b/example/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/example/windows/flutter/generated_plugin_registrant.cc b/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..4bf49e71 --- /dev/null +++ b/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + VpnclientEngineFlutterPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("VpnclientEngineFlutterPluginCApi")); +} diff --git a/example/windows/flutter/generated_plugin_registrant.h b/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..dc139d85 --- /dev/null +++ b/example/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/example/windows/flutter/generated_plugins.cmake b/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 00000000..f9fea320 --- /dev/null +++ b/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + vpnclient_engine_flutter +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) From dc189322a5baf4b6d75d860318028f6c555f3b97 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 3 Apr 2025 10:28:38 +0700 Subject: [PATCH 46/77] ios --- pubspec.yaml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 725e3447..7c2b20cf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,10 +29,21 @@ platforms: ios: dependencies: vpnclient_engine_ios: - git: - url: https://github.com/VPNclient/VPNclient-engine-ios.git - ref: main - path: . + path: ../VPNcleint-engine-ios +# git: +# url: https://github.com/VPNclient/VPNclient-engine-ios.git +# ref: main +# path: . + + macos: + dependencies: + vpnclient_engine_macos: + path: ../VPNcleint-engine-ios +# git: +# url: https://github.com/VPNclient/VPNclient-engine-ios.git +# ref: main +# path: . + windows: dependencies: vpnclient_engine_windows: From f0455ebbf01441f69ebd6eba5c07b337622fb360 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 3 Apr 2025 15:18:26 +0700 Subject: [PATCH 47/77] ios --- ios/Podfile | 5 +++-- ios/vpnclient_engine_flutter.podspec | 30 ---------------------------- pubspec.yaml | 4 ++-- 3 files changed, 5 insertions(+), 34 deletions(-) delete mode 100644 ios/vpnclient_engine_flutter.podspec diff --git a/ios/Podfile b/ios/Podfile index 78564ab9..d50e1b31 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -4,6 +4,7 @@ use_frameworks! target 'VPNclientEngineFlutter' do -# use_frameworks! -# pod 'VPNclientEngineIOS', :path => '../../VPNclient-engine-ios' + use_frameworks! + pod 'VPNclientEngine', :path => '../../VPNclient-engine-ios' + pod 'VPNclientTunnel', :path => '../../VPNclient-engine-ios' end diff --git a/ios/vpnclient_engine_flutter.podspec b/ios/vpnclient_engine_flutter.podspec deleted file mode 100644 index d32f6070..00000000 --- a/ios/vpnclient_engine_flutter.podspec +++ /dev/null @@ -1,30 +0,0 @@ -# -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. -# Run `pod lib lint vpnclient_engine_flutter.podspec` to validate before publishing. -# -Pod::Spec.new do |s| - s.name = 'vpnclient_engine_flutter' - s.version = '0.0.1' - s.summary = 'VPNclient Engine Flutter plugin project.' - s.description = <<-DESC -VPNclient Engine Flutter plugin project. - DESC - s.homepage = 'http://vpnclient.click' - s.license = { :file => '../LICENSE' } - s.author = { 'Your Company' => 'admin@nativemind.net' } - s.source = { :path => '.' } - s.source_files = 'Classes/**/*' - s.dependency 'Flutter' - #s.dependency 'VPNclientEngineIOS', :path => '../1/VPNclient-engine-ios' - s.platform = :ios, '12.0' - - # Flutter.framework does not contain a i386 slice. - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } - s.swift_version = '5.0' - - # If your plugin requires a privacy manifest, for example if it uses any - # required reason APIs, update the PrivacyInfo.xcprivacy file to describe your - # plugin's privacy impact, and then uncomment this line. For more information, - # see https://developer.apple.com/documentation/bundleresources/privacy_manifest_files - # s.resource_bundles = {'vpnclient_engine_flutter_privacy' => ['Resources/PrivacyInfo.xcprivacy']} -end diff --git a/pubspec.yaml b/pubspec.yaml index 7c2b20cf..47b2cf0d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,7 +29,7 @@ platforms: ios: dependencies: vpnclient_engine_ios: - path: ../VPNcleint-engine-ios + path: ../VPNclient-engine-ios # git: # url: https://github.com/VPNclient/VPNclient-engine-ios.git # ref: main @@ -38,7 +38,7 @@ platforms: macos: dependencies: vpnclient_engine_macos: - path: ../VPNcleint-engine-ios + path: ../VPNclient-engine-ios # git: # url: https://github.com/VPNclient/VPNclient-engine-ios.git # ref: main From 5b84136846b8133187f93712493f6ed8024719c9 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 3 Apr 2025 15:23:12 +0700 Subject: [PATCH 48/77] ios --- ios/Classes/VpnclientEngineFlutterPlugin.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ios/Classes/VpnclientEngineFlutterPlugin.swift b/ios/Classes/VpnclientEngineFlutterPlugin.swift index 74297bf0..9c550024 100644 --- a/ios/Classes/VpnclientEngineFlutterPlugin.swift +++ b/ios/Classes/VpnclientEngineFlutterPlugin.swift @@ -1,6 +1,7 @@ import Flutter import UIKit -// import VPNclientEngineIOS +import VPNclientTunnel +import VPNclientEngine public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { From 50d18823207e8cf1228c39049a6b9b75a3420a55 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Fri, 4 Apr 2025 17:55:01 +0700 Subject: [PATCH 49/77] ios --- ios/Podfile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ios/Podfile b/ios/Podfile index d50e1b31..86ec2f15 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -5,6 +5,11 @@ use_frameworks! target 'VPNclientEngineFlutter' do use_frameworks! + # pod 'VPNclientEngine', :git => 'https://github.com/VPNclient/VPNclient-engine-ios.git', :tag => '0.1.0' pod 'VPNclientEngine', :path => '../../VPNclient-engine-ios' - pod 'VPNclientTunnel', :path => '../../VPNclient-engine-ios' end + +target 'TunnelExtension' do + # pod 'VPNclientTunnel', :git => 'https://github.com/VPNclient/VPNclient-engine-ios.git', :tag => '0.1.0' + pod 'VPNclientTunnel', :path => '../../VPNclient-engine-ios' +end \ No newline at end of file From 23a0d0b4ba8baf25eac802f7985902e3204fbf36 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Fri, 4 Apr 2025 19:51:09 +0700 Subject: [PATCH 50/77] android --- android/build.gradle | 4 +++- android/settings.gradle | 12 ++++++++++-- .../VpnclientEngineFlutterPlugin.kt | 14 ++++++++++++++ lib/vpnclient_engine/engine.dart | 7 ++++++- lib/vpnclient_engine_flutter_method_channel.dart | 8 ++++++++ 5 files changed, 41 insertions(+), 4 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 7cb5671e..834b57df 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -19,6 +19,7 @@ allprojects { repositories { google() mavenCentral() + //maven { url 'https://jitpack.io' } } } @@ -51,7 +52,8 @@ android { dependencies { testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.mockito:mockito-core:5.0.0") - //implementation project(':vpnclient-engine-android') + implementation project(':vpnclient-engine-android') + //implementation 'com.github.VPNclient:VPNclient-engine:android-v0.1.1' } testOptions { diff --git a/android/settings.gradle b/android/settings.gradle index d8385ac9..8dbb3524 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,4 +1,12 @@ rootProject.name = 'vpnclient_engine_flutter' -include ':vpnclient_engine_android' -project(':vpnclient_engine_android').projectDir = new File('../../VPNclient_engine_android') \ No newline at end of file +//include ':vpnclient_engine_android' +//project(':vpnclient_engine_android').projectDir = new File('../../VPNclient_engine_android') + +def targetPath = new File(settingsDir, '../../VPNclient-engine/android') +println "[DEBUG55] Expected project dir: ${targetPath.absolutePath}" + + +include ':vpnclient-engine-android' +project(':vpnclient-engine-android').projectDir = new File('../../../VPNclient-engine/android') +//project(':vpnclient-engine-android').projectDir = new File('/Users/anton/proj/VPNclient/VPNclient-engine/android') \ No newline at end of file diff --git a/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt b/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt index afdc9f55..6e383e65 100644 --- a/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt +++ b/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt @@ -6,6 +6,8 @@ import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result +import click.vpnclient.engine.VPNManager + /** VpnclientEngineFlutterPlugin */ class VpnclientEngineFlutterPlugin: FlutterPlugin, MethodCallHandler { /// The MethodChannel that will the communication between Flutter and native Android @@ -20,6 +22,18 @@ class VpnclientEngineFlutterPlugin: FlutterPlugin, MethodCallHandler { } override fun onMethodCall(call: MethodCall, result: Result) { + when (call.method) { + "startVPN" -> { + val config = call.argument("config") ?: return result.error("NO_CONFIG", "Missing config", null) + val success = VPNManager.startVPN(context, config) + result.success(success) + } + "stopVPN" -> { + VPNManager.stopVPN() + result.success(null) + } + "status" -> result.success(VPNManager.status()) + } if (call.method == "getPlatformVersion") { result.success("Android ${android.os.Build.VERSION.RELEASE}") } else { diff --git a/lib/vpnclient_engine/engine.dart b/lib/vpnclient_engine/engine.dart index 4fda0b79..517c2914 100644 --- a/lib/vpnclient_engine/engine.dart +++ b/lib/vpnclient_engine/engine.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:dart_ping/dart_ping.dart'; - +import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter_platform_interface.dart'; enum ConnectionStatus { connecting, connected, disconnected, error } @@ -125,8 +125,13 @@ class VPNclientEngine { static Stream get onPingResult => _pingResultController.stream; static Future connect({required int subscriptionIndex, required int serverIndex}) async { + print(await VpnclientEngineFlutterPlatform.instance.getPlatformVersion()); + print('Connecting to subscription $subscriptionIndex, server $serverIndex...'); _connectionStatusController.add(ConnectionStatus.connecting); + + print(await VpnclientEngineFlutterPlatform.instance.getPlatformVersion()); + await Future.delayed(Duration(seconds: 5)); _connectionStatusController.add(ConnectionStatus.connected); print('Successfully connected'); diff --git a/lib/vpnclient_engine_flutter_method_channel.dart b/lib/vpnclient_engine_flutter_method_channel.dart index b1563b3f..7d27d7c0 100644 --- a/lib/vpnclient_engine_flutter_method_channel.dart +++ b/lib/vpnclient_engine_flutter_method_channel.dart @@ -14,4 +14,12 @@ class MethodChannelVpnclientEngineFlutter extends VpnclientEngineFlutterPlatform final version = await methodChannel.invokeMethod('getPlatformVersion'); return version; } + + Future startVPN(String configPath) => + methodChannel.invokeMethod('startVPN', {"config": configPath}); + + Future stopVPN() => methodChannel.invokeMethod('stopVPN'); + + Future getStatus() => + methodChannel.invokeMethod('status').then((value) => value.toString()); } From 358418662eebb27f2665f327bc2bcfcd6c2d07d0 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Fri, 4 Apr 2025 19:51:19 +0700 Subject: [PATCH 51/77] android --- android/gradlew | 185 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100755 android/gradlew diff --git a/android/gradlew b/android/gradlew new file mode 100755 index 00000000..4f906e0c --- /dev/null +++ b/android/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# 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 +# +# https://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. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" From 545774cb40f18e17050aa8855f153c14b213179b Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Tue, 8 Apr 2025 10:01:03 +0700 Subject: [PATCH 52/77] Update README.md --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 99781391..c2f79878 100644 --- a/README.md +++ b/README.md @@ -72,11 +72,8 @@ flutter pub add vpnclient_engine_flutter ); // Ping a server - VPNclientEngine.pingServer(subscriptionIndex: 0, index: 1); - - VPNclientEngine.onPingResult.listen((result) { - print("Ping result: ${result.latencyInMs} ms"); - }); + final result = await VPNclientEngine.ping(subscriptionIndex: 0, index: 1); + print("Ping result: ${result.latencyInMs} ms"); await Future.delayed(Duration(seconds: 10)); From 2e4ead4476d1173487a3bb6f847c0cbfe382de66 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Tue, 8 Apr 2025 10:12:04 +0700 Subject: [PATCH 53/77] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c2f79878..8457dc7c 100644 --- a/README.md +++ b/README.md @@ -72,8 +72,10 @@ flutter pub add vpnclient_engine_flutter ); // Ping a server - final result = await VPNclientEngine.ping(subscriptionIndex: 0, index: 1); - print("Ping result: ${result.latencyInMs} ms"); + VPNclientEngine.ping(subscriptionIndex: 0, index: 1); + VPNclientEngine.onPingResult.listen((result) { + print("Ping: sub=${result.subscriptionIndex}, server=${result.serverIndex}, latency=${result.latencyInMs} ms"); + }); await Future.delayed(Duration(seconds: 10)); From 5ab6267818bc82cbf7f05c7a98bcb3f06599de1e Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Tue, 8 Apr 2025 14:03:47 +0700 Subject: [PATCH 54/77] Update README.md --- README.md | 59 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 8457dc7c..263b74f4 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,50 @@ -# 🚀 VPN Client Engine Flutter +# VPN Client Engine Flutter (Flutter Plugin) -## 🌍 Overview +**VPNclient Engine Flutter** is a Flutter plugin that provides a high-level API for controlling VPN connections from a Dart/Flutter app. It wraps the native [VPNclient Engine](#vpnclient-engine) library, allowing Flutter developers to integrate advanced VPN functionality into their apps with ease. With this plugin, you can start and stop VPN connections, switch servers, apply routing rules, and listen to connection events using simple Dart calls, without worrying about platform-specific implementation details. -VPN Client Engine Flutter is a Flutter Plugin for managing VPN connections with an intuitive API. It provides seamless integration with various platforms, allowing developers to control VPN connections efficiently. -![VPN Client Engine](https://raw.githubusercontent.com/VPNclient/.github/refs/heads/main/assets/vpnclient_scheme2.png) +## 🚀 Key Features +- **Seamless Integration:** The plugin is built to be cross-platform. It uses platform-specific binaries and code (written in C++ and integrated via Dart FFI) to interface with iOS, Android, Windows, macOS, and Linux, but exposes a unified Dart interface. This means you write your VPN logic once in Dart and it works everywhere Flutter does. +- **Intuitive API:** The API is designed with Flutter developers in mind. You can initialize the VPN engine, connect to a server, and listen for status changes using streams and futures. The plugin handles asynchronous calls and background threads internally. +- **Powered by VPNclient Engine:** Under the hood, this plugin utilizes the native VPNclient Engine, which supports multiple protocols (Xray/VMess/VLESS/Reality, WireGuard, OpenVPN, etc.) and drivers. The plugin abstracts the complexity, so you can, for example, simply call `connect()` and the engine will take care of setting up a tun interface or proxy as needed on that platform. +## 🖥️ Supported Platforms +- ✅ iOS (15.0+) +- ✅ Android (5.0+) +- ✅ macOS (Intel/Silicon) +- ✅ Windows +- ✅ Unix (Linux/Debian/Ubuntu) -## 🏗️ Architecture Overview + +Each platform uses the native capabilities provided by VPNclient Engine: +- On Android and iOS, the engine uses the system VPN APIs (VpnService, NetworkExtension) to create a VPN tunnel. +- On desktop, it can either create a TUN interface or run as a local proxy (depending on driver configuration). + +## 📦 Architecture + +Internally, the plugin acts as a bridge between Dart and the native engine. It uses a combination of Dart FFI (Foreign Function Interface) and platform-specific setup to communicate with the native library. The basic flow: ```mermaid -graph TD - style A fill:#f9d5e5 - A[VPNclient App] --> B[VPNclient Engine Flutter Plugin] - style B fill:#eeac99 - B --> C[VPNclient Engine] - C --> D[iOS] - C --> E[Android] - C --> F[macOS] - C --> G[Windows] - C --> H[Linux] +flowchart LR + subgraph subGraph0["Flutter Application"] + UI@{ label: "Your Flutter App (Flutter UI)" } + end + subgraph subGraph1["Flutter Plugin"] + Plugin["VPNclient Engine Flutter"] + end + subgraph subGraph2["Native Core"] + Core["VPNclient Engine Library"] + end + UI --> Plugin + Plugin --> Core + Core --> iOS["iOS"] & Android["Android"] & macOS["macOS"] & Windows["Windows"] & Linux["Linux"] + + UI@{ shape: rect} ``` -### ✅ Supported Platforms -- iOS 15+ (iPhone, iPad, MacOS M) -- Android -- 🏗️ MacOS Intel -- 🏗️ Windows -- 🏗️ Ubuntu +*Diagram: Your Flutter app calls into the VPNclient Engine Flutter plugin (Dart layer). The plugin calls the native VPNclient Engine, which interfaces with the OS networking on each platform.* + +From a developer perspective, you primarily interact with the **Dart API** provided by this plugin. The plugin takes care of invoking native methods and ensures asynchronous operations (like connecting or disconnecting) do not block the UI thread. ## 📥 Getting Started From e7c2e0de156cc548c4867294736eb515394d02a7 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Tue, 8 Apr 2025 14:05:04 +0700 Subject: [PATCH 55/77] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 263b74f4..ad34a863 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # VPN Client Engine Flutter (Flutter Plugin) -**VPNclient Engine Flutter** is a Flutter plugin that provides a high-level API for controlling VPN connections from a Dart/Flutter app. It wraps the native [VPNclient Engine](#vpnclient-engine) library, allowing Flutter developers to integrate advanced VPN functionality into their apps with ease. With this plugin, you can start and stop VPN connections, switch servers, apply routing rules, and listen to connection events using simple Dart calls, without worrying about platform-specific implementation details. +**VPNclient Engine Flutter** is a Flutter plugin that provides a high-level API for controlling VPN connections from a Dart/Flutter app. It wraps the native [VPNclient Engine](https://github.com/VPNclient/VPNclient-engine) library, allowing Flutter developers to integrate advanced VPN functionality into their apps with ease. With this plugin, you can start and stop VPN connections, switch servers, apply routing rules, and listen to connection events using simple Dart calls, without worrying about platform-specific implementation details. ## 🚀 Key Features - **Seamless Integration:** The plugin is built to be cross-platform. It uses platform-specific binaries and code (written in C++ and integrated via Dart FFI) to interface with iOS, Android, Windows, macOS, and Linux, but exposes a unified Dart interface. This means you write your VPN logic once in Dart and it works everywhere Flutter does. From 738e82ccfbc5473f0b2b3ff70b25e96a1a0308f3 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Tue, 8 Apr 2025 14:07:16 +0700 Subject: [PATCH 56/77] Update README.md --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index ad34a863..40440b99 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,25 @@ flowchart LR From a developer perspective, you primarily interact with the **Dart API** provided by this plugin. The plugin takes care of invoking native methods and ensures asynchronous operations (like connecting or disconnecting) do not block the UI thread. +## Platform Setup + +Because this plugin sets up actual VPN tunnels, a few platform-specific configurations are required: + +- **Android:** No special code is needed (the plugin internally uses Android's `VpnService`), but you must declare the following in your app’s AndroidManifest.xml: + ```xml + + + ``` + These ensure the app can open network connections and run a foreground service for the VPN. The plugin will handle launching the VPN service. (Note: You do **not** need to declare `BIND_VPN_SERVICE` in the manifest; the plugin uses the VpnService class which has that intent filter built-in.) + +- **iOS:** Enable the Personal VPN capability for your app target in Xcode (this adds the necessary entitlements). Additionally, in your Info.plist, you might need to include a usage description for VPN if required. The VPNclient Engine uses a custom bundle identifier for its network extension (`click.vpnclient.engine` with an `allow-vpn` key), but if you integrate via this plugin, typically enabling the capability is sufficient. When you run the app the first time, iOS will prompt the user to allow the VPN configuration. + +- **Windows:** The app should be run with administrator privileges to create a TUN interface via WinTun. Alternatively, have the WinTun driver installed (which is usually present if WireGuard is installed on the system). No manifest changes are needed, but the user might need to approve driver installation if not already present. + +- **macOS/Linux:** The application will likely require root privileges or proper entitlements to create a tunnel (on macOS, Network Extension needs to be signed with the correct entitlements; on Linux, either run with root or configure `/dev/net/tun` access for the user). For development on macOS, you can enable "Network Extensions" in the sandbox if running unsigned. + +Once the above are set up, you can use the plugin in your Dart code as shown below. + ## 📥 Getting Started To start using VPN Client Engine Flutter, ensure you have Flutter installed and set up your project accordingly. From 1429d0d488afacf040981f0188ced865fa63d8ae Mon Sep 17 00:00:00 2001 From: ginterloper Date: Wed, 9 Apr 2025 21:21:38 +0300 Subject: [PATCH 57/77] launch fix --- ios/Classes/VpnclientEngineFlutterPlugin.swift | 4 ++-- ios/vpnclient_engine_flutter.podspec | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 ios/vpnclient_engine_flutter.podspec diff --git a/ios/Classes/VpnclientEngineFlutterPlugin.swift b/ios/Classes/VpnclientEngineFlutterPlugin.swift index 9c550024..36c0cfd8 100644 --- a/ios/Classes/VpnclientEngineFlutterPlugin.swift +++ b/ios/Classes/VpnclientEngineFlutterPlugin.swift @@ -1,7 +1,7 @@ import Flutter import UIKit -import VPNclientTunnel -import VPNclientEngine +//import VPNclientTunnel +//import VPNclientEngine public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { diff --git a/ios/vpnclient_engine_flutter.podspec b/ios/vpnclient_engine_flutter.podspec new file mode 100644 index 00000000..e7bfd73c --- /dev/null +++ b/ios/vpnclient_engine_flutter.podspec @@ -0,0 +1,12 @@ +Pod::Spec.new do |s| + s.name = 'vpnclient_engine_flutter' + s.version = '0.0.1' + s.summary = 'A Flutter plugin for VPN client engine.' + s.homepage = 'https://example.com' + s.license = { :type => 'MIT' } + s.author = { 'Your Name' => 'your.email@example.com' } + s.source = { :path => '.' } + s.ios.deployment_target = '11.0' + s.dependency 'Flutter' + s.source_files = 'Classes/**/*' +end \ No newline at end of file From cdf297499c6869fbe9e13565c8b2107cf87f28e0 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Mon, 21 Apr 2025 10:11:17 +0700 Subject: [PATCH 58/77] android --- android/build.gradle | 2 +- android/settings.gradle | 8 ++++---- .../VpnclientEngineFlutterPlugin.kt | 11 ++++++----- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 834b57df..13cdc4a6 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -52,7 +52,7 @@ android { dependencies { testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.mockito:mockito-core:5.0.0") - implementation project(':vpnclient-engine-android') + //implementation project(':vpnclient-engine-android') //implementation 'com.github.VPNclient:VPNclient-engine:android-v0.1.1' } diff --git a/android/settings.gradle b/android/settings.gradle index 8dbb3524..a6ee190a 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -3,10 +3,10 @@ rootProject.name = 'vpnclient_engine_flutter' //include ':vpnclient_engine_android' //project(':vpnclient_engine_android').projectDir = new File('../../VPNclient_engine_android') -def targetPath = new File(settingsDir, '../../VPNclient-engine/android') -println "[DEBUG55] Expected project dir: ${targetPath.absolutePath}" +//def targetPath = new File(settingsDir, '../../VPNclient-engine/android') +//println "[DEBUG55] Expected project dir: ${targetPath.absolutePath}" -include ':vpnclient-engine-android' -project(':vpnclient-engine-android').projectDir = new File('../../../VPNclient-engine/android') +//include ':vpnclient-engine-android' +//project(':vpnclient-engine-android').projectDir = new File('../../../VPNclient-engine/android') //project(':vpnclient-engine-android').projectDir = new File('/Users/anton/proj/VPNclient/VPNclient-engine/android') \ No newline at end of file diff --git a/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt b/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt index 6e383e65..dc7ea19a 100644 --- a/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt +++ b/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt @@ -6,7 +6,7 @@ import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result -import click.vpnclient.engine.VPNManager +//import click.vpnclient.engine.VPNManager /** VpnclientEngineFlutterPlugin */ class VpnclientEngineFlutterPlugin: FlutterPlugin, MethodCallHandler { @@ -25,14 +25,15 @@ class VpnclientEngineFlutterPlugin: FlutterPlugin, MethodCallHandler { when (call.method) { "startVPN" -> { val config = call.argument("config") ?: return result.error("NO_CONFIG", "Missing config", null) - val success = VPNManager.startVPN(context, config) - result.success(success) + //val success = VPNManager.startVPN(context, config) + //result.success(success) + result.success(null) } "stopVPN" -> { - VPNManager.stopVPN() + //VPNManager.stopVPN() result.success(null) } - "status" -> result.success(VPNManager.status()) + //"status" -> result.success(VPNManager.status()) } if (call.method == "getPlatformVersion") { result.success("Android ${android.os.Build.VERSION.RELEASE}") From f71bd7ec135ecb8e6aed6964510777f2be042af4 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Fri, 25 Apr 2025 10:46:42 +0700 Subject: [PATCH 59/77] server --- example/pubspec.lock | 8 +++++ lib/vpnclient_engine/engine.dart | 59 ++++++++++++++++++++++++++++++++ pubspec.lock | 8 +++++ pubspec.yaml | 1 + 4 files changed, 76 insertions(+) diff --git a/example/pubspec.lock b/example/pubspec.lock index 10ad335c..4e4e96d6 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -96,6 +96,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_v2ray: + dependency: transitive + description: + name: flutter_v2ray + sha256: "09dce3b4b58ea6a4220409d948a4d92a6eb3416184e6512ec0f5773fd48e5ab0" + url: "https://pub.dev" + source: hosted + version: "1.0.10" flutter_web_plugins: dependency: transitive description: flutter diff --git a/lib/vpnclient_engine/engine.dart b/lib/vpnclient_engine/engine.dart index 517c2914..641f7fcf 100644 --- a/lib/vpnclient_engine/engine.dart +++ b/lib/vpnclient_engine/engine.dart @@ -4,6 +4,19 @@ import 'package:http/http.dart' as http; import 'package:dart_ping/dart_ping.dart'; import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter_platform_interface.dart'; +/// +import 'package:flutter_v2ray/flutter_v2ray.dart'; + +/// + +final FlutterV2ray flutterV2ray = FlutterV2ray( + onStatusChanged: (status) { + // do something + }, +); + +/// + enum ConnectionStatus { connecting, connected, disconnected, error } class SessionStatistics { @@ -125,6 +138,52 @@ class VPNclientEngine { static Stream get onPingResult => _pingResultController.stream; static Future connect({required int subscriptionIndex, required int serverIndex}) async { + + + /// + // You must initialize V2Ray before using it. + print('Initializing...'); + await flutterV2ray.initializeV2Ray(); + + // v2ray share link like vmess://, vless://, ... + String link = + //"vless://c61daf3e-83ff-424f-a4ff-5bfcb46f0b30@5.35.98.91:8443?encryption=none&flow=&security=reality&sni=yandex.ru&fp=chrome&pbk=rLCmXWNVoRBiknloDUsbNS5ONjiI70v-BWQpWq0HCQ0&sid=108108108108#%F0%9F%87%B7%F0%9F%87%BA+%F0%9F%99%8F+Russia+%231"; + "vless://c61daf3e-83ff-424f-a4ff-5bfcb46f0b30@45.77.190.146:8443?encryption=none&flow=&security=reality&sni=www.gstatic.com&fp=chrome&pbk=rLCmXWNVoRBiknloDUsbNS5ONjiI70v-BWQpWq0HCQ0&sid=108108108108#%F0%9F%87%BA%F0%9F%87%B8+%F0%9F%99%8F+USA+%231"; + V2RayURL parser = FlutterV2ray.parseFromURL(link); + + // Get Server Delay + //print( + // '${flutterV2ray.getServerDelay(config: parser.getFullConfiguration())}ms', + // name: 'ServerDelay', + //); + + // Permission is not required if you using proxy only + print('Premissions...'); + if (await flutterV2ray.requestPermission()) { + print('Starting...'); + flutterV2ray.startV2Ray( + remark: parser.remark, + // The use of parser.getFullConfiguration() is not mandatory, + // and you can enter the desired V2Ray configuration in JSON format + config: parser.getFullConfiguration(), + blockedApps: null, + bypassSubnets: null, + proxyOnly: false, + ); + print('Started'); + } + + // Disconnect + ///flutterV2ray.stopV2Ray(); + + /// + + //TODO:move to right place + + + + + print(await VpnclientEngineFlutterPlatform.instance.getPlatformVersion()); print('Connecting to subscription $subscriptionIndex, server $serverIndex...'); diff --git a/pubspec.lock b/pubspec.lock index ef1ef3ad..f05ccddf 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -83,6 +83,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_v2ray: + dependency: "direct main" + description: + name: flutter_v2ray + sha256: "09dce3b4b58ea6a4220409d948a4d92a6eb3416184e6512ec0f5773fd48e5ab0" + url: "https://pub.dev" + source: hosted + version: "1.0.10" flutter_web_plugins: dependency: "direct main" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 47b2cf0d..6c48b552 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,6 +17,7 @@ dependencies: cupertino_icons: ^1.0.8 dart_ping: ^9.0.1 http: ^1.3.0 + flutter_v2ray: ^1.0.10 platforms: android: From 6c32b731071b721bc4d599c40f89e2918e8e4f24 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Mon, 28 Apr 2025 01:56:50 +0700 Subject: [PATCH 60/77] android --- .../VpnclientEngineFlutterPlugin.kt | 82 +++++++++++-------- ...nclient_engine_flutter_method_channel.dart | 37 +++++---- ...ent_engine_flutter_platform_interface.dart | 16 ++-- 3 files changed, 79 insertions(+), 56 deletions(-) diff --git a/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt b/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt index dc7ea19a..6bd8186a 100644 --- a/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt +++ b/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt @@ -1,48 +1,66 @@ package click.vpnclient.engine.flutter.vpnclient_engine_flutter +import android.content.Context +import click.vpnclient.engine.VPNManager import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result -//import click.vpnclient.engine.VPNManager - -/** VpnclientEngineFlutterPlugin */ +/** + * VpnclientEngineFlutterPlugin + * This class handles the communication between Flutter and native Android code + * for managing VPN connections. + */ class VpnclientEngineFlutterPlugin: FlutterPlugin, MethodCallHandler { - /// The MethodChannel that will the communication between Flutter and native Android - /// - /// This local reference serves to register the plugin with the Flutter Engine and unregister it - /// when the Flutter Engine is detached from the Activity - private lateinit var channel : MethodChannel + /// The MethodChannel that will the communication between Flutter and native Android + private lateinit var channel : MethodChannel + private lateinit var context: Context - override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - channel = MethodChannel(flutterPluginBinding.binaryMessenger, "vpnclient_engine_flutter") - channel.setMethodCallHandler(this) - } + override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "vpnclient_engine_flutter") + channel.setMethodCallHandler(this) + context = flutterPluginBinding.applicationContext + } - override fun onMethodCall(call: MethodCall, result: Result) { - when (call.method) { - "startVPN" -> { - val config = call.argument("config") ?: return result.error("NO_CONFIG", "Missing config", null) - //val success = VPNManager.startVPN(context, config) - //result.success(success) - result.success(null) - } - "stopVPN" -> { - //VPNManager.stopVPN() - result.success(null) + override fun onMethodCall(call: MethodCall, result: Result) { + when (call.method) { + "startVPN" -> startVPN(call, result) + "stopVPN" -> stopVPN(result) + "status" -> getStatus(result) + "getPlatformVersion" -> getPlatformVersion(result) + else -> result.notImplemented() } - //"status" -> result.success(VPNManager.status()) } - if (call.method == "getPlatformVersion") { - result.success("Android ${android.os.Build.VERSION.RELEASE}") - } else { - result.notImplemented() + + /** + * Start the VPN connection using the provided configuration. + * @param call MethodCall containing the configuration. + * @param result Result to send the success or error back to Flutter. + */ + private fun startVPN(call: MethodCall, result: Result) { + val config = call.argument("config") ?: return result.error("NO_CONFIG", "Missing config", null) + val success = VPNManager.startVPN(context, config) + result.success(success) } - } - override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { - channel.setMethodCallHandler(null) - } + /** + * Stop the VPN connection. + * @param result Result to send the success back to Flutter. + */ + private fun stopVPN(result: Result) { + VPNManager.stopVPN() + result.success(true) + } + private fun getStatus(result: Result) { + result.success(VPNManager.status()) + } + private fun getPlatformVersion(result: Result) { + result.success("Android ${android.os.Build.VERSION.RELEASE}") + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + } } diff --git a/lib/vpnclient_engine_flutter_method_channel.dart b/lib/vpnclient_engine_flutter_method_channel.dart index 7d27d7c0..1f98c844 100644 --- a/lib/vpnclient_engine_flutter_method_channel.dart +++ b/lib/vpnclient_engine_flutter_method_channel.dart @@ -1,25 +1,34 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; - import 'vpnclient_engine_flutter_platform_interface.dart'; -/// An implementation of [VpnclientEngineFlutterPlatform] that uses method channels. -class MethodChannelVpnclientEngineFlutter extends VpnclientEngineFlutterPlatform { - /// The method channel used to interact with the native platform. +class VpnclientEngineFlutterMethodChannel + extends VpnclientEngineFlutterPlatform { @visibleForTesting final methodChannel = const MethodChannel('vpnclient_engine_flutter'); @override - Future getPlatformVersion() async { - final version = await methodChannel.invokeMethod('getPlatformVersion'); - return version; + Future startVPN({required String configPath}) async { + try { + await methodChannel.invokeMethod('startVPN', {"config": configPath}); + } catch (e) { + debugPrint('Error starting VPN: $e'); + rethrow; + } } - Future startVPN(String configPath) => - methodChannel.invokeMethod('startVPN', {"config": configPath}); - - Future stopVPN() => methodChannel.invokeMethod('stopVPN'); - - Future getStatus() => - methodChannel.invokeMethod('status').then((value) => value.toString()); + @override + Future stopVPN() async { + try { + await methodChannel.invokeMethod('stopVPN'); + } catch (e) { + debugPrint('Error stopping VPN: $e'); + rethrow; + } + } + @override + Future checkVPNStatus() async { + final result = await methodChannel.invokeMethod('status'); + return result.toString(); + } } diff --git a/lib/vpnclient_engine_flutter_platform_interface.dart b/lib/vpnclient_engine_flutter_platform_interface.dart index 7626419d..63ea73aa 100644 --- a/lib/vpnclient_engine_flutter_platform_interface.dart +++ b/lib/vpnclient_engine_flutter_platform_interface.dart @@ -2,29 +2,25 @@ import 'package:plugin_platform_interface/plugin_platform_interface.dart'; import 'vpnclient_engine_flutter_method_channel.dart'; + abstract class VpnclientEngineFlutterPlatform extends PlatformInterface { - /// Constructs a VpnclientEngineFlutterPlatform. VpnclientEngineFlutterPlatform() : super(token: _token); static final Object _token = Object(); - static VpnclientEngineFlutterPlatform _instance = MethodChannelVpnclientEngineFlutter(); + static VpnclientEngineFlutterPlatform _instance = + MethodChannelVpnclientEngineFlutter(); - /// The default instance of [VpnclientEngineFlutterPlatform] to use. - /// - /// Defaults to [MethodChannelVpnclientEngineFlutter]. static VpnclientEngineFlutterPlatform get instance => _instance; - /// Platform-specific implementations should set this with their own - /// platform-specific class that extends [VpnclientEngineFlutterPlatform] when - /// they register themselves. static set instance(VpnclientEngineFlutterPlatform instance) { PlatformInterface.verifyToken(instance, _token); _instance = instance; } - Future getPlatformVersion() { - throw UnimplementedError('platformVersion() has not been implemented.'); + Future startVPN(String config) => throw UnimplementedError('startVPN() has not been implemented.'); + Future stopVPN() => throw UnimplementedError('stopVPN() has not been implemented.'); + Future checkVPNStatus() => throw UnimplementedError('checkVPNStatus() has not been implemented.'); } /* From 4d186047ed636022cf01e24fd23f8606240e973f Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Mon, 28 Apr 2025 02:15:21 +0700 Subject: [PATCH 61/77] ios --- .../VpnclientEngineFlutterPlugin.swift | 60 ++++++++++++++----- ios/vpnclient_engine_flutter.podspec | 1 + .../VpnclientEngineFlutterPlugin.swift | 50 ++++++++++++---- macos/vpnclient_engine_flutter.podspec | 1 + 4 files changed, 84 insertions(+), 28 deletions(-) diff --git a/ios/Classes/VpnclientEngineFlutterPlugin.swift b/ios/Classes/VpnclientEngineFlutterPlugin.swift index 36c0cfd8..cf0722ab 100644 --- a/ios/Classes/VpnclientEngineFlutterPlugin.swift +++ b/ios/Classes/VpnclientEngineFlutterPlugin.swift @@ -1,21 +1,51 @@ import Flutter import UIKit -//import VPNclientTunnel -//import VPNclientEngine +import NetworkExtension +/// Plugin class to handle VPN connections in the Flutter app. public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "vpnclient_engine_flutter", binaryMessenger: registrar.messenger()) - let instance = VpnclientEngineFlutterPlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case "getPlatformVersion": - result("iOS " + UIDevice.current.systemVersion) - default: - result(FlutterMethodNotImplemented) + private var tunnelProvider: NETunnelProviderManager? + + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "vpnclient_engine_flutter", binaryMessenger: registrar.messenger()) + let instance = VpnclientEngineFlutterPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "startVPN": + guard let args = call.arguments as? [String: Any], + let config = args["config"] as? String else { + result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing or invalid config", details: nil)) + return + } + startVPN(withConfig: config, result: result) + case "stopVPN": + stopVPN(result: result) + case "checkVPNStatus": + checkVPNStatus(result: result) + default: + result(FlutterMethodNotImplemented) + } + } + + /// Starts the VPN connection. + private func startVPN(withConfig config: String, result: @escaping FlutterResult) { + // Implement the logic to start the VPN connection using PacketTunnelProvider + result(nil) + } + + /// Stops the VPN connection. + private func stopVPN(result: @escaping FlutterResult) { + // Implement the logic to stop the VPN connection + result(nil) + } + + /// Checks the current status of the VPN connection. + private func checkVPNStatus(result: @escaping FlutterResult) { + // Implement the logic to check the VPN connection status + result(nil) } - } } + diff --git a/ios/vpnclient_engine_flutter.podspec b/ios/vpnclient_engine_flutter.podspec index e7bfd73c..fb92c34c 100644 --- a/ios/vpnclient_engine_flutter.podspec +++ b/ios/vpnclient_engine_flutter.podspec @@ -6,6 +6,7 @@ Pod::Spec.new do |s| s.license = { :type => 'MIT' } s.author = { 'Your Name' => 'your.email@example.com' } s.source = { :path => '.' } + s.dependency 'PacketTunnelProvider' s.ios.deployment_target = '11.0' s.dependency 'Flutter' s.source_files = 'Classes/**/*' diff --git a/macos/Classes/VpnclientEngineFlutterPlugin.swift b/macos/Classes/VpnclientEngineFlutterPlugin.swift index dc7adf4d..69e6f5c9 100644 --- a/macos/Classes/VpnclientEngineFlutterPlugin.swift +++ b/macos/Classes/VpnclientEngineFlutterPlugin.swift @@ -1,20 +1,44 @@ import Cocoa import FlutterMacOS -// import VPNclientEngineIOS +import NetworkExtension +/// Plugin for managing VPN connections on macOS. public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "vpnclient_engine_flutter", binaryMessenger: registrar.messenger) - let instance = VpnclientEngineFlutterPlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } + private var manager: NEVPNManager? + + /// Registers the plugin with the Flutter engine. + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "vpnclient_engine_flutter", binaryMessenger: registrar.messenger) + let instance = VpnclientEngineFlutterPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + /// Handles method calls from Flutter. + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "startVPN": + guard let config = call.arguments as? String else { + return result(FlutterError(code: "INVALID_ARGUMENTS", message: "Config is missing", details: nil)) + } + startVPN(config: config, result: result) + case "stopVPN": + stopVPN(result: result) + case "checkVPNStatus": + checkVPNStatus(result: result) + default: + result(FlutterMethodNotImplemented) + } + } - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case "getPlatformVersion": - result("macOS " + ProcessInfo.processInfo.operatingSystemVersionString) - default: - result(FlutterMethodNotImplemented) + private func startVPN(config: String, result: @escaping FlutterResult) { + // TODO: Implement startVPN logic + } + + private func stopVPN(result: @escaping FlutterResult) { + // TODO: Implement stopVPN logic + } + + private func checkVPNStatus(result: @escaping FlutterResult) { + // TODO: Implement checkVPNStatus logic } - } } diff --git a/macos/vpnclient_engine_flutter.podspec b/macos/vpnclient_engine_flutter.podspec index fd4297cb..ef1544de 100644 --- a/macos/vpnclient_engine_flutter.podspec +++ b/macos/vpnclient_engine_flutter.podspec @@ -23,6 +23,7 @@ VPNclient Engine Flutter plugin project. # s.resource_bundles = {'vpnclient_engine_flutter_privacy' => ['Resources/PrivacyInfo.xcprivacy']} s.dependency 'FlutterMacOS' + s.dependency 'PacketTunnelProvider' #s.dependency 'VPNclientEngineIOS', :path => '../../VPNclient-engine-ios' s.platform = :osx, '10.11' From 73a18b07a36b3d82b4497c3247b5d72859c2128d Mon Sep 17 00:00:00 2001 From: "anton.v.dodonov" Date: Sun, 27 Apr 2025 19:35:48 +0000 Subject: [PATCH 62/77] singbox --- .vscode/settings.json | 4 + .../VpnclientEngineFlutterPlugin.kt | 124 ++++++++++++++++-- .../VpnclientEngineFlutterPlugin.swift | 60 ++++++++- .../VpnclientEngineFlutterPlugin.swift | 87 +++++++++++- windows/vpnclient_engine_flutter_plugin.cpp | 58 ++++++++ 5 files changed, 309 insertions(+), 24 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..03adc8d2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "IDX.aI.enableInlineCompletion": true, + "IDX.aI.enableCodebaseIndexing": true +} \ No newline at end of file diff --git a/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt b/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt index 6bd8186a..9ced071f 100644 --- a/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt +++ b/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt @@ -1,22 +1,32 @@ package click.vpnclient.engine.flutter.vpnclient_engine_flutter import android.content.Context -import click.vpnclient.engine.VPNManager import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result +import go.Seq +import android.util.Log + + + + + + /** * VpnclientEngineFlutterPlugin * This class handles the communication between Flutter and native Android code * for managing VPN connections. */ -class VpnclientEngineFlutterPlugin: FlutterPlugin, MethodCallHandler { - /// The MethodChannel that will the communication between Flutter and native Android - private lateinit var channel : MethodChannel +class VpnclientEngineFlutterPlugin : FlutterPlugin, MethodCallHandler { + /// The MethodChannel that will handle the communication between Flutter and native Android + private lateinit var channel: MethodChannel private lateinit var context: Context + private val TAG = "VpnclientEngineFlutterPlugin" + + private var singboxCore: SingBoxCore? = null override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { channel = MethodChannel(flutterPluginBinding.binaryMessenger, "vpnclient_engine_flutter") @@ -27,13 +37,25 @@ class VpnclientEngineFlutterPlugin: FlutterPlugin, MethodCallHandler { override fun onMethodCall(call: MethodCall, result: Result) { when (call.method) { "startVPN" -> startVPN(call, result) - "stopVPN" -> stopVPN(result) - "status" -> getStatus(result) + "stopVPN" -> stopVPN(call, result) + "status" -> getStatus(call, result) "getPlatformVersion" -> getPlatformVersion(result) else -> result.notImplemented() } } + /** + * Initialize sing-box. + * @param config config for sing-box + */ + private fun initSingbox(config: String) { + try { + singboxCore = SingBoxCore(config) + } catch (e: Exception) { + Log.e(TAG, "Failed to initialize sing-box", e) + } + } + /** * Start the VPN connection using the provided configuration. * @param call MethodCall containing the configuration. @@ -41,20 +63,43 @@ class VpnclientEngineFlutterPlugin: FlutterPlugin, MethodCallHandler { */ private fun startVPN(call: MethodCall, result: Result) { val config = call.argument("config") ?: return result.error("NO_CONFIG", "Missing config", null) - val success = VPNManager.startVPN(context, config) - result.success(success) + try { + initSingbox(config) + singboxCore?.start() + result.success(true) + } catch (e: Exception) { + Log.e(TAG, "Failed to start sing-box", e) + result.error("START_ERROR", "Failed to start sing-box", e.message) + } } /** * Stop the VPN connection. * @param result Result to send the success back to Flutter. */ - private fun stopVPN(result: Result) { - VPNManager.stopVPN() - result.success(true) + private fun stopVPN(call: MethodCall, result: Result) { + try { + singboxCore?.stop() + singboxCore = null // Release sing-box instance after stopping + result.success(true) + } catch (e: Exception) { + Log.e(TAG, "Failed to stop sing-box", e) + result.error("STOP_ERROR", "Failed to stop sing-box", e.message) + } } - private fun getStatus(result: Result) { - result.success(VPNManager.status()) + + private fun getStatus(call: MethodCall, result: Result) { + try { + val status = singboxCore?.getStatus() ?: "stopped" + result.success(status) + } catch (e: Exception) { + Log.e(TAG, "Failed to get sing-box status", e) + result.error( + "STATUS_ERROR", + "Failed to get sing-box status", + e.message + ) + } } private fun getPlatformVersion(result: Result) { result.success("Android ${android.os.Build.VERSION.RELEASE}") @@ -64,3 +109,56 @@ class VpnclientEngineFlutterPlugin: FlutterPlugin, MethodCallHandler { channel.setMethodCallHandler(null) } } + + + +class SingBoxCore(config: String) { + private val TAG = "SingBoxCore" + + init { + try { + // Initialize the Go runtime + Seq.setContext(null) + // Load the sing-box configuration from the provided string + Singbox.setConfig(config) + } catch (e: Exception) { + Log.e(TAG, "Error initializing SingBox: ${e.message}") + throw e + } + } + + /** + * Start the SingBox core. + * @throws Exception if there is an error starting SingBox. + */ + fun start() { + try { + // Start the SingBox core + val err: String? = Singbox.start() + if (err != null && err.isNotEmpty()) { + throw Exception("Error starting SingBox: $err") + } + } catch (e: Exception) { + Log.e(TAG, "Error starting SingBox: ${e.message}") + throw e + } + } + + /** + * Stop the SingBox core. + * @throws Exception if there is an error stopping SingBox. + */ + fun stop() { + try { + // Stop the SingBox core + Singbox.stop() + } catch (e: Exception) { + Log.e(TAG, "Error stopping SingBox: ${e.message}") + throw e + } + } + + fun getStatus(): String { + return Singbox.getStatus() + } +} diff --git a/ios/Classes/VpnclientEngineFlutterPlugin.swift b/ios/Classes/VpnclientEngineFlutterPlugin.swift index cf0722ab..46a9a326 100644 --- a/ios/Classes/VpnclientEngineFlutterPlugin.swift +++ b/ios/Classes/VpnclientEngineFlutterPlugin.swift @@ -1,6 +1,7 @@ import Flutter import UIKit import NetworkExtension +import singbox /// Plugin class to handle VPN connections in the Flutter app. public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { @@ -32,20 +33,65 @@ public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { /// Starts the VPN connection. private func startVPN(withConfig config: String, result: @escaping FlutterResult) { - // Implement the logic to start the VPN connection using PacketTunnelProvider - result(nil) + do { + let singboxConfig = try SingboxConfig(json: config) + let builder = SingboxBuilder(config: singboxConfig) + + try builder.build() + + DispatchQueue.global(qos: .userInitiated).async { + do { + try builder.start() + + DispatchQueue.main.async { + result(nil) + } + } catch { + DispatchQueue.main.async { + result(FlutterError(code: "VPN_START_FAILED", message: "Failed to start VPN: \(error.localizedDescription)", details: nil)) + } + } + } + } catch { + result(FlutterError(code: "CONFIG_ERROR", message: "Invalid Singbox config: \(error.localizedDescription)", details: nil)) + } } - + /// Stops the VPN connection. private func stopVPN(result: @escaping FlutterResult) { - // Implement the logic to stop the VPN connection - result(nil) + DispatchQueue.global(qos: .userInitiated).async { + do { + try Singbox.shared.stop() + DispatchQueue.main.async { + result(nil) + } + } catch { + DispatchQueue.main.async { + result(FlutterError(code: "VPN_STOP_FAILED", message: "Failed to stop VPN: \(error.localizedDescription)", details: nil)) + } + } + } } /// Checks the current status of the VPN connection. private func checkVPNStatus(result: @escaping FlutterResult) { - // Implement the logic to check the VPN connection status - result(nil) + if Singbox.shared.isRunning { + result(true) + } else { + result(false) + } + } +} + +private extension SingboxConfig { + convenience init(json: String) throws { + guard let data = json.data(using: .utf8) else { + throw NSError(domain: "SingboxConfig", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to convert JSON string to data"]) + } + self.init() + + try self.fromJSONData(data: data) } } + diff --git a/macos/Classes/VpnclientEngineFlutterPlugin.swift b/macos/Classes/VpnclientEngineFlutterPlugin.swift index 69e6f5c9..9dfd10a7 100644 --- a/macos/Classes/VpnclientEngineFlutterPlugin.swift +++ b/macos/Classes/VpnclientEngineFlutterPlugin.swift @@ -1,37 +1,116 @@ import Cocoa import FlutterMacOS import NetworkExtension +import os.log /// Plugin for managing VPN connections on macOS. public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { - private var manager: NEVPNManager? + private var vpnManager: NEVPNManager? + private var vpnConnection: NEVPNConnection? + private let logger = OSLog(subsystem: "click.vpnclient.engine.flutter", category: "VPNPlugin") + /// Registers the plugin with the Flutter engine. public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel(name: "vpnclient_engine_flutter", binaryMessenger: registrar.messenger) let instance = VpnclientEngineFlutterPlugin() registrar.addMethodCallDelegate(instance, channel: channel) + + // Load VPN Configuration + instance.loadVPNConfiguration { (success, error) in + if success { + os_log(.info, log: instance.logger, "VPN configuration loaded successfully") + } else if let error = error { + os_log(.error, log: instance.logger, "Failed to load VPN configuration: %@", error.localizedDescription) + } + } } /// Handles method calls from Flutter. public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + os_log(.debug, log: logger, "Method call received: %@", call.method) + switch call.method { case "startVPN": guard let config = call.arguments as? String else { return result(FlutterError(code: "INVALID_ARGUMENTS", message: "Config is missing", details: nil)) } - startVPN(config: config, result: result) + self.startVPN(config: config, result: result) case "stopVPN": stopVPN(result: result) case "checkVPNStatus": - checkVPNStatus(result: result) + checkVPNStatus { (status) in + result(status) + } + default: result(FlutterMethodNotImplemented) } } + private func loadVPNConfiguration(completion: @escaping (Bool, Error?) -> Void) { + NEVPNManager.loadAllFromPreferences { [weak self] (managers, error) in + guard let self = self else { return } + if let error = error { + os_log(.error, log: self.logger, "Error loading VPN preferences: %@", error.localizedDescription) + completion(false, error) + return + } + + if let managers = managers, !managers.isEmpty { + self.vpnManager = managers[0] + os_log(.info, log: self.logger, "Existing VPN configuration loaded.") + } else { + self.vpnManager = NEVPNManager.shared() + self.vpnManager?.localizedDescription = "SingBoxVPN" + self.vpnManager?.isEnabled = true + + let protocolConfiguration = NEVPNProtocolIKEv2() + protocolConfiguration.serverAddress = "" + self.vpnManager?.protocolConfiguration = protocolConfiguration + + os_log(.info, log: self.logger, "New VPN configuration created.") + + self.vpnManager?.saveToPreferences(completionHandler: { error in + if let error = error { + os_log(.error, log: self.logger, "Error saving VPN preferences: %@", error.localizedDescription) + completion(false, error) + } else { + os_log(.info, log: self.logger, "VPN preferences saved.") + completion(true, nil) + } + }) + } + } + } + private func startVPN(config: String, result: @escaping FlutterResult) { - // TODO: Implement startVPN logic + guard let vpnManager = self.vpnManager else { + result(FlutterError(code: "VPN_MANAGER_NOT_LOADED", message: "VPN manager is not loaded", details: nil)) + return + } + do { + try vpnManager.connection.startVPNTunnel(options: ["config": config]) + result(true) + + } catch { + os_log(.error, log: logger, "Failed to start VPN: %@", error.localizedDescription) + result(FlutterError(code: "VPN_START_FAILED", message: "Failed to start VPN", details: error.localizedDescription)) + } + } + + private func stopVPN(result: @escaping FlutterResult) { + vpnManager?.connection.stopVPNTunnel() + result(true) + } + + private func checkVPNStatus(completion: @escaping (String) -> Void) { + if let status = vpnManager?.connection.status { + completion(status.rawValue.description) + }else{ + completion("disconnected") + } + } } private func stopVPN(result: @escaping FlutterResult) { diff --git a/windows/vpnclient_engine_flutter_plugin.cpp b/windows/vpnclient_engine_flutter_plugin.cpp index 897d16a9..daf1eda7 100644 --- a/windows/vpnclient_engine_flutter_plugin.cpp +++ b/windows/vpnclient_engine_flutter_plugin.cpp @@ -1,5 +1,7 @@ #include "vpnclient_engine_flutter_plugin.h" +#include +#include // This must be included before many other Windows headers. #include @@ -13,6 +15,12 @@ #include #include +// Include sing-box related headers (replace with actual headers if different) +#include + + +#pragma comment(lib, "ws2_32.lib") + namespace vpnclient_engine_flutter { // static @@ -35,6 +43,45 @@ void VpnclientEngineFlutterPlugin::RegisterWithRegistrar( VpnclientEngineFlutterPlugin::VpnclientEngineFlutterPlugin() {} + +bool VpnclientEngineFlutterPlugin::startSingBox(std::string configPath) { + std::cout << "Start SingBox with config: " << configPath << std::endl; + + // Check if the configuration file exists + std::ifstream configFile(configPath); + if (!configFile.good()) { + std::cerr << "Error: Configuration file not found at " << configPath << std::endl; + return false; + } + configFile.close(); + + // Here you would typically use system() or CreateProcess() + // to launch the sing-box executable with the specified configuration. + // For this example, we'll just simulate launching it. + + std::string command = "sing-box run -c \"" + configPath + "\""; + std::cout << "Executing command: " << command << std::endl; + + STARTUPINFOA startupInfo; + PROCESS_INFORMATION processInfo; + + ZeroMemory(&startupInfo, sizeof(startupInfo)); + startupInfo.cb = sizeof(startupInfo); + ZeroMemory(&processInfo, sizeof(processInfo)); + + if (CreateProcessA(NULL, (LPSTR)command.c_str(), NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo)) { + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + std::cout << "sing-box launched successfully." << std::endl; + return true; + } else { + std::cerr << "Failed to start sing-box. Error code: " << GetLastError() << std::endl; + return false; + } + + return false; +} + VpnclientEngineFlutterPlugin::~VpnclientEngineFlutterPlugin() {} void VpnclientEngineFlutterPlugin::HandleMethodCall( @@ -51,6 +98,17 @@ void VpnclientEngineFlutterPlugin::HandleMethodCall( version_stream << "7"; } result->Success(flutter::EncodableValue(version_stream.str())); + } else if (method_call.method_name().compare("startSingBox") == 0) { + const auto* arguments = std::get_if(method_call.arguments()); + if (arguments) { + auto configPathIt = arguments->find(flutter::EncodableValue("configPath")); + if (configPathIt != arguments->end()) { + std::string configPath = std::get(configPathIt->second); + bool success = startSingBox(configPath); + result->Success(flutter::EncodableValue(success)); + return; + } + } } else { result->NotImplemented(); } From ac47d84d4874f7542dda82ae7ad8ce030a341a3a Mon Sep 17 00:00:00 2001 From: "anton.v.dodonov" Date: Sun, 27 Apr 2025 19:40:59 +0000 Subject: [PATCH 63/77] ref1 --- example/lib/main.dart | 250 ++++++++++++-- lib/vpnclient_engine/engine.dart | 323 +++++++++++++----- lib/vpnclient_engine_flutter.dart | 9 +- ...nclient_engine_flutter_method_channel.dart | 34 -- ...ent_engine_flutter_platform_interface.dart | 46 --- lib/vpnclient_engine_flutter_web.dart | 26 -- ...nt_engine_flutter_method_channel_test.dart | 27 -- test/vpnclient_engine_flutter_test.dart | 29 -- 8 files changed, 457 insertions(+), 287 deletions(-) delete mode 100644 lib/vpnclient_engine_flutter_method_channel.dart delete mode 100644 lib/vpnclient_engine_flutter_platform_interface.dart delete mode 100644 lib/vpnclient_engine_flutter_web.dart delete mode 100644 test/vpnclient_engine_flutter_method_channel_test.dart delete mode 100644 test/vpnclient_engine_flutter_test.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 3d7c3869..98a5fcfa 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,63 +1,251 @@ import 'package:flutter/material.dart'; import 'dart:async'; - -import 'package:flutter/services.dart'; import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter.dart'; +import 'package:vpnclient_engine_flutter/vpnclient_engine/engine.dart'; void main() { runApp(const MyApp()); } -class MyApp extends StatefulWidget { +class MyApp extends StatelessWidget { const MyApp({super.key}); @override - State createState() => _MyAppState(); + Widget build(BuildContext context) { + return MaterialApp( + title: 'VPN Client Engine Demo', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: const VPNClientDemo(), + ); + } } -class _MyAppState extends State { - String _platformVersion = 'Unknown'; - final _vpnclientEngineFlutterPlugin = VpnclientEngineFlutter(); +class VPNClientDemo extends StatefulWidget { + const VPNClientDemo({super.key}); + + @override + VPNClientDemoState createState() => VPNClientDemoState(); +} + +class VPNClientDemoState extends State { + ConnectionStatus _connectionStatus = ConnectionStatus.disconnected; + String _currentServer = 'Not Connected'; + String _pingResult = 'Not Pinging'; + List _routingRules = []; + List _servers = []; + SessionStatistics _sessionStatistics = SessionStatistics(dataInBytes: 0, dataOutBytes: 0); + + final TextEditingController _subscriptionUrlController = TextEditingController(); + final List _loadedSubscriptions = []; @override void initState() { super.initState(); - initPlatformState(); + VPNclientEngine.initialize(); + VPNclientEngine.onConnectionStatusChanged.listen(_updateConnectionStatus); + VPNclientEngine.onPingResult.listen(_updatePingResult); + VPNclientEngine.onRoutingRulesApplied.listen(_updateRoutingRules); + VPNclientEngine.onDataUsageUpdated.listen(_updateSessionStatistics); + } + + void _updateConnectionStatus(ConnectionStatus status) { + setState(() { + _connectionStatus = status; + }); + } + + void _updatePingResult(PingResult result) { + setState(() { + _pingResult = 'Ping: sub=${result.subscriptionIndex}, server=${result.serverIndex}, latency=${result.latencyInMs} ms'; + }); + } + + void _updateRoutingRules(List rules) { + setState(() { + _routingRules = rules; + }); + } + + void _updateSessionStatistics(SessionStatistics stats) { + setState(() { + _sessionStatistics = stats; + }); } - // Platform messages are asynchronous, so we initialize in an async method. - Future initPlatformState() async { - String platformVersion; - // Platform messages may fail, so we use a try/catch PlatformException. - // We also handle the message potentially returning null. - try { - platformVersion = - await _vpnclientEngineFlutterPlugin.getPlatformVersion() ?? 'Unknown platform version'; - } on PlatformException { - platformVersion = 'Failed to get platform version.'; - } - // If the widget was removed from the tree while the asynchronous platform - // message was in flight, we want to discard the reply rather than calling - // setState to update our non-existent appearance. - if (!mounted) return; + void _connectToServer() async { + await VPNclientEngine.connect(subscriptionIndex: 0, serverIndex: 0); + setState(() { + _currentServer = 'Connecting...'; + }); + } + void _disconnectFromServer() async { + await VPNclientEngine.disconnect(); setState(() { - _platformVersion = platformVersion; + _currentServer = 'Not Connected'; }); } + void _pingServer() { + VPNclientEngine.pingServer(subscriptionIndex: 0, index: 0); + setState(() { + _pingResult = 'Pinging...'; + }); + } + + void _loadServers() { + _servers = VPNclientEngine.getServerList(); + setState(() {}); + } + + void _applyRoutingRules() { + List rules = [ + RoutingRule(appName: "YouTube", action: "proxy"), + RoutingRule(appName: "google.com", action: "direct"), + RoutingRule(domain: "ads.com", action: "block"), + ]; + VPNclientEngine.setRoutingRules(rules: rules); + } + + void _loadSubscription() async { + if (_subscriptionUrlController.text.isEmpty) return; + + setState(() { + _loadedSubscriptions.add(_subscriptionUrlController.text); + }); + await VPNclientEngine.loadSubscriptions(subscriptionLinks: [_subscriptionUrlController.text]); + } + + + @override Widget build(BuildContext context) { - return MaterialApp( - home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), - ), - body: Center( - child: Text('Running on: $_platformVersion\n'), + return Scaffold( + appBar: AppBar( + title: const Text('VPN Client Engine Demo'), + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Connection Status: $_connectionStatus'), + Text('Current Server: $_currentServer'), + Text(_pingResult), + ElevatedButton( + onPressed: _connectToServer, + child: const Text('Connect'), + ), + ElevatedButton( + onPressed: _disconnectFromServer, + child: const Text('Disconnect'), + ), + const SizedBox(height: 20), + Text('Session Statistics'), + Text('Session Duration: ${_sessionStatistics.sessionDuration}'), + Text('Data In: ${_sessionStatistics.dataInBytes} bytes'), + Text('Data Out: ${_sessionStatistics.dataOutBytes} bytes'), + const SizedBox(height: 20), + ElevatedButton( + onPressed: _pingServer, + child: const Text('Ping Server'), + ), + const SizedBox(height: 20), + ElevatedButton( + onPressed: _loadServers, + child: const Text('Load Servers'), + ), + const SizedBox(height: 10), + Text('Loaded Servers:'), + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: _servers.length, + itemBuilder: (context, index) { + return ListTile( + title: Text(_servers[index].address), + subtitle: Text('Latency: ${_servers[index].latency ?? 'N/A'}, Location: ${_servers[index].location ?? 'N/A'}'), + ); + }, + ), + const SizedBox(height: 20), + ElevatedButton( + onPressed: _applyRoutingRules, + child: const Text('Apply Routing Rules'), + ), + const SizedBox(height: 10), + Text('Routing Rules Applied:'), + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: _routingRules.length, + itemBuilder: (context, index) { + final rule = _routingRules[index]; + return ListTile( + title: Text('Rule ${index + 1}'), + subtitle: Text('App Name: ${rule.appName ?? 'N/A'}, Domain: ${rule.domain ?? 'N/A'}, Action: ${rule.action}'), + ); + }, + ), + const SizedBox(height: 20), + Text('Load Subscription'), + TextField( + controller: _subscriptionUrlController, + decoration: const InputDecoration( + hintText: 'Enter subscription URL', + ), + ), + const SizedBox(height: 10), + ElevatedButton( + onPressed: _loadSubscription, + child: const Text('Load Subscription'), + ), + const SizedBox(height: 10), + Text('Loaded Subscriptions:'), + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: _loadedSubscriptions.length, + itemBuilder: (context, index) { + return ListTile( + title: Text(_loadedSubscriptions[index]), + ); + }, + ), + const SizedBox(height: 20), + ElevatedButton( + onPressed: () { + VPNclientEngine.setAutoConnect(enable: true); + }, + child: const Text('Enable auto connect'), + ), + ElevatedButton( + onPressed: () { + VPNclientEngine.setAutoConnect(enable: false); + }, + child: const Text('Disable auto connect'), + ), + const SizedBox(height: 20), + ElevatedButton( + onPressed: () { + VPNclientEngine.setKillSwitch(enable: true); + }, + child: const Text('Enable kill switch'), + ), + ElevatedButton( + onPressed: () { + VPNclientEngine.setKillSwitch(enable: false); + }, + child: const Text('Disable kill switch'), + ), + + ], ), ), ); } } + diff --git a/lib/vpnclient_engine/engine.dart b/lib/vpnclient_engine/engine.dart index 641f7fcf..a7faf686 100644 --- a/lib/vpnclient_engine/engine.dart +++ b/lib/vpnclient_engine/engine.dart @@ -2,66 +2,163 @@ import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:dart_ping/dart_ping.dart'; -import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter_platform_interface.dart'; - -/// +import 'package:vpnclient_engine_flutter/vpnclient_engine/server_connection.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_v2ray/flutter_v2ray.dart'; +import 'package:rxdart/rxdart.dart'; -/// -final FlutterV2ray flutterV2ray = FlutterV2ray( - onStatusChanged: (status) { - // do something - }, -); +enum ConnectionStatus { + connecting, + connected, + disconnected, + error, +} + +enum ErrorCode { + invalidCredentials, + serverUnavailable, + subscriptionExpired, + unknownError, +} + +enum ProxyType { + socks5, + http, +} + +enum Action { + block, + allow, + routeThroughVPN, + direct, + proxy, +} -/// +class Server { + final String address; + final int? latency; + final String? location; + final bool? isPreferred; + + Server({ + required this.address, + this.latency, + this.location, + this.isPreferred, + }); +} -enum ConnectionStatus { connecting, connected, disconnected, error } +class SubscriptionDetails { + final DateTime? expiryDate; + final int? dataLimit; + final int? usedData; + + SubscriptionDetails({ + this.expiryDate, + this.dataLimit, + this.usedData, + }); +} class SessionStatistics { - final Duration sessionDuration; + final Duration? sessionDuration; final int dataInBytes; final int dataOutBytes; SessionStatistics({ - required this.sessionDuration, + this.sessionDuration, required this.dataInBytes, required this.dataOutBytes, }); } +class ErrorDetails { + final ErrorCode errorCode; + final String errorMessage; + + ErrorDetails({required this.errorCode, required this.errorMessage}); +} + +class ProxyConfig { + final ProxyType type; + final String address; + final int port; + final String? credentials; + + ProxyConfig({ + required this.type, + required this.address, + required this.port, + this.credentials, + }); +} + +class PingResult { + final int subscriptionIndex; + final int serverIndex; + final int latencyInMs; + + PingResult({ + required this.subscriptionIndex, + required this.serverIndex, + required this.latencyInMs, + }); +} + class RoutingRule { final String? appName; final String? domain; final String action; // proxy, direct, block RoutingRule({this.appName, this.domain, required this.action}); + } -class PingResult { - final int latencyInMs; - PingResult(this.latencyInMs); -} + + +final FlutterV2ray flutterV2ray = FlutterV2ray( + onStatusChanged: (status) { + print("status changed: $status"); + }, + ); class VPNclientEngine { static List> _subscriptionServers = []; + static Map _connections = {}; + static List _subscriptions = []; - static String setTitle(int x) { - switch (x) { - case 1: - return 'Super HIT'; - case 2: - return 'VPNClient'; - } - return 'Hello from backend!'; - } - static final StreamController _connectionStatusController = - StreamController.broadcast(); - static final StreamController _pingResultController = - StreamController.broadcast(); + static final _connectionStatusSubject = BehaviorSubject(); + static Stream get onConnectionStatusChanged => _connectionStatusSubject.stream; + + static final _errorSubject = BehaviorSubject(); + static Stream get onError => _errorSubject.stream; + + static final _serverSwitchedSubject = BehaviorSubject(); + static Stream get onServerSwitched => _serverSwitchedSubject.stream; + + static final _pingResultSubject = BehaviorSubject(); + static Stream get onPingResult => _pingResultSubject.stream; + + static final _subscriptionLoadedSubject = BehaviorSubject(); + static Stream get onSubscriptionLoaded => _subscriptionLoadedSubject.stream; + + static final _dataUsageUpdatedSubject = BehaviorSubject(); + static Stream get onDataUsageUpdated => _dataUsageUpdatedSubject.stream; + + static final _routingRulesAppliedSubject = BehaviorSubject>(); + static Stream> get onRoutingRulesApplied => _routingRulesAppliedSubject.stream; + + static final _killSwitchTriggeredSubject = BehaviorSubject(); + static Stream get onKillSwitchTriggered => _killSwitchTriggeredSubject.stream; + + + static void _emitError(ErrorCode code, String message) { + _errorSubject.add(ErrorDetails(errorCode: code, errorMessage: message)); + } + static List _subscriptions = []; @@ -127,80 +224,98 @@ class VPNclientEngine { // Save fetched servers to specific subscription index _subscriptionServers[subscriptionIndex] = servers; + _subscriptionLoadedSubject.add(SubscriptionDetails()); print('Subscription #$subscriptionIndex servers updated successfully'); } catch (e) { print('Error updating subscription: $e'); + _emitError(ErrorCode.unknownError, 'Error updating subscription: $e'); } } - static Stream get onConnectionStatusChanged => _connectionStatusController.stream; - static Stream get onPingResult => _pingResultController.stream; - static Future connect({required int subscriptionIndex, required int serverIndex}) async { + print('Connecting to subscription $subscriptionIndex, server $serverIndex...'); + if (subscriptionIndex < 0 || subscriptionIndex >= _subscriptionServers.length) { + _emitError(ErrorCode.unknownError, 'Invalid subscription index'); + return; + } + + if (serverIndex < 0 || serverIndex >= _subscriptionServers[subscriptionIndex].length) { + _emitError(ErrorCode.unknownError, 'Invalid server index'); + return; + } + + _connectionStatusSubject.add(ConnectionStatus.connecting); - /// // You must initialize V2Ray before using it. - print('Initializing...'); await flutterV2ray.initializeV2Ray(); + - // v2ray share link like vmess://, vless://, ... - String link = - //"vless://c61daf3e-83ff-424f-a4ff-5bfcb46f0b30@5.35.98.91:8443?encryption=none&flow=&security=reality&sni=yandex.ru&fp=chrome&pbk=rLCmXWNVoRBiknloDUsbNS5ONjiI70v-BWQpWq0HCQ0&sid=108108108108#%F0%9F%87%B7%F0%9F%87%BA+%F0%9F%99%8F+Russia+%231"; - "vless://c61daf3e-83ff-424f-a4ff-5bfcb46f0b30@45.77.190.146:8443?encryption=none&flow=&security=reality&sni=www.gstatic.com&fp=chrome&pbk=rLCmXWNVoRBiknloDUsbNS5ONjiI70v-BWQpWq0HCQ0&sid=108108108108#%F0%9F%87%BA%F0%9F%87%B8+%F0%9F%99%8F+USA+%231"; + //Get server info + String link = _subscriptionServers[subscriptionIndex][serverIndex]; V2RayURL parser = FlutterV2ray.parseFromURL(link); - // Get Server Delay - //print( - // '${flutterV2ray.getServerDelay(config: parser.getFullConfiguration())}ms', - // name: 'ServerDelay', - //); + // Permission is not required if you using proxy only - print('Premissions...'); - if (await flutterV2ray.requestPermission()) { - print('Starting...'); - flutterV2ray.startV2Ray( - remark: parser.remark, - // The use of parser.getFullConfiguration() is not mandatory, - // and you can enter the desired V2Ray configuration in JSON format - config: parser.getFullConfiguration(), - blockedApps: null, - bypassSubnets: null, - proxyOnly: false, - ); - print('Started'); + if (!(await flutterV2ray.requestPermission())) { + _connectionStatusSubject.add(ConnectionStatus.error); + _emitError(ErrorCode.unknownError, 'Permission not granted'); + return; } - // Disconnect - ///flutterV2ray.stopV2Ray(); - - /// - - //TODO:move to right place - + flutterV2ray.startV2Ray( + remark: parser.remark, + // The use of parser.getFullConfiguration() is not mandatory, + // and you can enter the desired V2Ray configuration in JSON format + config: parser.getFullConfiguration(), + blockedApps: null, + bypassSubnets: null, + proxyOnly: false, + ); + + + _connections[subscriptionIndex] = ServerConnection( + subscriptionIndex: subscriptionIndex, + serverIndex: serverIndex, + connectionStatus: ConnectionStatus.connected, + serverAddress: _subscriptionServers[subscriptionIndex][serverIndex], + connectedAt: DateTime.now(), + ); + + _connectionStatusSubject.add(ConnectionStatus.connected); + _serverSwitchedSubject.add(parser.remark ?? "null"); + + print('Successfully connected'); + + } + static Future disconnect() async { + print('Disconnecting...'); - - - print(await VpnclientEngineFlutterPlatform.instance.getPlatformVersion()); - - print('Connecting to subscription $subscriptionIndex, server $serverIndex...'); - _connectionStatusController.add(ConnectionStatus.connecting); + if(_connections.isEmpty){ + print('Noting to disconnect.'); + _connectionStatusSubject.add(ConnectionStatus.disconnected); + return; + } - print(await VpnclientEngineFlutterPlatform.instance.getPlatformVersion()); + try { + + await flutterV2ray.stopV2Ray(); + _connections.clear(); + _connectionStatusSubject.add(ConnectionStatus.disconnected); - await Future.delayed(Duration(seconds: 5)); - _connectionStatusController.add(ConnectionStatus.connected); - print('Successfully connected'); - } + } catch (e) { + _connectionStatusSubject.add(ConnectionStatus.error); + _emitError(ErrorCode.unknownError, 'Error disconnecting: $e'); + print('Error disconnecting: $e'); + } - static Future disconnect() async { - _connectionStatusController.add(ConnectionStatus.disconnected); print('Disconnected successfully'); } + static void setRoutingRules({required List rules}) { for (var rule in rules) { if (rule.appName != null) { @@ -216,31 +331,67 @@ class VPNclientEngine { print('Invalid subscription index'); return; } - if (index < 0 || index >= _subscriptionServers[subscriptionIndex].length) { print('Invalid server index'); return; } - final serverAddress = _subscriptionServers[subscriptionIndex][index]; print('Pinging server: $serverAddress'); - try { final ping = Ping(serverAddress, count: 3); final pingData = await ping.stream.firstWhere((data) => data.response != null); - if (pingData.response != null) { final latency = pingData.response!.time!.inMilliseconds; - final result = PingResult(latency); - _pingResultController.add(result); - print('Ping result: ${result.latencyInMs} ms'); + final result = PingResult(subscriptionIndex: subscriptionIndex, serverIndex: index, latencyInMs: latency); + _pingResultSubject.add(result); + print('Ping result: sub=${result.subscriptionIndex}, server=${result.serverIndex}, latency=${result.latencyInMs} ms'); } else { print('Ping failed: No response'); - _pingResultController.add(PingResult(-1)); // Indicate error with -1 + _pingResultSubject.add(PingResult(subscriptionIndex: subscriptionIndex, serverIndex: index, latencyInMs: -1)); // Indicate error with -1 } } catch (e) { print('Ping error: $e'); - _pingResultController.add(PingResult(-1)); + _pingResultSubject.add(PingResult(subscriptionIndex: subscriptionIndex, serverIndex: index, latencyInMs: -1)); } } + + static String getConnectionStatus() { + // enum ConnectionStatus { connecting, connected, disconnected, error } + return 'disconnected'; + } + + static List getServerList() { + //TODO: + //Fetches the list of available VPN servers. + return [ + Server(address: 'server1.com', latency: 50, location: 'USA', isPreferred: true), + Server(address: 'server2.com', latency: 100, location: 'UK', isPreferred: false), + Server(address: 'server3.com', latency: 75, location: 'Canada', isPreferred: false), + ]; + } + + static Future loadSubscriptions({required List subscriptionLinks}) async { + print('loadSubscriptions: ${subscriptionLinks.join(", ")}'); + _subscriptions.addAll(subscriptionLinks); + print('Subscriptions added: ${subscriptionLinks.join(", ")}'); + //TODO: loading process + } + + static SessionStatistics getSessionStatistics() { + //TODO: + return SessionStatistics( + sessionDuration: Duration(minutes: 30), + dataInBytes: 1024 * 1024 * 100, // 100MB + dataOutBytes: 1024 * 1024 * 50, // 50MB + ); + } + + static void setAutoConnect({required bool enable}) { + print('setAutoConnect: $enable'); + } + + static void setKillSwitch({required bool enable}) { + print('setKillSwitch: $enable'); + } + } \ No newline at end of file diff --git a/lib/vpnclient_engine_flutter.dart b/lib/vpnclient_engine_flutter.dart index f060813f..7516d99f 100644 --- a/lib/vpnclient_engine_flutter.dart +++ b/lib/vpnclient_engine_flutter.dart @@ -1,11 +1,4 @@ - -import 'vpnclient_engine_flutter_platform_interface.dart'; - export 'vpnclient_engine/engine.dart'; class VpnclientEngineFlutter { - - Future getPlatformVersion() { - return VpnclientEngineFlutterPlatform.instance.getPlatformVersion(); - } -} +} \ No newline at end of file diff --git a/lib/vpnclient_engine_flutter_method_channel.dart b/lib/vpnclient_engine_flutter_method_channel.dart deleted file mode 100644 index 1f98c844..00000000 --- a/lib/vpnclient_engine_flutter_method_channel.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; -import 'vpnclient_engine_flutter_platform_interface.dart'; - -class VpnclientEngineFlutterMethodChannel - extends VpnclientEngineFlutterPlatform { - @visibleForTesting - final methodChannel = const MethodChannel('vpnclient_engine_flutter'); - - @override - Future startVPN({required String configPath}) async { - try { - await methodChannel.invokeMethod('startVPN', {"config": configPath}); - } catch (e) { - debugPrint('Error starting VPN: $e'); - rethrow; - } - } - - @override - Future stopVPN() async { - try { - await methodChannel.invokeMethod('stopVPN'); - } catch (e) { - debugPrint('Error stopping VPN: $e'); - rethrow; - } - } - @override - Future checkVPNStatus() async { - final result = await methodChannel.invokeMethod('status'); - return result.toString(); - } -} diff --git a/lib/vpnclient_engine_flutter_platform_interface.dart b/lib/vpnclient_engine_flutter_platform_interface.dart deleted file mode 100644 index 63ea73aa..00000000 --- a/lib/vpnclient_engine_flutter_platform_interface.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; - -import 'vpnclient_engine_flutter_method_channel.dart'; - - -abstract class VpnclientEngineFlutterPlatform extends PlatformInterface { - VpnclientEngineFlutterPlatform() : super(token: _token); - - static final Object _token = Object(); - - static VpnclientEngineFlutterPlatform _instance = - MethodChannelVpnclientEngineFlutter(); - - static VpnclientEngineFlutterPlatform get instance => _instance; - - static set instance(VpnclientEngineFlutterPlatform instance) { - PlatformInterface.verifyToken(instance, _token); - _instance = instance; - } - - Future startVPN(String config) => throw UnimplementedError('startVPN() has not been implemented.'); - Future stopVPN() => throw UnimplementedError('stopVPN() has not been implemented.'); - Future checkVPNStatus() => throw UnimplementedError('checkVPNStatus() has not been implemented.'); - } - -/* - static dynamic _getPlatformImpl() { - if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) { - return AndroidNativeImpl(); - } else { - return DefaultImpl(); - } - } - - // В отдельном файле platforms/android.dart - class AndroidNativeImpl { - // Реализация для Android - } - - // В отдельном файле platforms/default.dart - class DefaultImpl { - // Заглушка для других платформ - } -*/ - -} diff --git a/lib/vpnclient_engine_flutter_web.dart b/lib/vpnclient_engine_flutter_web.dart deleted file mode 100644 index 47044070..00000000 --- a/lib/vpnclient_engine_flutter_web.dart +++ /dev/null @@ -1,26 +0,0 @@ -// In order to *not* need this ignore, consider extracting the "web" version -// of your plugin as a separate package, instead of inlining it in the same -// package as the core of your plugin. -// ignore: avoid_web_libraries_in_flutter - -import 'package:flutter_web_plugins/flutter_web_plugins.dart'; -import 'package:web/web.dart' as web; - -import 'vpnclient_engine_flutter_platform_interface.dart'; - -/// A web implementation of the VpnclientEngineFlutterPlatform of the VpnclientEngineFlutter plugin. -class VpnclientEngineFlutterWeb extends VpnclientEngineFlutterPlatform { - /// Constructs a VpnclientEngineFlutterWeb - VpnclientEngineFlutterWeb(); - - static void registerWith(Registrar registrar) { - VpnclientEngineFlutterPlatform.instance = VpnclientEngineFlutterWeb(); - } - - /// Returns a [String] containing the version of the platform. - @override - Future getPlatformVersion() async { - final version = web.window.navigator.userAgent; - return version; - } -} diff --git a/test/vpnclient_engine_flutter_method_channel_test.dart b/test/vpnclient_engine_flutter_method_channel_test.dart deleted file mode 100644 index b565a11b..00000000 --- a/test/vpnclient_engine_flutter_method_channel_test.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter_method_channel.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - MethodChannelVpnclientEngineFlutter platform = MethodChannelVpnclientEngineFlutter(); - const MethodChannel channel = MethodChannel('vpnclient_engine_flutter'); - - setUp(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( - channel, - (MethodCall methodCall) async { - return '42'; - }, - ); - }); - - tearDown(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null); - }); - - test('getPlatformVersion', () async { - expect(await platform.getPlatformVersion(), '42'); - }); -} diff --git a/test/vpnclient_engine_flutter_test.dart b/test/vpnclient_engine_flutter_test.dart deleted file mode 100644 index f4b85307..00000000 --- a/test/vpnclient_engine_flutter_test.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter.dart'; -import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter_platform_interface.dart'; -import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter_method_channel.dart'; -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; - -class MockVpnclientEngineFlutterPlatform - with MockPlatformInterfaceMixin - implements VpnclientEngineFlutterPlatform { - - @override - Future getPlatformVersion() => Future.value('42'); -} - -void main() { - final VpnclientEngineFlutterPlatform initialPlatform = VpnclientEngineFlutterPlatform.instance; - - test('$MethodChannelVpnclientEngineFlutter is the default instance', () { - expect(initialPlatform, isInstanceOf()); - }); - - test('getPlatformVersion', () async { - VpnclientEngineFlutter vpnclientEngineFlutterPlugin = VpnclientEngineFlutter(); - MockVpnclientEngineFlutterPlatform fakePlatform = MockVpnclientEngineFlutterPlatform(); - VpnclientEngineFlutterPlatform.instance = fakePlatform; - - expect(await vpnclientEngineFlutterPlugin.getPlatformVersion(), '42'); - }); -} From 60237d5d407ad970a31af26d1560be1b73b4639e Mon Sep 17 00:00:00 2001 From: "anton.v.dodonov" Date: Sun, 27 Apr 2025 19:42:42 +0000 Subject: [PATCH 64/77] cicd --- .github/workflows/publish.yml | 49 +++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..2551e466 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,49 @@ +name: Publish Flutter Plugin + +on: + push: + tags: + - 'v*' + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.16.0' # or specify latest stable + + - name: Install dependencies + run: flutter pub get + + - name: Analyze code + run: flutter analyze + + - name: Format code + run: dart format --output=none --set-exit-if-changed . + + - name: Update version + run: | + LATEST_TAG=$(git describe --tags --abbrev=0) + VERSION=${LATEST_TAG#v} + sed -i "s/version:.*/version: $VERSION/" pubspec.yaml + git config user.name github-actions + git config user.email github-actions@github.com + git add pubspec.yaml + git commit -m "chore: update version to $VERSION" + git push + + - name: Run tests + run: flutter test + + - name: Publish plugin + run: flutter pub publish --force + env: + PUB_HOSTED_URL: https://pub.dartlang.org + PUB_TOKEN: ${{ secrets.PUB_TOKEN }} \ No newline at end of file From 2a5b663aa61d041a4a63eb03a7288d7015b4ba48 Mon Sep 17 00:00:00 2001 From: "anton.v.dodonov" Date: Sun, 27 Apr 2025 19:48:43 +0000 Subject: [PATCH 65/77] ios --- .../VpnclientEngineFlutterPlugin.swift | 196 +++++++++++++----- lib/vpnclient_engine/engine.dart | 156 +++++++------- 2 files changed, 210 insertions(+), 142 deletions(-) diff --git a/ios/Classes/VpnclientEngineFlutterPlugin.swift b/ios/Classes/VpnclientEngineFlutterPlugin.swift index 46a9a326..e9b776ff 100644 --- a/ios/Classes/VpnclientEngineFlutterPlugin.swift +++ b/ios/Classes/VpnclientEngineFlutterPlugin.swift @@ -1,7 +1,13 @@ import Flutter import UIKit import NetworkExtension -import singbox +import flutter_v2ray_plugin + +enum VpnError: Error { + case missingConfig + case startFailed(String) + case stopFailed(String) +} /// Plugin class to handle VPN connections in the Flutter app. public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { @@ -11,87 +17,165 @@ public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { let channel = FlutterMethodChannel(name: "vpnclient_engine_flutter", binaryMessenger: registrar.messenger()) let instance = VpnclientEngineFlutterPlugin() registrar.addMethodCallDelegate(instance, channel: channel) + instance.channel = channel } + private var channel: FlutterMethodChannel? + + private var v2rayPlugin = FlutterV2rayPlugin.sharedInstance() + + private var tunnelManager: NETunnelProviderManager? + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { - case "startVPN": - guard let args = call.arguments as? [String: Any], - let config = args["config"] as? String else { - result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing or invalid config", details: nil)) + case "connect": + guard let arguments = call.arguments as? [String: Any] else { + result(FlutterError(code: "ARGUMENT_ERROR", message: "Invalid arguments", details: nil)) return } - startVPN(withConfig: config, result: result) - case "stopVPN": - stopVPN(result: result) - case "checkVPNStatus": - checkVPNStatus(result: result) + self.connect(arguments: arguments, result: result) + case "disconnect": + self.disconnect(result: result) + case "requestPermissions": + self.requestPermissions(result: result) + case "getConnectionStatus": + self.getConnectionStatus(result: result) + case "checkSystemPermission": + self.checkSystemPermission(result: result) default: result(FlutterMethodNotImplemented) } } - /// Starts the VPN connection. - private func startVPN(withConfig config: String, result: @escaping FlutterResult) { - do { - let singboxConfig = try SingboxConfig(json: config) - let builder = SingboxBuilder(config: singboxConfig) - - try builder.build() - - DispatchQueue.global(qos: .userInitiated).async { - do { - try builder.start() - - DispatchQueue.main.async { - result(nil) - } - } catch { - DispatchQueue.main.async { - result(FlutterError(code: "VPN_START_FAILED", message: "Failed to start VPN: \(error.localizedDescription)", details: nil)) - } + private func connect(arguments: [String: Any], result: @escaping FlutterResult) { + guard let link = arguments["link"] as? String else { + result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing or invalid config", details: nil)) + return + } + + let parsedConfig = FlutterV2ray.parseFromURL(link) + + let config: String = parsedConfig.getFullConfiguration() + + if config.isEmpty { + result(FlutterError(code: "CONFIG_ERROR", message: "Invalid V2Ray config", details: nil)) + } + + v2rayPlugin.startV2Ray( + remark: parsedConfig.remark, + config: config, + blockedApps: nil, + bypassSubnets: nil, + proxyOnly: false + ) { err in + if let err = err { + DispatchQueue.main.async { + self.sendError(errorCode: "VPN_START_FAILED", errorMessage: err) + result(FlutterError(code: "VPN_START_FAILED", message: "Failed to start VPN: \(err)", details: nil)) + } + } else { + DispatchQueue.main.async { + self.sendConnectionStatus(status: "connected") + result(nil) } } - } catch { - result(FlutterError(code: "CONFIG_ERROR", message: "Invalid Singbox config: \(error.localizedDescription)", details: nil)) } } - - /// Stops the VPN connection. - private func stopVPN(result: @escaping FlutterResult) { - DispatchQueue.global(qos: .userInitiated).async { - do { - try Singbox.shared.stop() + + private func sendError(errorCode: String, errorMessage: String) { + channel?.invokeMethod("onError", arguments: ["errorCode": errorCode, "errorMessage": errorMessage]) + } + + private func disconnect(result: @escaping FlutterResult) { + v2rayPlugin.stopV2Ray { err in + if let err = err { DispatchQueue.main.async { - result(nil) + self.sendError(errorCode: "VPN_STOP_FAILED", errorMessage: err) + result(FlutterError(code: "VPN_STOP_FAILED", message: "Failed to stop VPN: \(err)", details: nil)) } - } catch { + } else { DispatchQueue.main.async { - result(FlutterError(code: "VPN_STOP_FAILED", message: "Failed to stop VPN: \(error.localizedDescription)", details: nil)) + self.sendConnectionStatus(status: "disconnected") + result(nil) } } } } - /// Checks the current status of the VPN connection. - private func checkVPNStatus(result: @escaping FlutterResult) { - if Singbox.shared.isRunning { - result(true) - } else { - result(false) + private func requestPermissions(result: @escaping FlutterResult) { + + NETunnelProviderManager.loadAllFromPreferences { managers, error in + + if let error = error { + result(FlutterError(code: "PERMISSION_ERROR", message: "Failed to load VPN configurations: \(error.localizedDescription)", details: nil)) + return + } + + var manager: NETunnelProviderManager + if let managers = managers, let firstManager = managers.first { + manager = firstManager + } else { + manager = NETunnelProviderManager() + + manager.localizedDescription = "VPNClientEngine" + + let protocolConfiguration = NETunnelProviderProtocol() + protocolConfiguration.providerBundleIdentifier = "click.vpnclient.engine" + manager.protocolConfiguration = protocolConfiguration + } + + manager.isEnabled = true + + manager.saveToPreferences { error in + if let error = error { + result(FlutterError(code: "PERMISSION_ERROR", message: "Failed to save VPN configuration: \(error.localizedDescription)", details: nil)) + return + } + + manager.loadFromPreferences { error in + if let error = error { + result(FlutterError(code: "PERMISSION_ERROR", message: "Failed to load VPN preferences: \(error.localizedDescription)", details: nil)) + return + } + + result(true) + } + } } } -} - -private extension SingboxConfig { - convenience init(json: String) throws { - guard let data = json.data(using: .utf8) else { - throw NSError(domain: "SingboxConfig", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to convert JSON string to data"]) + + private func sendConnectionStatus(status: String) { + channel?.invokeMethod("onConnectionStatusChanged", arguments: status) + } + + private func checkSystemPermission(result: @escaping FlutterResult) { + NETunnelProviderManager.loadAllFromPreferences { managers, error in + + if let error = error { + result(FlutterError(code: "PERMISSION_ERROR", message: "Failed to load VPN configurations: \(error.localizedDescription)", details: nil)) + return + } + + if managers?.isEmpty == false { + if let firstManager = managers?.first { + if firstManager.isEnabled == true { + result(true) + } else { + result(false) + } + } + } else { + result(false) + } + } + } + + private func getConnectionStatus(result: @escaping FlutterResult) { + if v2rayPlugin.isRunning() == true { + result("connected") + } else { + result("disconnected") } - self.init() - - try self.fromJSONData(data: data) } } - diff --git a/lib/vpnclient_engine/engine.dart b/lib/vpnclient_engine/engine.dart index a7faf686..b09d4b55 100644 --- a/lib/vpnclient_engine/engine.dart +++ b/lib/vpnclient_engine/engine.dart @@ -6,6 +6,7 @@ import 'package:vpnclient_engine_flutter/vpnclient_engine/server_connection.dart import 'package:flutter/foundation.dart'; import 'package:flutter_v2ray/flutter_v2ray.dart'; import 'package:rxdart/rxdart.dart'; +import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter.dart'; enum ConnectionStatus { @@ -112,7 +113,7 @@ class RoutingRule { final String action; // proxy, direct, block RoutingRule({this.appName, this.domain, required this.action}); - + } @@ -122,6 +123,7 @@ final FlutterV2ray flutterV2ray = FlutterV2ray( onStatusChanged: (status) { print("status changed: $status"); }, + ); class VPNclientEngine { static List> _subscriptionServers = []; @@ -234,72 +236,50 @@ class VPNclientEngine { } static Future connect({required int subscriptionIndex, required int serverIndex}) async { - print('Connecting to subscription $subscriptionIndex, server $serverIndex...'); - - if (subscriptionIndex < 0 || subscriptionIndex >= _subscriptionServers.length) { - _emitError(ErrorCode.unknownError, 'Invalid subscription index'); - return; - } - - if (serverIndex < 0 || serverIndex >= _subscriptionServers[subscriptionIndex].length) { - _emitError(ErrorCode.unknownError, 'Invalid server index'); - return; - } - - _connectionStatusSubject.add(ConnectionStatus.connecting); - - // You must initialize V2Ray before using it. - await flutterV2ray.initializeV2Ray(); - + print('Connecting to subscription $subscriptionIndex, server $serverIndex...'); - //Get server info - String link = _subscriptionServers[subscriptionIndex][serverIndex]; - V2RayURL parser = FlutterV2ray.parseFromURL(link); + if (subscriptionIndex < 0 || subscriptionIndex >= _subscriptionServers.length) { + _emitError(ErrorCode.unknownError, 'Invalid subscription index'); + return; + } - + if (serverIndex < 0 || serverIndex >= _subscriptionServers[subscriptionIndex].length) { + _emitError(ErrorCode.unknownError, 'Invalid server index'); + return; + } - // Permission is not required if you using proxy only - if (!(await flutterV2ray.requestPermission())) { - _connectionStatusSubject.add(ConnectionStatus.error); - _emitError(ErrorCode.unknownError, 'Permission not granted'); - return; - } + _connectionStatusSubject.add(ConnectionStatus.connecting); + VpnclientEngineFlutterPlatform.instance.connect(subscriptionIndex: subscriptionIndex, serverIndex: serverIndex).then((_) { + _connectionStatusSubject.add(ConnectionStatus.connected); + }).catchError((error) { + _connectionStatusSubject.add(ConnectionStatus.error); + _emitError(ErrorCode.unknownError, 'Error on connect: $error'); + }); - flutterV2ray.startV2Ray( - remark: parser.remark, - // The use of parser.getFullConfiguration() is not mandatory, - // and you can enter the desired V2Ray configuration in JSON format - config: parser.getFullConfiguration(), - blockedApps: null, - bypassSubnets: null, - proxyOnly: false, - ); - - - _connections[subscriptionIndex] = ServerConnection( - subscriptionIndex: subscriptionIndex, - serverIndex: serverIndex, - connectionStatus: ConnectionStatus.connected, - serverAddress: _subscriptionServers[subscriptionIndex][serverIndex], - connectedAt: DateTime.now(), - ); + //Get server info + String link = _subscriptionServers[subscriptionIndex][serverIndex]; + V2RayURL parser = FlutterV2ray.parseFromURL(link); + _serverSwitchedSubject.add(parser.remark ?? "null"); - _connectionStatusSubject.add(ConnectionStatus.connected); - _serverSwitchedSubject.add(parser.remark ?? "null"); - - print('Successfully connected'); - + print('Connecting to server: $link'); } static Future disconnect() async { print('Disconnecting...'); - + if(_connections.isEmpty){ print('Noting to disconnect.'); _connectionStatusSubject.add(ConnectionStatus.disconnected); return; } - + + VpnclientEngineFlutterPlatform.instance.disconnect().then((_) { + _connectionStatusSubject.add(ConnectionStatus.disconnected); + }).catchError((error) { + _connectionStatusSubject.add(ConnectionStatus.error); + _emitError(ErrorCode.unknownError, 'Error on disconnect: $error'); + }); +/* try { await flutterV2ray.stopV2Ray(); @@ -310,8 +290,8 @@ class VPNclientEngine { _connectionStatusSubject.add(ConnectionStatus.error); _emitError(ErrorCode.unknownError, 'Error disconnecting: $e'); print('Error disconnecting: $e'); - } - + }*/ + print('Disconnected successfully'); } @@ -337,13 +317,14 @@ class VPNclientEngine { } final serverAddress = _subscriptionServers[subscriptionIndex][index]; print('Pinging server: $serverAddress'); + try { final ping = Ping(serverAddress, count: 3); final pingData = await ping.stream.firstWhere((data) => data.response != null); if (pingData.response != null) { final latency = pingData.response!.time!.inMilliseconds; final result = PingResult(subscriptionIndex: subscriptionIndex, serverIndex: index, latencyInMs: latency); - _pingResultSubject.add(result); + _pingResultSubject.add(result); print('Ping result: sub=${result.subscriptionIndex}, server=${result.serverIndex}, latency=${result.latencyInMs} ms'); } else { print('Ping failed: No response'); @@ -356,42 +337,45 @@ class VPNclientEngine { } static String getConnectionStatus() { - // enum ConnectionStatus { connecting, connected, disconnected, error } - return 'disconnected'; + // enum ConnectionStatus { connecting, connected, disconnected, error } + return 'disconnected'; } - static List getServerList() { - //TODO: - //Fetches the list of available VPN servers. - return [ - Server(address: 'server1.com', latency: 50, location: 'USA', isPreferred: true), - Server(address: 'server2.com', latency: 100, location: 'UK', isPreferred: false), - Server(address: 'server3.com', latency: 75, location: 'Canada', isPreferred: false), - ]; - } + static List getServerList() { + //TODO: + //Fetches the list of available VPN servers. + return [ + Server(address: 'server1.com', latency: 50, location: 'USA', isPreferred: true), + Server(address: 'server2.com', latency: 100, location: 'UK', isPreferred: false), + Server(address: 'server3.com', latency: 75, location: 'Canada', isPreferred: false), + ]; + } - static Future loadSubscriptions({required List subscriptionLinks}) async { - print('loadSubscriptions: ${subscriptionLinks.join(", ")}'); - _subscriptions.addAll(subscriptionLinks); - print('Subscriptions added: ${subscriptionLinks.join(", ")}'); - //TODO: loading process + static Future loadSubscriptions({required List subscriptionLinks}) async { + print('loadSubscriptions: ${subscriptionLinks.join(", ")}'); + _subscriptions.addAll(subscriptionLinks); + for (var element in subscriptionLinks) { + addSubscription(subscriptionURL: element); + await updateSubscription(subscriptionIndex: _subscriptions.length-1); } + print('Subscriptions added: ${subscriptionLinks.join(", ")}'); + } - static SessionStatistics getSessionStatistics() { - //TODO: - return SessionStatistics( - sessionDuration: Duration(minutes: 30), - dataInBytes: 1024 * 1024 * 100, // 100MB - dataOutBytes: 1024 * 1024 * 50, // 50MB - ); - } + static SessionStatistics getSessionStatistics() { + //TODO: + return SessionStatistics( + sessionDuration: Duration(minutes: 30), + dataInBytes: 1024 * 1024 * 100, // 100MB + dataOutBytes: 1024 * 1024 * 50, // 50MB + ); + } - static void setAutoConnect({required bool enable}) { - print('setAutoConnect: $enable'); - } + static void setAutoConnect({required bool enable}) { + print('setAutoConnect: $enable'); + } - static void setKillSwitch({required bool enable}) { - print('setKillSwitch: $enable'); - } + static void setKillSwitch({required bool enable}) { + print('setKillSwitch: $enable'); + } } \ No newline at end of file From 6b3137ade3f7589b6a55d67c05d7a554e9b7ae1e Mon Sep 17 00:00:00 2001 From: "anton.v.dodonov" Date: Sun, 27 Apr 2025 19:56:58 +0000 Subject: [PATCH 66/77] wireguard --- README.md | 30 +- .../VpnclientEngineFlutterPlugin.swift | 53 ++- lib/vpnclient_engine/core.dart | 394 ++++++++++++++++++ lib/vpnclient_engine/engine.dart | 81 ++-- 4 files changed, 468 insertions(+), 90 deletions(-) create mode 100644 lib/vpnclient_engine/core.dart diff --git a/README.md b/README.md index 40440b99..8a140aed 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,28 @@ -# VPN Client Engine Flutter (Flutter Plugin) +# VPN Client Engine Flutter Plugin -**VPNclient Engine Flutter** is a Flutter plugin that provides a high-level API for controlling VPN connections from a Dart/Flutter app. It wraps the native [VPNclient Engine](https://github.com/VPNclient/VPNclient-engine) library, allowing Flutter developers to integrate advanced VPN functionality into their apps with ease. With this plugin, you can start and stop VPN connections, switch servers, apply routing rules, and listen to connection events using simple Dart calls, without worrying about platform-specific implementation details. +**VPN Client Engine Flutter** is a Flutter plugin that offers a high-level Dart API for managing VPN connections, making it easy to integrate advanced VPN functionality into Flutter apps. It abstracts the complexities of working with different VPN protocols and drivers, providing a unified interface for developers. The plugin supports multiple VPN protocols, including V2Ray and WireGuard, allowing you to choose the best technology for your needs. ## 🚀 Key Features -- **Seamless Integration:** The plugin is built to be cross-platform. It uses platform-specific binaries and code (written in C++ and integrated via Dart FFI) to interface with iOS, Android, Windows, macOS, and Linux, but exposes a unified Dart interface. This means you write your VPN logic once in Dart and it works everywhere Flutter does. -- **Intuitive API:** The API is designed with Flutter developers in mind. You can initialize the VPN engine, connect to a server, and listen for status changes using streams and futures. The plugin handles asynchronous calls and background threads internally. -- **Powered by VPNclient Engine:** Under the hood, this plugin utilizes the native VPNclient Engine, which supports multiple protocols (Xray/VMess/VLESS/Reality, WireGuard, OpenVPN, etc.) and drivers. The plugin abstracts the complexity, so you can, for example, simply call `connect()` and the engine will take care of setting up a tun interface or proxy as needed on that platform. +- **Cross-Platform Compatibility:** Built to work seamlessly across iOS, Android, Windows, macOS, and Linux, offering a consistent Dart interface that hides platform-specific code. +- **Intuitive Dart API:** Provides a simple, easy-to-use Dart interface for initializing the VPN engine, connecting/disconnecting, managing subscriptions, and listening for events via streams and futures. +- **Protocol Agnostic:** The core logic is abstracted to support multiple VPN protocols. Currently supports **V2Ray** and **WireGuard**. +- **Unified Core Management**: Offers the ability to choose between different cores(now supports **V2RayCore** and **WireGuardCore**) via the `VpnCore` interface, allowing developers to select the VPN technology that best suits their application requirements. +- **Powered by `flutter_v2ray`:** Utilizes the `flutter_v2ray` plugin to manage the core VPN functionality, ensuring a robust and efficient implementation for both **V2Ray** and **WireGuard**. +- **Seamless Integration with Native APIs:** Leverages platform-specific VPN APIs like `VpnService` on Android and `NetworkExtension` on iOS, ensuring optimal performance and security. ## 🖥️ Supported Platforms -- ✅ iOS (15.0+) -- ✅ Android (5.0+) -- ✅ macOS (Intel/Silicon) -- ✅ Windows -- ✅ Unix (Linux/Debian/Ubuntu) +- ✅ iOS (15.0+) +- ✅ Android (5.0+) +- ✅ macOS (Intel/Silicon) +- ✅ Windows +- ✅ Unix (Linux/Debian/Ubuntu) -Each platform uses the native capabilities provided by VPNclient Engine: -- On Android and iOS, the engine uses the system VPN APIs (VpnService, NetworkExtension) to create a VPN tunnel. -- On desktop, it can either create a TUN interface or run as a local proxy (depending on driver configuration). +Each platform leverages native capabilities for VPN functionality: + +- **Android and iOS:** Employs system VPN APIs (`VpnService`, `NetworkExtension`) for creating a secure VPN tunnel. +- **Desktop (Windows, macOS, Linux):** Can establish a TUN interface or operate as a local proxy, configurable based on the chosen driver. ## 📦 Architecture diff --git a/ios/Classes/VpnclientEngineFlutterPlugin.swift b/ios/Classes/VpnclientEngineFlutterPlugin.swift index e9b776ff..c5f287a6 100644 --- a/ios/Classes/VpnclientEngineFlutterPlugin.swift +++ b/ios/Classes/VpnclientEngineFlutterPlugin.swift @@ -1,8 +1,3 @@ -import Flutter -import UIKit -import NetworkExtension -import flutter_v2ray_plugin - enum VpnError: Error { case missingConfig case startFailed(String) @@ -10,6 +5,10 @@ enum VpnError: Error { } /// Plugin class to handle VPN connections in the Flutter app. +import Flutter +import UIKit +import NetworkExtension +import flutter_v2ray_plugin public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { private var tunnelProvider: NETunnelProviderManager? @@ -25,6 +24,7 @@ public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { private var v2rayPlugin = FlutterV2rayPlugin.sharedInstance() private var tunnelManager: NETunnelProviderManager? + private var isVpnRunning: Bool = false public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { @@ -40,6 +40,8 @@ public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { self.requestPermissions(result: result) case "getConnectionStatus": self.getConnectionStatus(result: result) + case "getPlatformVersion": + result("iOS " + UIDevice.current.systemVersion) case "checkSystemPermission": self.checkSystemPermission(result: result) default: @@ -50,23 +52,30 @@ public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { private func connect(arguments: [String: Any], result: @escaping FlutterResult) { guard let link = arguments["link"] as? String else { result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing or invalid config", details: nil)) - return - } - - let parsedConfig = FlutterV2ray.parseFromURL(link) + return + } - let config: String = parsedConfig.getFullConfiguration() - - if config.isEmpty { + var parsedConfig: V2RayURL + do { + parsedConfig = try FlutterV2ray.parseFromURL(link) + } catch { + result(FlutterError(code: "PARSE_ERROR", message: "Failed to parse config: \(error)", details: nil)) + return + } + + let config = parsedConfig.getFullConfiguration() + + if config.isEmpty { result(FlutterError(code: "CONFIG_ERROR", message: "Invalid V2Ray config", details: nil)) - } - - v2rayPlugin.startV2Ray( - remark: parsedConfig.remark, - config: config, - blockedApps: nil, - bypassSubnets: nil, - proxyOnly: false + return + } + + v2rayPlugin.startV2Ray( + remark: parsedConfig.remark, + config: config, + blockedApps: nil, + bypassSubnets: nil, + proxyOnly: false ) { err in if let err = err { DispatchQueue.main.async { @@ -76,6 +85,7 @@ public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { } else { DispatchQueue.main.async { self.sendConnectionStatus(status: "connected") + self.isVpnRunning = true result(nil) } } @@ -96,6 +106,7 @@ public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { } else { DispatchQueue.main.async { self.sendConnectionStatus(status: "disconnected") + self.isVpnRunning = false result(nil) } } @@ -171,7 +182,7 @@ public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { } private func getConnectionStatus(result: @escaping FlutterResult) { - if v2rayPlugin.isRunning() == true { + if isVpnRunning == true { result("connected") } else { result("disconnected") diff --git a/lib/vpnclient_engine/core.dart b/lib/vpnclient_engine/core.dart new file mode 100644 index 00000000..12f1fec2 --- /dev/null +++ b/lib/vpnclient_engine/core.dart @@ -0,0 +1,394 @@ +import 'dart:async'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:dart_ping/dart_ping.dart'; + +import 'package:flutter/foundation.dart'; +import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter_platform_interface.dart'; +import 'package:flutter_v2ray/flutter_v2ray.dart'; +import 'package:rxdart/rxdart.dart'; + +enum ConnectionStatus { + connecting, + connected, + disconnected, + error, +} + +enum ErrorCode { + invalidCredentials, + serverUnavailable, + subscriptionExpired, + unknownError, +} + +enum ProxyType { + socks5, + http, +} + +enum Action { + block, + allow, + routeThroughVPN, + direct, + proxy, +} + +class Server { + final String address; + final int? latency; + final String? location; + final bool? isPreferred; + + Server({ + required this.address, + this.latency, + this.location, + this.isPreferred, + }); +} + +class SubscriptionDetails { + final DateTime? expiryDate; + final int? dataLimit; + final int? usedData; + + SubscriptionDetails({ + this.expiryDate, + this.dataLimit, + this.usedData, + }); +} + +class SessionStatistics { + final Duration? sessionDuration; + final int dataInBytes; + final int dataOutBytes; + + SessionStatistics({ + this.sessionDuration, + required this.dataInBytes, + required this.dataOutBytes, + }); +} + +class ErrorDetails { + final ErrorCode errorCode; + final String errorMessage; + + ErrorDetails({required this.errorCode, required this.errorMessage}); +} + +class ProxyConfig { + final ProxyType type; + final String address; + final int port; + final String? credentials; + + ProxyConfig({ + required this.type, + required this.address, + required this.port, + this.credentials, + }); +} + +class PingResult { + final int subscriptionIndex; + final int serverIndex; + final int latencyInMs; + + PingResult({ + required this.subscriptionIndex, + required this.serverIndex, + required this.latencyInMs, + }); +} + +class RoutingRule { + final String? appName; + final String? domain; + final String action; // proxy, direct, block + + RoutingRule({this.appName, this.domain, required this.action}); +} + +abstract class VpnCore { + Future connect({required int subscriptionIndex, required int serverIndex}); + Future disconnect(); + String getConnectionStatus(); + void setRoutingRules({required List rules}); + SessionStatistics getSessionStatistics(); + Future loadSubscriptions({required List subscriptionLinks}); + void pingServer({required int subscriptionIndex, required int index}); + List getServerList(); + void setAutoConnect({required bool enable}); + void setKillSwitch({required bool enable}); +} + +class V2RayCore implements VpnCore { + final FlutterV2ray _flutterV2ray = FlutterV2ray( + onStatusChanged: (status) { + // do something + }, + ); + List> _subscriptionServers = []; + List _subscriptions = []; + + final _connectionStatusSubject = BehaviorSubject(); + Stream get onConnectionStatusChanged => + _connectionStatusSubject.stream; + + final _errorSubject = BehaviorSubject(); + Stream get onError => _errorSubject.stream; + + final _serverSwitchedSubject = BehaviorSubject(); + Stream get onServerSwitched => _serverSwitchedSubject.stream; + + final _pingResultSubject = BehaviorSubject(); + Stream get onPingResult => _pingResultSubject.stream; + + final _subscriptionLoadedSubject = BehaviorSubject(); + Stream get onSubscriptionLoaded => + _subscriptionLoadedSubject.stream; + + final _dataUsageUpdatedSubject = BehaviorSubject(); + Stream get onDataUsageUpdated => + _dataUsageUpdatedSubject.stream; + + final _routingRulesAppliedSubject = BehaviorSubject>(); + Stream> get onRoutingRulesApplied => + _routingRulesAppliedSubject.stream; + + final _killSwitchTriggeredSubject = BehaviorSubject(); + Stream get onKillSwitchTriggered => _killSwitchTriggeredSubject.stream; + + void _emitError(ErrorCode code, String message) { + _errorSubject.add(ErrorDetails(errorCode: code, errorMessage: message)); + } + + void initialize() { + print('V2RayCore initialized'); + } + + void clearSubscriptions() { + _subscriptions.clear(); + print('All subscriptions cleared'); + } + + void addSubscription({required String subscriptionURL}) { + _subscriptions.add(subscriptionURL); + print('Subscription added: $subscriptionURL'); + } + + void addSubscriptions({required List subscriptionURLs}) { + _subscriptions.addAll(subscriptionURLs); + print('Subscriptions added: ${subscriptionURLs.join(", ")}'); + } + + Future updateSubscription({required int subscriptionIndex}) async { + if (subscriptionIndex < 0 || subscriptionIndex >= _subscriptions.length) { + print('Invalid subscription index'); + return; + } + + final url = _subscriptions[subscriptionIndex]; + print('Fetching subscription data from: $url'); + + try { + final response = await http.get(Uri.parse(url)); + + if (response.statusCode != 200) { + print('Failed to fetch subscription: HTTP ${response.statusCode}'); + return; + } + + final content = response.body.trim(); + + List servers = []; + + if (content.startsWith('[')) { + final jsonList = jsonDecode(content) as List; + for (var server in jsonList) { + servers.add(server.toString()); + } + print('Parsed JSON subscription: ${servers.length} servers loaded'); + } else { + servers = content + .split('\n') + .where((line) => line.trim().isNotEmpty) + .toList(); + print('Parsed NEWLINE subscription: ${servers.length} servers loaded'); + } + + while (_subscriptionServers.length <= subscriptionIndex) { + _subscriptionServers.add([]); + } + + _subscriptionServers[subscriptionIndex] = servers; + _subscriptionLoadedSubject.add(SubscriptionDetails()); + + print('Subscription #$subscriptionIndex servers updated successfully'); + } catch (e) { + print('Error updating subscription: $e'); + _emitError(ErrorCode.unknownError, 'Error updating subscription: $e'); + } + } + + @override + Future connect( + {required int subscriptionIndex, required int serverIndex}) async { + try { + if (subscriptionIndex < 0 || + subscriptionIndex >= _subscriptionServers.length) { + print('Invalid subscription index'); + return; + } + if (serverIndex < 0 || + serverIndex >= _subscriptionServers[subscriptionIndex].length) { + print('Invalid server index'); + return; + } + + await _flutterV2ray.initializeV2Ray(); + + final serverAddress = _subscriptionServers[subscriptionIndex][serverIndex]; + V2RayURL parser = FlutterV2ray.parseFromURL(serverAddress); + + _connectionStatusSubject.add(ConnectionStatus.connecting); + if (await _flutterV2ray.requestPermission()) { + _flutterV2ray.startV2Ray( + remark: parser.remark, + config: parser.getFullConfiguration(), + blockedApps: null, + bypassSubnets: null, + proxyOnly: false, + ); + } + _serverSwitchedSubject.add(serverAddress); + _connectionStatusSubject.add(ConnectionStatus.connected); + print('Successfully connected'); + } catch (e) { + _emitError(ErrorCode.unknownError, 'Error connecting: $e'); + _connectionStatusSubject.add(ConnectionStatus.error); + } + } + + @override + Future disconnect() async { + _flutterV2ray.stopV2Ray(); + _connectionStatusSubject.add(ConnectionStatus.disconnected); + print('Disconnected successfully'); + } + + @override + void setRoutingRules({required List rules}) { + for (var rule in rules) { + if (rule.appName != null) { + print('Routing rule for app ${rule.appName}: ${rule.action}'); + } else if (rule.domain != null) { + print('Routing rule for domain ${rule.domain}: ${rule.action}'); + } + } + } + + @override + void pingServer({required int subscriptionIndex, required int index}) async { + if (subscriptionIndex < 0 || + subscriptionIndex >= _subscriptionServers.length) { + print('Invalid subscription index'); + _emitError(ErrorCode.unknownError, 'Invalid subscription index'); + return; + } + if (index < 0 || + index >= _subscriptionServers[subscriptionIndex].length) { + print('Invalid server index'); + _emitError(ErrorCode.unknownError, 'Invalid server index'); + return; + } + final serverAddress = _subscriptionServers[subscriptionIndex][index]; + print('Pinging server: $serverAddress'); + try { + final ping = Ping(serverAddress, count: 3); + final pingData = + await ping.stream.firstWhere((data) => data.response != null); + if (pingData.response != null) { + final latency = pingData.response!.time!.inMilliseconds; + final result = PingResult( + subscriptionIndex: subscriptionIndex, + serverIndex: index, + latencyInMs: latency); + _pingResultSubject.add(result); + print( + 'Ping result: sub=${result.subscriptionIndex}, server=${result.serverIndex}, latency=${result.latencyInMs} ms'); + } else { + print('Ping failed: No response'); + _pingResultSubject.add(PingResult( + subscriptionIndex: subscriptionIndex, + serverIndex: index, + latencyInMs: -1)); + _emitError(ErrorCode.serverUnavailable, 'Ping failed: No response'); + } + } catch (e) { + print('Ping error: $e'); + _pingResultSubject.add(PingResult( + subscriptionIndex: subscriptionIndex, + serverIndex: index, + latencyInMs: -1)); + _emitError(ErrorCode.unknownError, 'Ping error: $e'); + } + } + + @override + String getConnectionStatus() { + return 'disconnected'; + } + + @override + List getServerList() { + return [ + Server( + address: 'server1.com', latency: 50, location: 'USA', isPreferred: true), + Server( + address: 'server2.com', + latency: 100, + location: 'UK', + isPreferred: false), + Server( + address: 'server3.com', + latency: 75, + location: 'Canada', + isPreferred: false), + ]; + } + + @override + Future loadSubscriptions({required List subscriptionLinks}) async { + print('loadSubscriptions: ${subscriptionLinks.join(", ")}'); + _subscriptions.addAll(subscriptionLinks); + print('Subscriptions added: ${subscriptionLinks.join(", ")}'); + for (var index = 0; index < subscriptionLinks.length; index++) { + await updateSubscription(subscriptionIndex: index); + } + } + + @override + SessionStatistics getSessionStatistics() { + return SessionStatistics( + sessionDuration: Duration(minutes: 30), + dataInBytes: 1024 * 1024 * 100, + dataOutBytes: 1024 * 1024 * 50, + ); + } + + @override + void setAutoConnect({required bool enable}) { + print('setAutoConnect: $enable'); + } + + @override + void setKillSwitch({required bool enable}) { + print('setKillSwitch: $enable'); + } +} \ No newline at end of file diff --git a/lib/vpnclient_engine/engine.dart b/lib/vpnclient_engine/engine.dart index b09d4b55..24ba0de1 100644 --- a/lib/vpnclient_engine/engine.dart +++ b/lib/vpnclient_engine/engine.dart @@ -2,11 +2,10 @@ import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:dart_ping/dart_ping.dart'; -import 'package:vpnclient_engine_flutter/vpnclient_engine/server_connection.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter_v2ray/flutter_v2ray.dart'; import 'package:rxdart/rxdart.dart'; import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter.dart'; +import 'package:vpnclient_engine_flutter/vpnclient_engine/core.dart'; enum ConnectionStatus { @@ -113,18 +112,11 @@ class RoutingRule { final String action; // proxy, direct, block RoutingRule({this.appName, this.domain, required this.action}); - } -final FlutterV2ray flutterV2ray = FlutterV2ray( - onStatusChanged: (status) { - print("status changed: $status"); - }, - - ); class VPNclientEngine { static List> _subscriptionServers = []; static Map _connections = {}; @@ -156,6 +148,8 @@ class VPNclientEngine { static final _killSwitchTriggeredSubject = BehaviorSubject(); static Stream get onKillSwitchTriggered => _killSwitchTriggeredSubject.stream; + static VpnCore? _vpnCore; + static void _emitError(ErrorCode code, String message) { _errorSubject.add(ErrorDetails(errorCode: code, errorMessage: message)); @@ -167,6 +161,10 @@ class VPNclientEngine { static void initialize() { print('VPNclient Engine initialized'); + if (_vpnCore == null) { + // Default core is V2Ray + _vpnCore = V2RayCore(); + } } static void ClearSubscriptions() { @@ -235,56 +233,28 @@ class VPNclientEngine { } } - static Future connect({required int subscriptionIndex, required int serverIndex}) async { - print('Connecting to subscription $subscriptionIndex, server $serverIndex...'); - - if (subscriptionIndex < 0 || subscriptionIndex >= _subscriptionServers.length) { - _emitError(ErrorCode.unknownError, 'Invalid subscription index'); + static Future connect({ + required int subscriptionIndex, + required int serverIndex, + ProxyConfig? proxyConfig, + }) async { + if (_vpnCore == null) { + _emitError(ErrorCode.unknownError, 'VPN core is not initialized.'); return; } - if (serverIndex < 0 || serverIndex >= _subscriptionServers[subscriptionIndex].length) { - _emitError(ErrorCode.unknownError, 'Invalid server index'); - return; - } - - _connectionStatusSubject.add(ConnectionStatus.connecting); - VpnclientEngineFlutterPlatform.instance.connect(subscriptionIndex: subscriptionIndex, serverIndex: serverIndex).then((_) { - _connectionStatusSubject.add(ConnectionStatus.connected); - }).catchError((error) { - _connectionStatusSubject.add(ConnectionStatus.error); - _emitError(ErrorCode.unknownError, 'Error on connect: $error'); - }); - - //Get server info - String link = _subscriptionServers[subscriptionIndex][serverIndex]; - V2RayURL parser = FlutterV2ray.parseFromURL(link); - _serverSwitchedSubject.add(parser.remark ?? "null"); - - print('Connecting to server: $link'); + await _vpnCore!.connect(Server(address: _subscriptionServers[subscriptionIndex][serverIndex]), proxyConfig); } - static Future disconnect() async { - print('Disconnecting...'); - if(_connections.isEmpty){ - print('Noting to disconnect.'); - _connectionStatusSubject.add(ConnectionStatus.disconnected); + static Future disconnect() async { + if (_vpnCore == null) { + _emitError(ErrorCode.unknownError, 'VPN core is not initialized.'); return; } - - VpnclientEngineFlutterPlatform.instance.disconnect().then((_) { - _connectionStatusSubject.add(ConnectionStatus.disconnected); - }).catchError((error) { - _connectionStatusSubject.add(ConnectionStatus.error); - _emitError(ErrorCode.unknownError, 'Error on disconnect: $error'); - }); -/* - try { - - await flutterV2ray.stopV2Ray(); - _connections.clear(); - _connectionStatusSubject.add(ConnectionStatus.disconnected); + + await _vpnCore!.disconnect(); + /* try { } catch (e) { _connectionStatusSubject.add(ConnectionStatus.error); @@ -292,7 +262,7 @@ class VPNclientEngine { print('Error disconnecting: $e'); }*/ - print('Disconnected successfully'); + } @@ -355,10 +325,9 @@ class VPNclientEngine { print('loadSubscriptions: ${subscriptionLinks.join(", ")}'); _subscriptions.addAll(subscriptionLinks); for (var element in subscriptionLinks) { - addSubscription(subscriptionURL: element); - await updateSubscription(subscriptionIndex: _subscriptions.length-1); - } - print('Subscriptions added: ${subscriptionLinks.join(", ")}'); + addSubscription(subscriptionURL: element); + await updateSubscription(subscriptionIndex: _subscriptions.length - 1); + } } static SessionStatistics getSessionStatistics() { From 7531b1019e8c17317a90bdd1b817f884118c4961 Mon Sep 17 00:00:00 2001 From: "anton.v.dodonov" Date: Sun, 27 Apr 2025 20:03:16 +0000 Subject: [PATCH 67/77] imp --- .../engine/VpnclientEngineFlutterPlugin.kt | 75 +++++++++++++++++++ lib/vpnclient_engine_flutter.dart | 55 +++++++++++++- 2 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 android/src/main/kotlin/click/vpnclient/engine/VpnclientEngineFlutterPlugin.kt diff --git a/android/src/main/kotlin/click/vpnclient/engine/VpnclientEngineFlutterPlugin.kt b/android/src/main/kotlin/click/vpnclient/engine/VpnclientEngineFlutterPlugin.kt new file mode 100644 index 00000000..ffb465df --- /dev/null +++ b/android/src/main/kotlin/click/vpnclient/engine/VpnclientEngineFlutterPlugin.kt @@ -0,0 +1,75 @@ +import 'dart:async'; +import 'package:flutter/services.dart'; +import 'package:vpnclient_engine_flutter/vpnclient_engine/core.dart'; +import 'package:vpnclient_engine_flutter/vpnclient_engine/engine.dart'; +import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter.dart'; +import 'package:flutter_v2ray/flutter_v2ray.dart'; + +class AndroidVpnclientEngineFlutter extends VpnclientEngineFlutterPlatform { + static const MethodChannel _channel = MethodChannel('vpnclient_engine_flutter'); + final FlutterV2ray flutterV2ray = FlutterV2ray( + onStatusChanged: (status) { + switch (status) { + case V2RayStatus.connected: + _connectionStatusSubject.add(ConnectionStatus.connected); + break; + case V2RayStatus.connecting: + _connectionStatusSubject.add(ConnectionStatus.connecting); + break; + case V2RayStatus.disconnected: + _connectionStatusSubject.add(ConnectionStatus.disconnected); + break; + case V2RayStatus.error: + _connectionStatusSubject.add(ConnectionStatus.error); + break; + } + }, + ); + static final _connectionStatusSubject = StreamController.broadcast(); + + + static void registerWith() { + VpnclientEngineFlutterPlatform.instance = AndroidVpnclientEngineFlutter(); + } + + @override + Future connect(String url) async { + try { + _connectionStatusSubject.add(ConnectionStatus.connecting); + final parser = FlutterV2ray.parseFromURL(url); + if (await flutterV2ray.requestPermission()) { + await flutterV2ray.startV2Ray( + remark: parser.remark, + config: parser.getFullConfiguration(), + blockedApps: null, + bypassSubnets: null, + proxyOnly: false, + ); + } else { + _connectionStatusSubject.add(ConnectionStatus.error); + VPNclientEngine.emitError(ErrorCode.unknownError, 'Permission denied'); + } + } catch (e) { + _connectionStatusSubject.add(ConnectionStatus.error); + VPNclientEngine.emitError(ErrorCode.unknownError, 'Error: $e'); + } + } + + @override + Future disconnect() async { + try { + _connectionStatusSubject.add(ConnectionStatus.disconnected); + await flutterV2ray.stopV2Ray(); + } catch (e) { + VPNclientEngine.emitError(ErrorCode.unknownError, 'Error: $e'); + } + } + @override + Future requestPermission() async { + try{ + return await flutterV2ray.requestPermission(); + } catch (e){ + return false; + } + } +} \ No newline at end of file diff --git a/lib/vpnclient_engine_flutter.dart b/lib/vpnclient_engine_flutter.dart index 7516d99f..201cff83 100644 --- a/lib/vpnclient_engine_flutter.dart +++ b/lib/vpnclient_engine_flutter.dart @@ -1,4 +1,57 @@ +import 'dart:async'; +import 'dart:io' show Platform; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:vpnclient_engine_flutter/platforms/android.dart'; + +import 'vpnclient_engine/engine.dart'; + +export 'vpnclient_engine/core.dart'; export 'vpnclient_engine/engine.dart'; -class VpnclientEngineFlutter { +abstract class VpnclientEngineFlutterPlatform { + static VpnclientEngineFlutterPlatform? _instance; + + static VpnclientEngineFlutterPlatform get instance { + if (_instance == null) { + if (Platform.isAndroid) { + _instance = AndroidVpnclientEngineFlutter(); + } else if (Platform.isIOS) { + _instance = IosVpnclientEngineFlutter(); + } else { + throw UnimplementedError('Platform not supported'); + } + } + return _instance!; + } + + Future getPlatformVersion(); + + Future connect({ + required String url, + }); + + Future disconnect(); +} + +class IosVpnclientEngineFlutter extends VpnclientEngineFlutterPlatform { + static const MethodChannel _channel = + MethodChannel('vpnclient_engine_flutter'); + + @override + Future getPlatformVersion() async { + final version = await _channel.invokeMethod('getPlatformVersion'); + return version; + } + + @override + Future connect({required String url}) async { + await _channel.invokeMethod('connect', {'url': url}); + } + + @override + Future disconnect() async { + await _channel.invokeMethod('disconnect'); + } } \ No newline at end of file From ffc1e422741924b0a5f378e14f38907a1185fe7c Mon Sep 17 00:00:00 2001 From: "anton.v.dodonov" Date: Sun, 27 Apr 2025 20:06:33 +0000 Subject: [PATCH 68/77] openvpn --- .../engine/VpnclientEngineFlutterPlugin.kt | 131 ++++++++++++------ .../VpnclientEngineFlutterPlugin.swift | 86 +++++++----- lib/vpnclient_engine/core.dart | 22 ++- lib/vpnclient_engine/engine.dart | 35 +++-- lib/vpnclient_engine_flutter.dart | 33 +++-- 5 files changed, 197 insertions(+), 110 deletions(-) diff --git a/android/src/main/kotlin/click/vpnclient/engine/VpnclientEngineFlutterPlugin.kt b/android/src/main/kotlin/click/vpnclient/engine/VpnclientEngineFlutterPlugin.kt index ffb465df..9faa0ea2 100644 --- a/android/src/main/kotlin/click/vpnclient/engine/VpnclientEngineFlutterPlugin.kt +++ b/android/src/main/kotlin/click/vpnclient/engine/VpnclientEngineFlutterPlugin.kt @@ -1,75 +1,114 @@ import 'dart:async'; -import 'package:flutter/services.dart'; + import 'package:vpnclient_engine_flutter/vpnclient_engine/core.dart'; import 'package:vpnclient_engine_flutter/vpnclient_engine/engine.dart'; import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_v2ray/flutter_v2ray.dart'; + class AndroidVpnclientEngineFlutter extends VpnclientEngineFlutterPlatform { static const MethodChannel _channel = MethodChannel('vpnclient_engine_flutter'); - final FlutterV2ray flutterV2ray = FlutterV2ray( - onStatusChanged: (status) { - switch (status) { - case V2RayStatus.connected: - _connectionStatusSubject.add(ConnectionStatus.connected); - break; - case V2RayStatus.connecting: - _connectionStatusSubject.add(ConnectionStatus.connecting); - break; - case V2RayStatus.disconnected: - _connectionStatusSubject.add(ConnectionStatus.disconnected); - break; - case V2RayStatus.error: - _connectionStatusSubject.add(ConnectionStatus.error); - break; - } - }, - ); - static final _connectionStatusSubject = StreamController.broadcast(); + final FlutterV2ray _flutterV2ray = FlutterV2ray( + onStatusChanged: (status) { + switch (status) { + case V2RayStatus.connected: + _connectionStatusSubject.add(ConnectionStatus.connected); + break; + case V2RayStatus.connecting: + _connectionStatusSubject.add(ConnectionStatus.connecting); + break; + case V2RayStatus.disconnected: + _connectionStatusSubject.add(ConnectionStatus.disconnected); + break; + case V2RayStatus.error: + _connectionStatusSubject.add(ConnectionStatus.error); + break; + } + }); + static final _connectionStatusSubject = StreamController.broadcast(); + static final _statusStream = _connectionStatusSubject.stream.asBroadcastStream(); static void registerWith() { VpnclientEngineFlutterPlatform.instance = AndroidVpnclientEngineFlutter(); } @override - Future connect(String url) async { - try { + Future connect(String url, VpnCore core) async { + try { + if (url.startsWith('vless://') || url.startsWith('vmess://')) { _connectionStatusSubject.add(ConnectionStatus.connecting); - final parser = FlutterV2ray.parseFromURL(url); - if (await flutterV2ray.requestPermission()) { - await flutterV2ray.startV2Ray( - remark: parser.remark, - config: parser.getFullConfiguration(), - blockedApps: null, - bypassSubnets: null, - proxyOnly: false, - ); - } else { - _connectionStatusSubject.add(ConnectionStatus.error); - VPNclientEngine.emitError(ErrorCode.unknownError, 'Permission denied'); - } - } catch (e) { - _connectionStatusSubject.add(ConnectionStatus.error); - VPNclientEngine.emitError(ErrorCode.unknownError, 'Error: $e'); + await _startV2Ray(url); + } else if (url.startsWith('wg://')) { + _connectionStatusSubject.add(ConnectionStatus.connecting); + await _startWireguard(url); + } else if (url.startsWith('ovpn://')){ + _connectionStatusSubject.add(ConnectionStatus.connecting); + await _startOpenvpn(url); + }else { + VPNclientEngine.emitError(ErrorCode.unknownError, 'Invalid URL protocol'); + _connectionStatusSubject.add(ConnectionStatus.error); } + } catch (e) { + VPNclientEngine.emitError(ErrorCode.unknownError, 'Error: $e'); + _connectionStatusSubject.add(ConnectionStatus.error); + } + } + + Future _startV2Ray(String url) async { + final parser = FlutterV2ray.parseFromURL(url); + if (await _flutterV2ray.requestPermission()) { + await _flutterV2ray.startV2Ray( + remark: parser.remark, + config: parser.getFullConfiguration(), + blockedApps: null, + bypassSubnets: null, + proxyOnly: false, + ); + } else { + VPNclientEngine.emitError(ErrorCode.unknownError, 'Permission denied'); + _connectionStatusSubject.add(ConnectionStatus.error); + } } + Future _startWireguard(String url) async { + final parser = FlutterV2ray.parseFromURL(url); + if (await _flutterV2ray.requestPermission()) { + await _flutterV2ray.startV2Ray( + remark: parser.remark, + config: parser.getFullConfiguration(), + blockedApps: null, + bypassSubnets: null, + proxyOnly: false, + ); + } else { + VPNclientEngine.emitError(ErrorCode.unknownError, 'Permission denied'); + _connectionStatusSubject.add(ConnectionStatus.error); + } + } + Future _startOpenvpn(String url) async { + VPNclientEngine.emitError(ErrorCode.unknownError, 'OpenVPN not implemented yet'); + _connectionStatusSubject.add(ConnectionStatus.error); + } @override Future disconnect() async { try { _connectionStatusSubject.add(ConnectionStatus.disconnected); - await flutterV2ray.stopV2Ray(); + if(await _flutterV2ray.isRunning()){ + await _flutterV2ray.stopV2Ray(); + } } catch (e) { - VPNclientEngine.emitError(ErrorCode.unknownError, 'Error: $e'); + VPNclientEngine.emitError(ErrorCode.unknownError, 'Error: $e'); } } - @override + + @override Future requestPermission() async { - try{ - return await flutterV2ray.requestPermission(); - } catch (e){ - return false; - } + try { + return await _flutterV2ray.requestPermission(); + } catch (e) { + return false; + } } } \ No newline at end of file diff --git a/ios/Classes/VpnclientEngineFlutterPlugin.swift b/ios/Classes/VpnclientEngineFlutterPlugin.swift index c5f287a6..5fbf06d9 100644 --- a/ios/Classes/VpnclientEngineFlutterPlugin.swift +++ b/ios/Classes/VpnclientEngineFlutterPlugin.swift @@ -1,9 +1,7 @@ -enum VpnError: Error { - case missingConfig - case startFailed(String) - case stopFailed(String) -} - +import Foundation +import NetworkExtension +import Flutter +import FlutterV2ray /// Plugin class to handle VPN connections in the Flutter app. import Flutter import UIKit @@ -18,11 +16,11 @@ public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { registrar.addMethodCallDelegate(instance, channel: channel) instance.channel = channel } - private var channel: FlutterMethodChannel? - - private var v2rayPlugin = FlutterV2rayPlugin.sharedInstance() - + private let v2rayPlugin = FlutterV2rayPlugin.sharedInstance() + private var isVpnRunning = false + private var currentConfig: V2RayURL? + private var tunnelManager: NETunnelProviderManager? private var isVpnRunning: Bool = false @@ -54,32 +52,47 @@ public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing or invalid config", details: nil)) return } - - var parsedConfig: V2RayURL - do { - parsedConfig = try FlutterV2ray.parseFromURL(link) - } catch { - result(FlutterError(code: "PARSE_ERROR", message: "Failed to parse config: \(error)", details: nil)) - return - } - - let config = parsedConfig.getFullConfiguration() - - if config.isEmpty { + if link.starts(with: "vless://") || link.starts(with: "vmess://") { + var parsedConfig: V2RayURL + do { + parsedConfig = try FlutterV2ray.parseFromURL(link) + } catch { + sendError(errorCode: "PARSE_ERROR", errorMessage: "Failed to parse config: \(error)") + result(FlutterError(code: "PARSE_ERROR", message: "Failed to parse config: \(error)", details: nil)) + return + } + + currentConfig = parsedConfig + startV2Ray(config: parsedConfig, result: result) + } else if link.starts(with: "wg://") { + var parsedConfig: V2RayURL + do { + parsedConfig = try FlutterV2ray.parseFromURL(link) + } catch { + sendError(errorCode: "PARSE_ERROR", errorMessage: "Failed to parse config: \(error)") + result(FlutterError(code: "PARSE_ERROR", message: "Failed to parse config: \(error)", details: nil)) + return + } + + currentConfig = parsedConfig + startV2Ray(config: parsedConfig, result: result) + } else { + sendError(errorCode: "UNKNOWN_PROTOCOL", errorMessage: "Unknown protocol") + result(FlutterError(code: "UNKNOWN_PROTOCOL", message: "Unknown protocol", details: nil)) + } + } + + private func startV2Ray(config: V2RayURL, result: @escaping FlutterResult) { + let fullConfig = config.getFullConfiguration() + if fullConfig.isEmpty { + sendError(errorCode: "CONFIG_ERROR", errorMessage: "Invalid V2Ray config") result(FlutterError(code: "CONFIG_ERROR", message: "Invalid V2Ray config", details: nil)) - return - } - - v2rayPlugin.startV2Ray( - remark: parsedConfig.remark, - config: config, - blockedApps: nil, - bypassSubnets: nil, - proxyOnly: false - ) { err in + return + } + v2rayPlugin.startV2Ray(remark: config.remark, config: fullConfig, blockedApps: nil, bypassSubnets: nil, proxyOnly: false) { err in if let err = err { DispatchQueue.main.async { - self.sendError(errorCode: "VPN_START_FAILED", errorMessage: err) + self.sendError(errorCode: "VPN_ERROR", errorMessage: err) result(FlutterError(code: "VPN_START_FAILED", message: "Failed to start VPN: \(err)", details: nil)) } } else { @@ -90,7 +103,7 @@ public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { } } } - } + } private func sendError(errorCode: String, errorMessage: String) { channel?.invokeMethod("onError", arguments: ["errorCode": errorCode, "errorMessage": errorMessage]) @@ -98,12 +111,13 @@ public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { private func disconnect(result: @escaping FlutterResult) { v2rayPlugin.stopV2Ray { err in + self.currentConfig = nil if let err = err { DispatchQueue.main.async { - self.sendError(errorCode: "VPN_STOP_FAILED", errorMessage: err) + self.sendError(errorCode: "VPN_ERROR", errorMessage: err) result(FlutterError(code: "VPN_STOP_FAILED", message: "Failed to stop VPN: \(err)", details: nil)) } - } else { + } else if self.isVpnRunning { DispatchQueue.main.async { self.sendConnectionStatus(status: "disconnected") self.isVpnRunning = false diff --git a/lib/vpnclient_engine/core.dart b/lib/vpnclient_engine/core.dart index 12f1fec2..a58fa861 100644 --- a/lib/vpnclient_engine/core.dart +++ b/lib/vpnclient_engine/core.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:dart_ping/dart_ping.dart'; +import 'package:vpnclient_engine_flutter/vpnclient_engine/core.dart'; import 'package:flutter/foundation.dart'; import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter_platform_interface.dart'; @@ -114,6 +115,16 @@ class RoutingRule { RoutingRule({this.appName, this.domain, required this.action}); } +class WireGuardCore implements VpnCore { + @override + Future connect({required int subscriptionIndex, required int serverIndex}) { + // TODO: implement connect + throw UnimplementedError(); + } + + +} + abstract class VpnCore { Future connect({required int subscriptionIndex, required int serverIndex}); Future disconnect(); @@ -127,6 +138,8 @@ abstract class VpnCore { void setKillSwitch({required bool enable}); } + + class V2RayCore implements VpnCore { final FlutterV2ray _flutterV2ray = FlutterV2ray( onStatusChanged: (status) { @@ -391,4 +404,11 @@ class V2RayCore implements VpnCore { void setKillSwitch({required bool enable}) { print('setKillSwitch: $enable'); } -} \ No newline at end of file +} + +class OpenVPNCore implements VpnCore { + @override + Future connect({required int subscriptionIndex, required int serverIndex}) { + // TODO: implement connect + throw UnimplementedError(); + } diff --git a/lib/vpnclient_engine/engine.dart b/lib/vpnclient_engine/engine.dart index 24ba0de1..91b901ea 100644 --- a/lib/vpnclient_engine/engine.dart +++ b/lib/vpnclient_engine/engine.dart @@ -2,12 +2,12 @@ import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:dart_ping/dart_ping.dart'; -import 'package:flutter/foundation.dart'; import 'package:rxdart/rxdart.dart'; import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter.dart'; import 'package:vpnclient_engine_flutter/vpnclient_engine/core.dart'; + enum ConnectionStatus { connecting, connected, @@ -148,13 +148,15 @@ class VPNclientEngine { static final _killSwitchTriggeredSubject = BehaviorSubject(); static Stream get onKillSwitchTriggered => _killSwitchTriggeredSubject.stream; - static VpnCore? _vpnCore; + static VpnCore _vpnCore = V2RayCore(); + static void _emitError(ErrorCode code, String message) { _errorSubject.add(ErrorDetails(errorCode: code, errorMessage: message)); } + static List _subscriptions = []; @@ -238,15 +240,27 @@ class VPNclientEngine { required int serverIndex, ProxyConfig? proxyConfig, }) async { - if (_vpnCore == null) { - _emitError(ErrorCode.unknownError, 'VPN core is not initialized.'); + final url = _subscriptionServers[subscriptionIndex][serverIndex]; + + if (url.startsWith('vless://') || url.startsWith('vmess://') || url.startsWith('v2ray://')) { + _vpnCore = V2RayCore(); + } else if (url.startsWith('wg://')) { + _vpnCore = WireGuardCore(); + } else if (url.startsWith('openvpn://') || url.endsWith('.ovpn')) { + _vpnCore = OpenVPNCore(); + } else { + _emitError(ErrorCode.unknownError, 'Unsupported URL format'); + return; + } + if (serverIndex < 0 || serverIndex >= _subscriptionServers[subscriptionIndex].length) { + _emitError(ErrorCode.unknownError, 'Invalid server index'); return; } - await _vpnCore!.connect(Server(address: _subscriptionServers[subscriptionIndex][serverIndex]), proxyConfig); + await _vpnCore.connect(Server(address: _subscriptionServers[subscriptionIndex][serverIndex]), proxyConfig); } - + static Future disconnect() async { if (_vpnCore == null) { _emitError(ErrorCode.unknownError, 'VPN core is not initialized.'); @@ -254,13 +268,6 @@ class VPNclientEngine { } await _vpnCore!.disconnect(); - /* try { - - } catch (e) { - _connectionStatusSubject.add(ConnectionStatus.error); - _emitError(ErrorCode.unknownError, 'Error disconnecting: $e'); - print('Error disconnecting: $e'); - }*/ } @@ -326,7 +333,7 @@ class VPNclientEngine { _subscriptions.addAll(subscriptionLinks); for (var element in subscriptionLinks) { addSubscription(subscriptionURL: element); - await updateSubscription(subscriptionIndex: _subscriptions.length - 1); + await updateSubscription(subscriptionIndex: _subscriptions.indexOf(element)); } } diff --git a/lib/vpnclient_engine_flutter.dart b/lib/vpnclient_engine_flutter.dart index 201cff83..713301a1 100644 --- a/lib/vpnclient_engine_flutter.dart +++ b/lib/vpnclient_engine_flutter.dart @@ -1,7 +1,10 @@ import 'dart:async'; import 'dart:io' show Platform; +import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:vpnclient_engine_flutter/platforms/ios.dart'; import 'package:flutter/services.dart'; import 'package:vpnclient_engine_flutter/platforms/android.dart'; @@ -20,7 +23,11 @@ abstract class VpnclientEngineFlutterPlatform { } else if (Platform.isIOS) { _instance = IosVpnclientEngineFlutter(); } else { - throw UnimplementedError('Platform not supported'); + _instance = VpnclientEngineFlutterPlatform(); + print( + 'VPNclientEngineFlutter: Warning: Platform not yet supported, fallback to default implementation'); + print('Please report this platform ${Platform.operatingSystem} on https://github.com/VPNclient/vpnclient_engine_flutter/issues'); + } } return _instance!; @@ -33,25 +40,25 @@ abstract class VpnclientEngineFlutterPlatform { }); Future disconnect(); -} -class IosVpnclientEngineFlutter extends VpnclientEngineFlutterPlatform { - static const MethodChannel _channel = - MethodChannel('vpnclient_engine_flutter'); + void sendStatus(ConnectionStatus status) { + print("default: $status"); + } - @override - Future getPlatformVersion() async { - final version = await _channel.invokeMethod('getPlatformVersion'); - return version; + void sendError(ErrorCode errorCode, String errorMessage) { + print("default: $errorCode $errorMessage"); } +} + +class VpnclientEngineFlutter extends VpnclientEngineFlutterPlatform { @override Future connect({required String url}) async { - await _channel.invokeMethod('connect', {'url': url}); + return Future.value(); } @override - Future disconnect() async { - await _channel.invokeMethod('disconnect'); + Future getPlatformVersion() async { + return "Platform not yet supported"; } -} \ No newline at end of file +} From 799f78025bd063f189888273197315a49d1b201c Mon Sep 17 00:00:00 2001 From: "anton.v.dodonov" Date: Sun, 27 Apr 2025 20:28:47 +0000 Subject: [PATCH 69/77] react-native --- VPNclient_react_native/index.js | 3 + VPNclient_react_native/metro.config.js | 11 + VPNclient_react_native/package-lock.json | 14064 ++++++++++++++++ VPNclient_react_native/package.json | 42 + .../src/native/android/VpnClientModule.java | 90 + .../src/native/android/VpnClientPackage.java | 21 + .../src/native/ios/VpnClientManager.m | 123 + .../src/screens/HomeScreen.js | 106 + VPNclient_react_native/src/vpn/VpnManager.js | 134 + 9 files changed, 14594 insertions(+) create mode 100644 VPNclient_react_native/index.js create mode 100644 VPNclient_react_native/metro.config.js create mode 100644 VPNclient_react_native/package-lock.json create mode 100644 VPNclient_react_native/package.json create mode 100644 VPNclient_react_native/src/native/android/VpnClientModule.java create mode 100644 VPNclient_react_native/src/native/android/VpnClientPackage.java create mode 100644 VPNclient_react_native/src/native/ios/VpnClientManager.m create mode 100644 VPNclient_react_native/src/screens/HomeScreen.js create mode 100644 VPNclient_react_native/src/vpn/VpnManager.js diff --git a/VPNclient_react_native/index.js b/VPNclient_react_native/index.js new file mode 100644 index 00000000..e1a30337 --- /dev/null +++ b/VPNclient_react_native/index.js @@ -0,0 +1,3 @@ +import VpnManager from './src/vpn/VpnManager'; + +export default VpnManager; \ No newline at end of file diff --git a/VPNclient_react_native/metro.config.js b/VPNclient_react_native/metro.config.js new file mode 100644 index 00000000..96a7040f --- /dev/null +++ b/VPNclient_react_native/metro.config.js @@ -0,0 +1,11 @@ +const { getDefaultConfig } = require("metro-config"); + +/** + * Metro configuration + * https://facebook.github.io/metro/docs/configuration + */ + +module.exports = (async () => { + const config = await getDefaultConfig(__dirname); + return config; +})(); \ No newline at end of file diff --git a/VPNclient_react_native/package-lock.json b/VPNclient_react_native/package-lock.json new file mode 100644 index 00000000..7c3c7764 --- /dev/null +++ b/VPNclient_react_native/package-lock.json @@ -0,0 +1,14064 @@ +{ + "name": "VPNclient", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "VPNclient", + "version": "0.0.1", + "dependencies": { + "@react-native-community/netinfo": "^9.4.1", + "@react-navigation/native": "^6.1.9", + "@react-navigation/stack": "^6.3.20", + "react": "18.2.0", + "react-native": "0.73.1", + "react-native-safe-area-context": "^4.8.2", + "react-native-screens": "^3.29.0", + "react-native-svg-transformer": "^1.5.0" + }, + "devDependencies": { + "@babel/core": "^7.20.0", + "@babel/preset-env": "^7.20.0", + "@babel/runtime": "^7.20.0", + "@react-native-community/cli": "^12.0.0", + "@react-native/babel-preset": "0.73.18", + "@react-native/eslint-config": "0.73.2", + "@react-native/metro-config": "0.73.2", + "@react-native/typescript-config": "0.73.1", + "@types/react": "^18.2.6", + "@types/react-test-renderer": "^18.0.0", + "babel-jest": "^29.6.3", + "eslint": "^8.19.0", + "jest": "^29.6.3", + "prettier": "2.8.8", + "react-test-renderer": "18.2.0", + "typescript": "5.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", + "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/eslint-parser": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.27.0.tgz", + "integrity": "sha512-dtnzmSjXfgL/HDgMcmsLSzyGbEosi4DrGWoCNfuI+W4IkVJw6izpTe7LtOdwAXnkDqw5yweboYCTkM2rQizCng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0", + "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", + "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz", + "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.8", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz", + "integrity": "sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/helper-replace-supers": "^7.26.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/traverse": "^7.27.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.0.tgz", + "integrity": "sha512-fO8l08T76v48BhpNRW/nQ0MxfnSdoSKUJBMjubOAYffsVuGG5qOfMq7N6Es7UJvi7Y8goXXo07EfcHZXDPuELQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "regexpu-core": "^6.2.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz", + "integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", + "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", + "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", + "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-wrap-function": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", + "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/traverse": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", + "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", + "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", + "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", + "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", + "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", + "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", + "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", + "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.", + "license": "MIT", + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-default-from": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.25.9.tgz", + "integrity": "sha512-ykqgwNfSnNOB+C8fV5X4mG3AVmvu+WVxcaU9xHHtBb7PCrPeweMmPjGsn8eMaeJg6SJuoUuZENeeSWaarWqonQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", + "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-catch-binding": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", + "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead.", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-default-from": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.25.9.tgz", + "integrity": "sha512-9MhJ/SMTsVqsd69GyQg89lYR4o9T+oDGv5F6IsigxxqFVOyR/IflDLYP8WDI1l8fkhNGGktqkvL5qwNCtGEpgQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.26.0.tgz", + "integrity": "sha512-B+O2DnPc0iG+YXFqOxv2WNuNU97ToWjOomUQ78DouOENWUaM5sVrmet9mcomUGQFwpJd//gvUagXBSdzO1fRKg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", + "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", + "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz", + "integrity": "sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-remap-async-to-generator": "^7.25.9", + "@babel/traverse": "^7.26.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", + "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz", + "integrity": "sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.0.tgz", + "integrity": "sha512-u1jGphZ8uDI2Pj/HJj6YQ6XQLZCNjOlprjxB5SVz6rq2T6SwAR+CdrWK0CP7F+9rDVMXdB0+r6Am5G5aobOjAQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", + "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", + "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", + "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/traverse": "^7.25.9", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", + "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/template": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", + "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", + "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", + "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", + "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", + "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", + "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.26.5.tgz", + "integrity": "sha512-eGK26RsbIkYUns3Y8qKl362juDDYK+wEdPGHGrhzUl6CewZFo55VZ7hg+CyMFU4dd5QQakBN86nBMpRsFpRvbQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/plugin-syntax-flow": "^7.26.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.26.9.tgz", + "integrity": "sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", + "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", + "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", + "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", + "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", + "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", + "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", + "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", + "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", + "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", + "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.26.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz", + "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", + "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", + "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", + "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", + "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", + "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", + "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", + "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", + "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz", + "integrity": "sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz", + "integrity": "sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", + "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", + "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.0.tgz", + "integrity": "sha512-LX/vCajUJQDqE7Aum/ELUMZAY19+cDpghxrnyt5I1tV6X5PyC86AOoWXWFYFeIvauyeSA6/ktn4tQVn/3ZifsA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", + "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", + "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.26.10.tgz", + "integrity": "sha512-NWaL2qG6HRpONTnj4JvDU6th4jYeZOJgu3QhmFTCihib0ermtOJqktA5BduGm3suhhVe9EMP9c9+mfJ/I9slqw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.11.0", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", + "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", + "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", + "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz", + "integrity": "sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.0.tgz", + "integrity": "sha512-+LLkxA9rKJpNoGsbLnAgOCdESl73vwYn+V6b+5wHbrE7OGKVDPHIQvbFSzqE6rwqaCw2RE+zdJrlLkcf8YOA0w==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.0.tgz", + "integrity": "sha512-fRGGjO2UEGPjvEcyAZXRXAS8AfdaQoq7HnxAbJoAoW10B9xOKesmmndJv+Sym2a+9FHWZ9KbyyLCe9s0Sn5jtg==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.27.0", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-syntax-typescript": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", + "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", + "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", + "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", + "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.9.tgz", + "integrity": "sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.8", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.26.0", + "@babel/plugin-syntax-import-attributes": "^7.26.0", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.25.9", + "@babel/plugin-transform-async-generator-functions": "^7.26.8", + "@babel/plugin-transform-async-to-generator": "^7.25.9", + "@babel/plugin-transform-block-scoped-functions": "^7.26.5", + "@babel/plugin-transform-block-scoping": "^7.25.9", + "@babel/plugin-transform-class-properties": "^7.25.9", + "@babel/plugin-transform-class-static-block": "^7.26.0", + "@babel/plugin-transform-classes": "^7.25.9", + "@babel/plugin-transform-computed-properties": "^7.25.9", + "@babel/plugin-transform-destructuring": "^7.25.9", + "@babel/plugin-transform-dotall-regex": "^7.25.9", + "@babel/plugin-transform-duplicate-keys": "^7.25.9", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-dynamic-import": "^7.25.9", + "@babel/plugin-transform-exponentiation-operator": "^7.26.3", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-for-of": "^7.26.9", + "@babel/plugin-transform-function-name": "^7.25.9", + "@babel/plugin-transform-json-strings": "^7.25.9", + "@babel/plugin-transform-literals": "^7.25.9", + "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", + "@babel/plugin-transform-member-expression-literals": "^7.25.9", + "@babel/plugin-transform-modules-amd": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.26.3", + "@babel/plugin-transform-modules-systemjs": "^7.25.9", + "@babel/plugin-transform-modules-umd": "^7.25.9", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-new-target": "^7.25.9", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.26.6", + "@babel/plugin-transform-numeric-separator": "^7.25.9", + "@babel/plugin-transform-object-rest-spread": "^7.25.9", + "@babel/plugin-transform-object-super": "^7.25.9", + "@babel/plugin-transform-optional-catch-binding": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9", + "@babel/plugin-transform-private-methods": "^7.25.9", + "@babel/plugin-transform-private-property-in-object": "^7.25.9", + "@babel/plugin-transform-property-literals": "^7.25.9", + "@babel/plugin-transform-regenerator": "^7.25.9", + "@babel/plugin-transform-regexp-modifiers": "^7.26.0", + "@babel/plugin-transform-reserved-words": "^7.25.9", + "@babel/plugin-transform-shorthand-properties": "^7.25.9", + "@babel/plugin-transform-spread": "^7.25.9", + "@babel/plugin-transform-sticky-regex": "^7.25.9", + "@babel/plugin-transform-template-literals": "^7.26.8", + "@babel/plugin-transform-typeof-symbol": "^7.26.7", + "@babel/plugin-transform-unicode-escapes": "^7.25.9", + "@babel/plugin-transform-unicode-property-regex": "^7.25.9", + "@babel/plugin-transform-unicode-regex": "^7.25.9", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.11.0", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.40.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-flow": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.25.9.tgz", + "integrity": "sha512-EASHsAhE+SSlEzJ4bzfusnXSHiU+JfAYzj+jbw2vgQKgq5HrUr8qs+vgtiEL5dOH6sEweI+PNt2D7AqrDSHyqQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-transform-flow-strip-types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.0.tgz", + "integrity": "sha512-vxaPFfJtHhgeOVXRKuHpHPAOgymmy8V8I65T1q53R7GCZlefKeCaTyDs3zOPHTTbmquvNlQYC5klEvWsBAtrBQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.26.3", + "@babel/plugin-transform-typescript": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/register": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.25.9.tgz", + "integrity": "sha512-8D43jXtGsYmEeDvm4MWHYUpWf8iiXgWYx3fW7E7Wb7Oe6FWqJPl5K6TuFW0dOwNZzEE5rjlaSJYH9JjrUKJszA==", + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.6", + "source-map-support": "^0.5.16" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/register/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "license": "MIT", + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/@babel/register/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/register/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", + "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.27.0", + "@babel/parser": "^7.27.0", + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@egjs/hammerjs": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", + "integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/hammerjs": "^2.0.36" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz", + "integrity": "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/core/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/create-cache-key-function": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", + "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/reporters/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/reporters/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map/node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@react-native-community/cli": { + "version": "12.3.7", + "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.7.tgz", + "integrity": "sha512-7+mOhk+3+X3BjSJZZvYrDJynA00gPYTlvT28ZjiLlbuVGfqfNiBKaxuF7rty+gjjpch4iKGvLhIhSN5cuOsdHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-clean": "12.3.7", + "@react-native-community/cli-config": "12.3.7", + "@react-native-community/cli-debugger-ui": "12.3.7", + "@react-native-community/cli-doctor": "12.3.7", + "@react-native-community/cli-hermes": "12.3.7", + "@react-native-community/cli-plugin-metro": "12.3.7", + "@react-native-community/cli-server-api": "12.3.7", + "@react-native-community/cli-tools": "12.3.7", + "@react-native-community/cli-types": "12.3.7", + "chalk": "^4.1.2", + "commander": "^9.4.1", + "deepmerge": "^4.3.0", + "execa": "^5.0.0", + "find-up": "^4.1.0", + "fs-extra": "^8.1.0", + "graceful-fs": "^4.1.3", + "prompts": "^2.4.2", + "semver": "^7.5.2" + }, + "bin": { + "react-native": "build/bin.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native-community/cli-clean": { + "version": "12.3.7", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-12.3.7.tgz", + "integrity": "sha512-BCYW77QqyxfhiMEBOoHyciJRNV6Rhz1RvclReIKnCA9wAwmoJBeu4Mu+AwiECA2bUITX16fvPt3NwDsSd1jwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-tools": "12.3.7", + "chalk": "^4.1.2", + "execa": "^5.0.0" + } + }, + "node_modules/@react-native-community/cli-config": { + "version": "12.3.7", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-12.3.7.tgz", + "integrity": "sha512-IU2UhO9yj1rEBNhHWGzIXpPDzha4hizLP/PUOrhR4BUf6RVPUWEp+e1PXNGR0qjIf6esu7OC7t6mLOhH0NUJEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-tools": "12.3.7", + "chalk": "^4.1.2", + "cosmiconfig": "^5.1.0", + "deepmerge": "^4.3.0", + "glob": "^7.1.3", + "joi": "^17.2.1" + } + }, + "node_modules/@react-native-community/cli-debugger-ui": { + "version": "12.3.7", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.7.tgz", + "integrity": "sha512-UHUFrRdcjWSCdWG9KIp2QjuRIahBQnb9epnQI7JCq6NFbFHYfEI4rI7msjMn+gG8/tSwKTV2PTPuPmZ5wWlE7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "serve-static": "^1.13.1" + } + }, + "node_modules/@react-native-community/cli-doctor": { + "version": "12.3.7", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-12.3.7.tgz", + "integrity": "sha512-gCamZztRoAyhciuQPqdz4Xe4t3gOdNsaADNd+rva+Rx8W2PoPeNv60i7/et06wlsn6B6Sh0/hMiAftJbiHDFkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-config": "12.3.7", + "@react-native-community/cli-platform-android": "12.3.7", + "@react-native-community/cli-platform-ios": "12.3.7", + "@react-native-community/cli-tools": "12.3.7", + "chalk": "^4.1.2", + "command-exists": "^1.2.8", + "deepmerge": "^4.3.0", + "envinfo": "^7.10.0", + "execa": "^5.0.0", + "hermes-profile-transformer": "^0.0.6", + "node-stream-zip": "^1.9.1", + "ora": "^5.4.1", + "semver": "^7.5.2", + "strip-ansi": "^5.2.0", + "wcwidth": "^1.0.1", + "yaml": "^2.2.1" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@react-native-community/cli-hermes": { + "version": "12.3.7", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-12.3.7.tgz", + "integrity": "sha512-ezzeiSKjRXK2+i1AAe7NhhN9CEHrgtRmTn2MAdBpE++N8fH5EQZgxFcGgGdwGvns2fm9ivyyeVnI5eAYwvM+jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-platform-android": "12.3.7", + "@react-native-community/cli-tools": "12.3.7", + "chalk": "^4.1.2", + "hermes-profile-transformer": "^0.0.6" + } + }, + "node_modules/@react-native-community/cli-platform-android": { + "version": "12.3.7", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-12.3.7.tgz", + "integrity": "sha512-mOltF3cpjNdJb3WSFwEHc1GH4ibCcnOvQ34OdWyblKy9ijuvG5SjNTlYR/UW/CURaDi3OUKAhxQMTY5d27bzGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-tools": "12.3.7", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-xml-parser": "^4.2.4", + "glob": "^7.1.3", + "logkitty": "^0.7.1" + } + }, + "node_modules/@react-native-community/cli-platform-ios": { + "version": "12.3.7", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-12.3.7.tgz", + "integrity": "sha512-2WnVsMH4ORZIhBm/5nCms1NeeKG4KarNC7PMLmrXWXB/bibDcaNsjrJiqnmCUcpTEvTQTokRfoO7Aj6NM0Cqow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-tools": "12.3.7", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-xml-parser": "^4.0.12", + "glob": "^7.1.3", + "ora": "^5.4.1" + } + }, + "node_modules/@react-native-community/cli-plugin-metro": { + "version": "12.3.7", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-12.3.7.tgz", + "integrity": "sha512-ahEw0Vfnv2Nv/jdZ2QDuGjQ9l2SczO4lXjb3ubu5vEYNLyTw3jYsLMK6iES7YQ/ApQmKdG476HU1O9uZdpaYPg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@react-native-community/cli-server-api": { + "version": "12.3.7", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-12.3.7.tgz", + "integrity": "sha512-LYETs3CCjrLn1ZU0kYv44TywiIl5IPFHZGeXhAh2TtgOk4mo3kvXxECDil9CdO3bmDra6qyiG61KHvzr8IrHdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@react-native-community/cli-debugger-ui": "12.3.7", + "@react-native-community/cli-tools": "12.3.7", + "compression": "^1.7.1", + "connect": "^3.6.5", + "errorhandler": "^1.5.1", + "nocache": "^3.0.1", + "pretty-format": "^26.6.2", + "serve-static": "^1.13.1", + "ws": "^7.5.1" + } + }, + "node_modules/@react-native-community/cli-tools": { + "version": "12.3.7", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-12.3.7.tgz", + "integrity": "sha512-7NL/1/i+wzd4fBr/FSr3ypR05tiU/Kv9l/M1sL1c6jfcDtWXAL90R161gQkQFK7shIQ8Idp0dQX1rq49tSyfQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "appdirsjs": "^1.2.4", + "chalk": "^4.1.2", + "find-up": "^5.0.0", + "mime": "^2.4.1", + "node-fetch": "^2.6.0", + "open": "^6.2.0", + "ora": "^5.4.1", + "semver": "^7.5.2", + "shell-quote": "^1.7.3", + "sudo-prompt": "^9.0.0" + } + }, + "node_modules/@react-native-community/cli-tools/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-tools/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-tools/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native-community/cli-tools/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@react-native-community/cli-types": { + "version": "12.3.7", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-12.3.7.tgz", + "integrity": "sha512-NFtUMyIrNfi3A5C1cjVKDVvYHvvOF7MnOMwdD8jm2NQKewQJrehKBh1eMuykKdqhWyZmuemD4KKhL8f4FxgG0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "joi": "^17.2.1" + } + }, + "node_modules/@react-native-community/cli/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@react-native-community/netinfo": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-9.5.0.tgz", + "integrity": "sha512-sppTBobjvIlPYXyDAyb5WJoBaQq1hprnHj1PWICsA10mVnlmwX5ZVkgO2vGjsfFtb+fmWK9XtZF+aQ6ijqQcwg==", + "license": "MIT", + "peerDependencies": { + "react-native": ">=0.59" + } + }, + "node_modules/@react-native/assets-registry": { + "version": "0.73.1", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.73.1.tgz", + "integrity": "sha512-2FgAbU7uKM5SbbW9QptPPZx8N9Ke2L7bsHb+EhAanZjFZunA9PaYtyjUQ1s7HD+zDVqOQIvjkpXSv7Kejd2tqg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/babel-plugin-codegen": { + "version": "0.79.1", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.79.1.tgz", + "integrity": "sha512-y3VyrPO/ej8Uhjk2IM+vBZok8cEyMl3DwJ3o/tsgiIVROITL+MWdk6M6iQOHRvwRWAgLe5jLSR3Zv5IIdDVY4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.3", + "@react-native/codegen": "0.79.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/babel-preset": { + "version": "0.73.18", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.73.18.tgz", + "integrity": "sha512-FzPasmazoX9WZnmwotk6SK9ydiExdqS4Xt5VaukPoY9u8u3AUUODzqjTsWSOxjFD9eRF3Knyg5H8JMDe6pj5wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.20.0", + "@babel/plugin-proposal-async-generator-functions": "^7.0.0", + "@babel/plugin-proposal-class-properties": "^7.18.0", + "@babel/plugin-proposal-export-default-from": "^7.0.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", + "@babel/plugin-proposal-numeric-separator": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.20.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", + "@babel/plugin-proposal-optional-chaining": "^7.20.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-default-from": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.18.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", + "@babel/plugin-syntax-optional-chaining": "^7.0.0", + "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-async-to-generator": "^7.20.0", + "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-classes": "^7.0.0", + "@babel/plugin-transform-computed-properties": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.20.0", + "@babel/plugin-transform-flow-strip-types": "^7.20.0", + "@babel/plugin-transform-function-name": "^7.0.0", + "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-modules-commonjs": "^7.0.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", + "@babel/plugin-transform-parameters": "^7.0.0", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.11", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-react-jsx-self": "^7.0.0", + "@babel/plugin-transform-react-jsx-source": "^7.0.0", + "@babel/plugin-transform-runtime": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0", + "@babel/plugin-transform-spread": "^7.0.0", + "@babel/plugin-transform-sticky-regex": "^7.0.0", + "@babel/plugin-transform-typescript": "^7.5.0", + "@babel/plugin-transform-unicode-regex": "^7.0.0", + "@babel/template": "^7.0.0", + "@react-native/babel-plugin-codegen": "*", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/codegen": { + "version": "0.79.1", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.79.1.tgz", + "integrity": "sha512-cTVXfCICkmUU6UvUpnLP4BE82O14JRuVz42cg/A19oasTaZmzHl0+uIDzt2cZEbt/N2sJ/EZnZL61qqpwbNXWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^7.1.1", + "hermes-parser": "0.25.1", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "yargs": "^17.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/community-cli-plugin": { + "version": "0.73.11", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.11.tgz", + "integrity": "sha512-s0bprwljKS1Al8wOKathDDmRyF+70CcNE2G/aqZ7+L0NoOE0Uxxx/5P2BxlM2Mfht7O33B4SeMNiPdE/FqIubQ==", + "license": "MIT", + "dependencies": { + "@react-native-community/cli-server-api": "12.3.0", + "@react-native-community/cli-tools": "12.3.0", + "@react-native/dev-middleware": "^0.73.6", + "@react-native/metro-babel-transformer": "^0.73.12", + "chalk": "^4.0.0", + "execa": "^5.1.1", + "metro": "^0.80.0", + "metro-config": "^0.80.0", + "metro-core": "^0.80.0", + "node-fetch": "^2.2.0", + "readline": "^1.3.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/@react-native-community/cli-debugger-ui": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.0.tgz", + "integrity": "sha512-w3b0iwjQlk47GhZWHaeTG8kKH09NCMUJO729xSdMBXE8rlbm4kHpKbxQY9qKb6NlfWSJN4noGY+FkNZS2rRwnQ==", + "license": "MIT", + "dependencies": { + "serve-static": "^1.13.1" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/@react-native-community/cli-server-api": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-12.3.0.tgz", + "integrity": "sha512-Rode8NrdyByC+lBKHHn+/W8Zu0c+DajJvLmOWbe2WY/ECvnwcd9MHHbu92hlT2EQaJ9LbLhGrSbQE3cQy9EOCw==", + "license": "MIT", + "dependencies": { + "@react-native-community/cli-debugger-ui": "12.3.0", + "@react-native-community/cli-tools": "12.3.0", + "compression": "^1.7.1", + "connect": "^3.6.5", + "errorhandler": "^1.5.1", + "nocache": "^3.0.1", + "pretty-format": "^26.6.2", + "serve-static": "^1.13.1", + "ws": "^7.5.1" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/@react-native-community/cli-tools": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-12.3.0.tgz", + "integrity": "sha512-2GafnCr8D88VdClwnm9KZfkEb+lzVoFdr/7ybqhdeYM0Vnt/tr2N+fM1EQzwI1DpzXiBzTYemw8GjRq+Utcz2Q==", + "license": "MIT", + "dependencies": { + "appdirsjs": "^1.2.4", + "chalk": "^4.1.2", + "find-up": "^5.0.0", + "mime": "^2.4.1", + "node-fetch": "^2.6.0", + "open": "^6.2.0", + "ora": "^5.4.1", + "semver": "^7.5.2", + "shell-quote": "^1.7.3", + "sudo-prompt": "^9.0.0" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@react-native/debugger-frontend": { + "version": "0.73.3", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.73.3.tgz", + "integrity": "sha512-RgEKnWuoo54dh7gQhV7kvzKhXZEhpF9LlMdZolyhGxHsBqZ2gXdibfDlfcARFFifPIiaZ3lXuOVVa4ei+uPgTw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/dev-middleware": { + "version": "0.73.8", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.73.8.tgz", + "integrity": "sha512-oph4NamCIxkMfUL/fYtSsE+JbGOnrlawfQ0kKtDQ5xbOjPKotKoXqrs1eGwozNKv7FfQ393stk1by9a6DyASSg==", + "license": "MIT", + "dependencies": { + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "0.73.3", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^1.0.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "node-fetch": "^2.2.0", + "open": "^7.0.3", + "serve-static": "^1.13.1", + "temp-dir": "^2.0.0", + "ws": "^6.2.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/@react-native/dev-middleware/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/ws": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "license": "MIT", + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/@react-native/eslint-config": { + "version": "0.73.2", + "resolved": "https://registry.npmjs.org/@react-native/eslint-config/-/eslint-config-0.73.2.tgz", + "integrity": "sha512-YzMfes19loTfbrkbYNAfHBDXX4oRBzc5wnvHs4h2GIHUj6YKs5ZK5lldqSrBJCdZAI3nuaO9Qj+t5JRwou571w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.20.0", + "@babel/eslint-parser": "^7.20.0", + "@react-native/eslint-plugin": "0.73.1", + "@typescript-eslint/eslint-plugin": "^5.57.1", + "@typescript-eslint/parser": "^5.57.1", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-ft-flow": "^2.0.1", + "eslint-plugin-jest": "^26.5.3", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-react": "^7.30.1", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-native": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": ">=8", + "prettier": ">=2" + } + }, + "node_modules/@react-native/eslint-plugin": { + "version": "0.73.1", + "resolved": "https://registry.npmjs.org/@react-native/eslint-plugin/-/eslint-plugin-0.73.1.tgz", + "integrity": "sha512-8BNMFE8CAI7JLWLOs3u33wcwcJ821LYs5g53Xyx9GhSg0h8AygTwDrwmYb/pp04FkCNCPjKPBoaYRthQZmxgwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/gradle-plugin": { + "version": "0.73.5", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.73.5.tgz", + "integrity": "sha512-Orrn8J/kqzEuXudl96XcZk84ZcdIpn1ojjwGSuaSQSXNcCYbOXyt0RwtW5kjCqjgSzGnOMsJNZc5FDXHVq/WzA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/js-polyfills": { + "version": "0.73.1", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.73.1.tgz", + "integrity": "sha512-ewMwGcumrilnF87H4jjrnvGZEaPFCAC4ebraEK+CurDDmwST/bIicI4hrOAv+0Z0F7DEK4O4H7r8q9vH7IbN4g==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/metro-babel-transformer": { + "version": "0.73.15", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.73.15.tgz", + "integrity": "sha512-LlkSGaXCz+xdxc9819plmpsl4P4gZndoFtpjN3GMBIu6f7TBV0GVbyJAU4GE8fuAWPVSVL5ArOcdkWKSbI1klw==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.20.0", + "@react-native/babel-preset": "0.73.21", + "hermes-parser": "0.15.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/@react-native/babel-plugin-codegen": { + "version": "0.73.4", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.73.4.tgz", + "integrity": "sha512-XzRd8MJGo4Zc5KsphDHBYJzS1ryOHg8I2gOZDAUCGcwLFhdyGu1zBNDJYH2GFyDrInn9TzAbRIf3d4O+eltXQQ==", + "license": "MIT", + "dependencies": { + "@react-native/codegen": "0.73.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/@react-native/babel-preset": { + "version": "0.73.21", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.73.21.tgz", + "integrity": "sha512-WlFttNnySKQMeujN09fRmrdWqh46QyJluM5jdtDNrkl/2Hx6N4XeDUGhABvConeK95OidVO7sFFf7sNebVXogA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.20.0", + "@babel/plugin-proposal-async-generator-functions": "^7.0.0", + "@babel/plugin-proposal-class-properties": "^7.18.0", + "@babel/plugin-proposal-export-default-from": "^7.0.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.0", + "@babel/plugin-proposal-numeric-separator": "^7.0.0", + "@babel/plugin-proposal-object-rest-spread": "^7.20.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", + "@babel/plugin-proposal-optional-chaining": "^7.20.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-default-from": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.18.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", + "@babel/plugin-syntax-optional-chaining": "^7.0.0", + "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-async-to-generator": "^7.20.0", + "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-classes": "^7.0.0", + "@babel/plugin-transform-computed-properties": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.20.0", + "@babel/plugin-transform-flow-strip-types": "^7.20.0", + "@babel/plugin-transform-function-name": "^7.0.0", + "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-modules-commonjs": "^7.0.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", + "@babel/plugin-transform-parameters": "^7.0.0", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.11", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-react-jsx-self": "^7.0.0", + "@babel/plugin-transform-react-jsx-source": "^7.0.0", + "@babel/plugin-transform-runtime": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0", + "@babel/plugin-transform-spread": "^7.0.0", + "@babel/plugin-transform-sticky-regex": "^7.0.0", + "@babel/plugin-transform-typescript": "^7.5.0", + "@babel/plugin-transform-unicode-regex": "^7.0.0", + "@babel/template": "^7.0.0", + "@react-native/babel-plugin-codegen": "0.73.4", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/@react-native/codegen": { + "version": "0.73.3", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.73.3.tgz", + "integrity": "sha512-sxslCAAb8kM06vGy9Jyh4TtvjhcP36k/rvj2QE2Jdhdm61KvfafCATSIsOfc0QvnduWFcpXUPvAVyYwuv7PYDg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.0", + "flow-parser": "^0.206.0", + "glob": "^7.1.1", + "invariant": "^2.2.4", + "jscodeshift": "^0.14.0", + "mkdirp": "^0.5.1", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" + } + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/hermes-estree": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.15.0.tgz", + "integrity": "sha512-lLYvAd+6BnOqWdnNbP/Q8xfl8LOGw4wVjfrNd9Gt8eoFzhNBRVD95n4l2ksfMVOoxuVyegs85g83KS9QOsxbVQ==", + "license": "MIT" + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/hermes-parser": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.15.0.tgz", + "integrity": "sha512-Q1uks5rjZlE9RjMMjSUCkGrEIPI5pKJILeCtK1VmTj7U4pf3wVPoo+cxfu+s4cBAPy2JzikIIdCZgBoR6x7U1Q==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.15.0" + } + }, + "node_modules/@react-native/metro-config": { + "version": "0.73.2", + "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.73.2.tgz", + "integrity": "sha512-sYBtFigV3L5Kc/D0xjgxAS3dVUg9UlCIT9D7qHhk6SMCh73YS5W9ZBmJAhXW9I8I4NPvCkol2iIvrfVszqEu7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@react-native/js-polyfills": "^0.73.1", + "@react-native/metro-babel-transformer": "^0.73.12", + "metro-config": "^0.80.0", + "metro-runtime": "^0.80.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/normalize-colors": { + "version": "0.73.2", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.73.2.tgz", + "integrity": "sha512-bRBcb2T+I88aG74LMVHaKms2p/T8aQd8+BZ7LuuzXlRfog1bMWWn/C5i0HVuvW4RPtXQYgIlGiXVDy9Ir1So/w==", + "license": "MIT" + }, + "node_modules/@react-native/typescript-config": { + "version": "0.73.1", + "resolved": "https://registry.npmjs.org/@react-native/typescript-config/-/typescript-config-0.73.1.tgz", + "integrity": "sha512-7Wrmdp972ZO7xvDid+xRGtvX6xz47cpGj7Y7VKlUhSVFFqbOGfB5WCpY1vMr6R/fjl+Og2fRw+TETN2+JnJi0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@react-native/virtualized-lists": { + "version": "0.73.4", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.73.4.tgz", + "integrity": "sha512-HpmLg1FrEiDtrtAbXiwCgXFYyloK/dOIPIuWW3fsqukwJEWAiTzm1nXGJ7xPU5XTHiWZ4sKup5Ebaj8z7iyWog==", + "license": "MIT", + "dependencies": { + "invariant": "^2.2.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react-native": "*" + } + }, + "node_modules/@react-navigation/core": { + "version": "6.4.17", + "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-6.4.17.tgz", + "integrity": "sha512-Nd76EpomzChWAosGqWOYE3ItayhDzIEzzZsT7PfGcRFDgW5miHV2t4MZcq9YIK4tzxZjVVpYbIynOOQQd1e0Cg==", + "license": "MIT", + "dependencies": { + "@react-navigation/routers": "^6.1.9", + "escape-string-regexp": "^4.0.0", + "nanoid": "^3.1.23", + "query-string": "^7.1.3", + "react-is": "^16.13.0", + "use-latest-callback": "^0.2.1" + }, + "peerDependencies": { + "react": "*" + } + }, + "node_modules/@react-navigation/elements": { + "version": "1.3.31", + "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-1.3.31.tgz", + "integrity": "sha512-bUzP4Awlljx5RKEExw8WYtif8EuQni2glDaieYROKTnaxsu9kEIA515sXQgUDZU4Ob12VoL7+z70uO3qrlfXcQ==", + "license": "MIT", + "peerDependencies": { + "@react-navigation/native": "^6.0.0", + "react": "*", + "react-native": "*", + "react-native-safe-area-context": ">= 3.0.0" + } + }, + "node_modules/@react-navigation/native": { + "version": "6.1.18", + "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-6.1.18.tgz", + "integrity": "sha512-mIT9MiL/vMm4eirLcmw2h6h/Nm5FICtnYSdohq4vTLA2FF/6PNhByM7s8ffqoVfE5L0uAa6Xda1B7oddolUiGg==", + "license": "MIT", + "dependencies": { + "@react-navigation/core": "^6.4.17", + "escape-string-regexp": "^4.0.0", + "fast-deep-equal": "^3.1.3", + "nanoid": "^3.1.23" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/@react-navigation/routers": { + "version": "6.1.9", + "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-6.1.9.tgz", + "integrity": "sha512-lTM8gSFHSfkJvQkxacGM6VJtBt61ip2XO54aNfswD+KMw6eeZ4oehl7m0me3CR9hnDE4+60iAZR8sAhvCiI3NA==", + "license": "MIT", + "dependencies": { + "nanoid": "^3.1.23" + } + }, + "node_modules/@react-navigation/stack": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@react-navigation/stack/-/stack-6.4.1.tgz", + "integrity": "sha512-upMEHOKMtuMu4c9gmoPlO/JqI6mDlSqwXg1aXKOTQLXAF8H5koOLRfrmi7AkdiE9A7lDXWUAZoGuD9O88cYvDQ==", + "license": "MIT", + "dependencies": { + "@react-navigation/elements": "^1.3.31", + "color": "^4.2.3", + "warn-once": "^0.1.0" + }, + "peerDependencies": { + "@react-navigation/native": "^6.0.0", + "react": "*", + "react-native": "*", + "react-native-gesture-handler": ">= 1.0.0", + "react-native-safe-area-context": ">= 3.0.0", + "react-native-screens": ">= 3.0.0" + } + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "license": "BSD-3-Clause" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "license": "MIT", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/core/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/@svgr/core/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@svgr/core/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@svgr/core/node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@svgr/core/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@svgr/core/node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@svgr/core/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@svgr/plugin-svgo": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", + "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", + "license": "MIT", + "dependencies": { + "cosmiconfig": "^8.1.3", + "deepmerge": "^4.3.1", + "svgo": "^3.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@svgr/plugin-svgo/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/@svgr/plugin-svgo/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@svgr/plugin-svgo/node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@svgr/plugin-svgo/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@svgr/plugin-svgo/node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@svgr/plugin-svgo/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "license": "ISC", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/hammerjs": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz", + "integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.15.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.2.tgz", + "integrity": "sha512-uKXqKN9beGoMdBfcaTY1ecwz6ctxuJAcUlwE55938g0ZJ8lRxwAZqRz2AJ4pzpt5dHdTPMB863UZ0ESiFUcP7A==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.20", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.20.tgz", + "integrity": "sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-test-renderer": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-18.3.1.tgz", + "integrity": "sha512-vAhnk0tG2eGa37lkU9+s5SoroCsRI08xnsWFiAXOuPH2jqzMbcXvKExXViPi1P5fIklDeCvXqyrdmipFaSkZrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "^18" + } + }, + "node_modules/@types/semver": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", + "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/anser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", + "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", + "license": "MIT" + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-fragments": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", + "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", + "license": "MIT", + "dependencies": { + "colorette": "^1.0.7", + "slice-ansi": "^2.0.0", + "strip-ansi": "^5.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/appdirsjs": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", + "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "license": "MIT" + }, + "node_modules/ast-types": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.15.2.tgz", + "integrity": "sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ast-types/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-core": { + "version": "7.0.0-bridge.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", + "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", + "license": "MIT", + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz", + "integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.4", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", + "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.3", + "core-js-compat": "^3.40.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz", + "integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.4" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-transform-flow-enums": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz", + "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==", + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-flow": "^7.12.1" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", + "license": "MIT", + "dependencies": { + "callsites": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", + "license": "MIT", + "dependencies": { + "caller-callsite": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001715", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz", + "integrity": "sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/chrome-launcher": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", + "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0" + }, + "bin": { + "print-chrome-path": "bin/print-chrome-path.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/chrome-launcher/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chromium-edge-launcher": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-1.0.0.tgz", + "integrity": "sha512-pgtgjNKZ7i5U++1g1PWv75umkHvhVTDOQIZ+sjeUX9483S7Y6MUvO0lrd7ShGlQlFHMN4SwKTCq/X8hWrbv2KA==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, + "node_modules/chromium-edge-launcher/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chromium-edge-launcher/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/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==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "license": "MIT" + }, + "node_modules/command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "license": "MIT" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", + "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.0.2", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/core-js-compat": { + "version": "3.41.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.41.0.tgz", + "integrity": "sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "license": "MIT", + "dependencies": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-tree/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "license": "CC0-1.0" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/denodeify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", + "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==", + "license": "MIT" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/deprecated-react-native-prop-types": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-5.0.0.tgz", + "integrity": "sha512-cIK8KYiiGVOFsKdPMmm1L3tA/Gl+JopXL6F5+C7x39MyPsQYnP57Im/D6bNUzcborD7fcMwiwZqcBdBXXZucYQ==", + "license": "MIT", + "dependencies": { + "@react-native/normalize-colors": "^0.73.0", + "invariant": "^2.2.4", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dot-case/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.143", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.143.tgz", + "integrity": "sha512-QqklJMOFBMqe46k8iIOwA9l2hz57V2OKMmP5eSWcUvwx+mASAsbU+wkF1pHjn9ZVSBPrsYWr4/W/95y5SwYg2g==", + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/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==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/envinfo": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", + "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/errorhandler": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", + "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.7", + "escape-html": "~1.0.3" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-abstract": { + "version": "1.23.9", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", + "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.0", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-regex": "^1.2.1", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.0", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.18" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.6", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.4", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", + "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-eslint-comments": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.2.0.tgz", + "integrity": "sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5", + "ignore": "^5.0.5" + }, + "engines": { + "node": ">=6.5.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-eslint-comments/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint-plugin-ft-flow": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-ft-flow/-/eslint-plugin-ft-flow-2.0.3.tgz", + "integrity": "sha512-Vbsd/b+LYA99jUbsL6viEUWShFaYQt2YQs3QN3f+aeszOhh2sgdcU0mjzDyD4yyBvMc8qy2uwvBBWfMzEX06tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21", + "string-natural-compare": "^3.0.1" + }, + "engines": { + "node": ">=12.22.0" + }, + "peerDependencies": { + "@babel/eslint-parser": "^7.12.0", + "eslint": "^8.1.0" + } + }, + "node_modules/eslint-plugin-jest": { + "version": "26.9.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-26.9.0.tgz", + "integrity": "sha512-TWJxWGp1J628gxh2KhaH1H1paEdgE2J61BBF1I59c6xWeL5+D1BzMxGDN/nXAfX+aSkR5u80K+XhskK6Gwq9ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^5.10.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": ">=7.28.0", + "prettier": ">=2.0.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react-native": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-native/-/eslint-plugin-react-native-4.1.0.tgz", + "integrity": "sha512-QLo7rzTBOl43FvVqDdq5Ql9IoElIuTdjrz9SKAXCvULvBoRZ44JGSkx9z4999ZusCsb4rK3gjS8gOGyeYqZv2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-plugin-react-native-globals": "^0.1.1" + }, + "peerDependencies": { + "eslint": "^3.17.0 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react-native-globals": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-native-globals/-/eslint-plugin-react-native-globals-0.1.2.tgz", + "integrity": "sha512-9aEPf1JEpiTjcFAmmyw8eiIXmcNZOqaZyHO77wgm0/dWfT/oxC1SrIq8ET38pMxHYrcB6Uew+TzUVsBeczF88g==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz", + "integrity": "sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==", + "license": "Apache-2.0" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-xml-parser": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", + "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.1.1" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-cache-dir/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-cache-dir/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-cache-dir/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "license": "MIT", + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-cache-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-cache-dir/node_modules/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==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-cache-dir/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/find-cache-dir/node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-cache-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/flow-enums-runtime": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", + "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", + "license": "MIT" + }, + "node_modules/flow-parser": { + "version": "0.206.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.206.0.tgz", + "integrity": "sha512-HVzoK3r6Vsg+lKvlIZzaWNBVai+FXTX1wdYhz/wVlH13tb/gOdLXmlTqy6odmTBhT5UoWUbq0k8263Qhr9d88w==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/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==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/hermes-profile-transformer": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/hermes-profile-transformer/-/hermes-profile-transformer-0.0.6.tgz", + "integrity": "sha512-cnN7bQUm65UWOy6cbGcCcZ3rpwW8Q/j4OP5aWRhEry4Z2t2aR1cjrbp0BS+KiBN0smvP1caBgAuxutvyvJILzQ==", + "license": "MIT", + "dependencies": { + "source-map": "^0.7.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-size": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", + "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", + "license": "MIT", + "dependencies": { + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, + "node_modules/import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "license": "MIT", + "dependencies": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/ip": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", + "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", + "license": "MIT" + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-config/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-config/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-leak-detector/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-validate/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsc-android": { + "version": "250231.0.0", + "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-250231.0.0.tgz", + "integrity": "sha512-rS46PvsjYmdmuz1OAWXY/1kCYG7pnf1TBqeTiOJr1iDz7s5DLxxC9n/ZMknLDxzYzNVfI7R95MH10emSSG1Wuw==", + "license": "BSD-2-Clause" + }, + "node_modules/jsc-safe-url": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", + "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", + "license": "0BSD" + }, + "node_modules/jscodeshift": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.14.0.tgz", + "integrity": "sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.13.16", + "@babel/parser": "^7.13.16", + "@babel/plugin-proposal-class-properties": "^7.13.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", + "@babel/plugin-proposal-optional-chaining": "^7.13.12", + "@babel/plugin-transform-modules-commonjs": "^7.13.8", + "@babel/preset-flow": "^7.13.13", + "@babel/preset-typescript": "^7.13.0", + "@babel/register": "^7.13.16", + "babel-core": "^7.0.0-bridge.0", + "chalk": "^4.1.2", + "flow-parser": "0.*", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.4", + "neo-async": "^2.5.0", + "node-dir": "^0.1.17", + "recast": "^0.21.0", + "temp": "^0.8.4", + "write-file-atomic": "^2.3.0" + }, + "bin": { + "jscodeshift": "bin/jscodeshift.js" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" + } + }, + "node_modules/jscodeshift/node_modules/write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lighthouse-logger": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^2.6.9", + "marky": "^1.2.2" + } + }, + "node_modules/lighthouse-logger/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/lighthouse-logger/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/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==", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/logkitty": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz", + "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", + "license": "MIT", + "dependencies": { + "ansi-fragments": "^0.2.1", + "dayjs": "^1.8.15", + "yargs": "^15.1.0" + }, + "bin": { + "logkitty": "bin/logkitty.js" + } + }, + "node_modules/logkitty/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/logkitty/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/logkitty/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.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": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logkitty/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lower-case/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/marky": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz", + "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==", + "license": "Apache-2.0" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "license": "CC0-1.0", + "peer": true + }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", + "license": "MIT" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/metro": { + "version": "0.80.12", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.80.12.tgz", + "integrity": "sha512-1UsH5FzJd9quUsD1qY+zUG4JY3jo3YEMxbMYH9jT6NK3j4iORhlwTK8fYTfAUBhDKjgLfKjAh7aoazNE23oIRA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.0", + "@babel/parser": "^7.20.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.20.0", + "@babel/types": "^7.20.0", + "accepts": "^1.3.7", + "chalk": "^4.0.0", + "ci-info": "^2.0.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "denodeify": "^1.2.1", + "error-stack-parser": "^2.0.6", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "hermes-parser": "0.23.1", + "image-size": "^1.0.2", + "invariant": "^2.2.4", + "jest-worker": "^29.6.3", + "jsc-safe-url": "^0.2.2", + "lodash.throttle": "^4.1.1", + "metro-babel-transformer": "0.80.12", + "metro-cache": "0.80.12", + "metro-cache-key": "0.80.12", + "metro-config": "0.80.12", + "metro-core": "0.80.12", + "metro-file-map": "0.80.12", + "metro-resolver": "0.80.12", + "metro-runtime": "0.80.12", + "metro-source-map": "0.80.12", + "metro-symbolicate": "0.80.12", + "metro-transform-plugins": "0.80.12", + "metro-transform-worker": "0.80.12", + "mime-types": "^2.1.27", + "nullthrows": "^1.1.1", + "serialize-error": "^2.1.0", + "source-map": "^0.5.6", + "strip-ansi": "^6.0.0", + "throat": "^5.0.0", + "ws": "^7.5.10", + "yargs": "^17.6.2" + }, + "bin": { + "metro": "src/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-babel-transformer": { + "version": "0.80.12", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.80.12.tgz", + "integrity": "sha512-YZziRs0MgA3pzCkkvOoQRXjIoVjvrpi/yRlJnObyIvMP6lFdtyG4nUGIwGY9VXnBvxmXD6mPY2e+NSw6JAyiRg==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.20.0", + "flow-enums-runtime": "^0.0.6", + "hermes-parser": "0.23.1", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-babel-transformer/node_modules/hermes-estree": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.23.1.tgz", + "integrity": "sha512-eT5MU3f5aVhTqsfIReZ6n41X5sYn4IdQL0nvz6yO+MMlPxw49aSARHLg/MSehQftyjnrE8X6bYregzSumqc6cg==", + "license": "MIT" + }, + "node_modules/metro-babel-transformer/node_modules/hermes-parser": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.23.1.tgz", + "integrity": "sha512-oxl5h2DkFW83hT4DAUJorpah8ou4yvmweUzLJmmr6YV2cezduCdlil1AvU/a/xSsAFo4WUcNA4GoV5Bvq6JffA==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.23.1" + } + }, + "node_modules/metro-cache": { + "version": "0.80.12", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.80.12.tgz", + "integrity": "sha512-p5kNHh2KJ0pbQI/H7ZBPCEwkyNcSz7OUkslzsiIWBMPQGFJ/xArMwkV7I+GJcWh+b4m6zbLxE5fk6fqbVK1xGA==", + "license": "MIT", + "dependencies": { + "exponential-backoff": "^3.1.1", + "flow-enums-runtime": "^0.0.6", + "metro-core": "0.80.12" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-cache-key": { + "version": "0.80.12", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.80.12.tgz", + "integrity": "sha512-o4BspKnugg/pE45ei0LGHVuBJXwRgruW7oSFAeSZvBKA/sGr0UhOGY3uycOgWInnS3v5yTTfiBA9lHlNRhsvGA==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-config": { + "version": "0.80.12", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.80.12.tgz", + "integrity": "sha512-4rwOWwrhm62LjB12ytiuR5NgK1ZBNr24/He8mqCsC+HXZ+ATbrewLNztzbAZHtFsrxP4D4GLTGgh96pCpYLSAQ==", + "license": "MIT", + "dependencies": { + "connect": "^3.6.5", + "cosmiconfig": "^5.0.5", + "flow-enums-runtime": "^0.0.6", + "jest-validate": "^29.6.3", + "metro": "0.80.12", + "metro-cache": "0.80.12", + "metro-core": "0.80.12", + "metro-runtime": "0.80.12" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-core": { + "version": "0.80.12", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.80.12.tgz", + "integrity": "sha512-QqdJ/yAK+IpPs2HU/h5v2pKEdANBagSsc6DRSjnwSyJsCoHlmyJKCaCJ7KhWGx+N4OHxh37hoA8fc2CuZbx0Fw==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "lodash.throttle": "^4.1.1", + "metro-resolver": "0.80.12" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-file-map": { + "version": "0.80.12", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.80.12.tgz", + "integrity": "sha512-sYdemWSlk66bWzW2wp79kcPMzwuG32x1ZF3otI0QZTmrnTaaTiGyhE66P1z6KR4n2Eu5QXiABa6EWbAQv0r8bw==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.0.3", + "debug": "^2.2.0", + "fb-watchman": "^2.0.0", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "invariant": "^2.2.4", + "jest-worker": "^29.6.3", + "micromatch": "^4.0.4", + "node-abort-controller": "^3.1.1", + "nullthrows": "^1.1.1", + "walker": "^1.0.7" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/metro-file-map/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/metro-file-map/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/metro-minify-terser": { + "version": "0.80.12", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.80.12.tgz", + "integrity": "sha512-muWzUw3y5k+9083ZoX9VaJLWEV2Jcgi+Oan0Mmb/fBNMPqP9xVDuy4pOMn/HOiGndgfh/MK7s4bsjkyLJKMnXQ==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "terser": "^5.15.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-resolver": { + "version": "0.80.12", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.80.12.tgz", + "integrity": "sha512-PR24gYRZnYHM3xT9pg6BdbrGbM/Cu1TcyIFBVlAk7qDAuHkUNQ1nMzWumWs+kwSvtd9eZGzHoucGJpTUEeLZAw==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-runtime": { + "version": "0.80.12", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.80.12.tgz", + "integrity": "sha512-LIx7+92p5rpI0i6iB4S4GBvvLxStNt6fF0oPMaUd1Weku7jZdfkCZzmrtDD9CSQ6EPb0T9NUZoyXIxlBa3wOCw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.0", + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-source-map": { + "version": "0.80.12", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.80.12.tgz", + "integrity": "sha512-o+AXmE7hpvM8r8MKsx7TI21/eerYYy2DCDkWfoBkv+jNkl61khvDHlQn0cXZa6lrcNZiZkl9oHSMcwLLIrFmpw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.20.0", + "@babel/types": "^7.20.0", + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-symbolicate": "0.80.12", + "nullthrows": "^1.1.1", + "ob1": "0.80.12", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-source-map/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/metro-symbolicate": { + "version": "0.80.12", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.80.12.tgz", + "integrity": "sha512-/dIpNdHksXkGHZXARZpL7doUzHqSNxgQ8+kQGxwpJuHnDhGkENxB5PS2QBaTDdEcmyTMjS53CN1rl9n1gR6fmw==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-source-map": "0.80.12", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "through2": "^2.0.1", + "vlq": "^1.0.0" + }, + "bin": { + "metro-symbolicate": "src/index.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-symbolicate/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/metro-transform-plugins": { + "version": "0.80.12", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.80.12.tgz", + "integrity": "sha512-WQWp00AcZvXuQdbjQbx1LzFR31IInlkCDYJNRs6gtEtAyhwpMMlL2KcHmdY+wjDO9RPcliZ+Xl1riOuBecVlPA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.20.0", + "flow-enums-runtime": "^0.0.6", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-transform-worker": { + "version": "0.80.12", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.80.12.tgz", + "integrity": "sha512-KAPFN1y3eVqEbKLx1I8WOarHPqDMUa8WelWxaJCNKO/yHCP26zELeqTJvhsQup+8uwB6EYi/sp0b6TGoh6lOEA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.0", + "@babel/parser": "^7.20.0", + "@babel/types": "^7.20.0", + "flow-enums-runtime": "^0.0.6", + "metro": "0.80.12", + "metro-babel-transformer": "0.80.12", + "metro-cache": "0.80.12", + "metro-cache-key": "0.80.12", + "metro-minify-terser": "0.80.12", + "metro-source-map": "0.80.12", + "metro-transform-plugins": "0.80.12", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro/node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "license": "MIT" + }, + "node_modules/metro/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/metro/node_modules/hermes-estree": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.23.1.tgz", + "integrity": "sha512-eT5MU3f5aVhTqsfIReZ6n41X5sYn4IdQL0nvz6yO+MMlPxw49aSARHLg/MSehQftyjnrE8X6bYregzSumqc6cg==", + "license": "MIT" + }, + "node_modules/metro/node_modules/hermes-parser": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.23.1.tgz", + "integrity": "sha512-oxl5h2DkFW83hT4DAUJorpah8ou4yvmweUzLJmmr6YV2cezduCdlil1AvU/a/xSsAFo4WUcNA4GoV5Bvq6JffA==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.23.1" + } + }, + "node_modules/metro/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/metro/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/metro/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/no-case/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/nocache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", + "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "license": "MIT" + }, + "node_modules/node-dir": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", + "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==", + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.2" + }, + "engines": { + "node": ">= 0.10.5" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "license": "MIT" + }, + "node_modules/node-stream-zip": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", + "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/antelle" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "license": "MIT" + }, + "node_modules/ob1": { + "version": "0.80.12", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.80.12.tgz", + "integrity": "sha512-VMArClVT6LkhUGpnuEoBuyjG9rzUyEzg4PDkav6wK1cLhOK02gPCYFxoiB4mqVnrMhDpIzJcrGNAMVi9P+hXrw==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", + "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", + "license": "MIT", + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module/node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "license": "MIT", + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "license": "MIT", + "dependencies": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/pretty-format/node_modules/@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/pretty-format/node_modules/@types/yargs": { + "version": "15.0.19", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", + "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "license": "MIT" + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/promise": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "license": "MIT", + "dependencies": { + "asap": "~2.0.6" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/query-string": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "license": "MIT", + "dependencies": { + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.3" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-devtools-core": { + "version": "4.28.5", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.28.5.tgz", + "integrity": "sha512-cq/o30z9W2Wb4rzBefjv5fBalHU0rJGZCHAkf/RHSBWSSYwh8PlQTqqOJmgIIbBtpj27T6FIPXeomIjZtCNVqA==", + "license": "MIT", + "dependencies": { + "shell-quote": "^1.6.1", + "ws": "^7" + } + }, + "node_modules/react-freeze": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.4.tgz", + "integrity": "sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=17.0.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-native": { + "version": "0.73.1", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.73.1.tgz", + "integrity": "sha512-nLl9O2yKRh1nMXwsk4SUiD0ddd19RqlKgNU9AU8bTK/zD2xwnVOG56YK1/22SN67niWyoeG83vVg1eTk+S6ReA==", + "license": "MIT", + "dependencies": { + "@jest/create-cache-key-function": "^29.6.3", + "@react-native-community/cli": "12.3.0", + "@react-native-community/cli-platform-android": "12.3.0", + "@react-native-community/cli-platform-ios": "12.3.0", + "@react-native/assets-registry": "^0.73.1", + "@react-native/codegen": "^0.73.2", + "@react-native/community-cli-plugin": "0.73.11", + "@react-native/gradle-plugin": "^0.73.4", + "@react-native/js-polyfills": "^0.73.1", + "@react-native/normalize-colors": "^0.73.2", + "@react-native/virtualized-lists": "^0.73.4", + "abort-controller": "^3.0.0", + "anser": "^1.4.9", + "ansi-regex": "^5.0.0", + "base64-js": "^1.5.1", + "deprecated-react-native-prop-types": "^5.0.0", + "event-target-shim": "^5.0.1", + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "jest-environment-node": "^29.6.3", + "jsc-android": "^250231.0.0", + "memoize-one": "^5.0.0", + "metro-runtime": "^0.80.0", + "metro-source-map": "^0.80.0", + "mkdirp": "^0.5.1", + "nullthrows": "^1.1.1", + "pretty-format": "^26.5.2", + "promise": "^8.3.0", + "react-devtools-core": "^4.27.7", + "react-refresh": "^0.14.0", + "react-shallow-renderer": "^16.15.0", + "regenerator-runtime": "^0.13.2", + "scheduler": "0.24.0-canary-efb381bbf-20230505", + "stacktrace-parser": "^0.1.10", + "whatwg-fetch": "^3.0.0", + "ws": "^6.2.2", + "yargs": "^17.6.2" + }, + "bin": { + "react-native": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "18.2.0" + } + }, + "node_modules/react-native-gesture-handler": { + "version": "2.25.0", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.25.0.tgz", + "integrity": "sha512-NPjJi6mislXxvjxQPU9IYwBjb1Uejp8GvAbE1Lhh+xMIMEvmgAvVIp5cz1P+xAbV6uYcRRArm278+tEInGOqWg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@egjs/hammerjs": "^2.0.17", + "hoist-non-react-statics": "^3.3.0", + "invariant": "^2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-safe-area-context": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.14.1.tgz", + "integrity": "sha512-+tUhT5WBl8nh5+P+chYhAjR470iCByf9z5EYdCEbPaAK3Yfzw+o8VRPnUgmPAKlSccOgQBxx3NOl/Wzckn9ujg==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-screens": { + "version": "3.37.0", + "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.37.0.tgz", + "integrity": "sha512-vEi4qZqWYoGuVGuHTv1K2XA90rgSydksmR5+tb5uhL93whl6Bch6EEXzC+8eEfj4SimiCgXBPY7r/xTXJxvnUg==", + "license": "MIT", + "dependencies": { + "react-freeze": "^1.0.0", + "warn-once": "^0.1.0" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-svg": { + "version": "15.11.2", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.11.2.tgz", + "integrity": "sha512-+YfF72IbWQUKzCIydlijV1fLuBsQNGMT6Da2kFlo1sh+LE3BIm/2Q7AR1zAAR6L0BFLi1WaQPLfFUC9bNZpOmw==", + "license": "MIT", + "peer": true, + "dependencies": { + "css-select": "^5.1.0", + "css-tree": "^1.1.3", + "warn-once": "0.1.1" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-svg-transformer": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/react-native-svg-transformer/-/react-native-svg-transformer-1.5.0.tgz", + "integrity": "sha512-RG5fSWJT7mjCQYocgYFUo1KYPLOoypPVG5LQab+pZZO7m4ciGaQIe0mhok3W4R5jLQsEXKo0u+aQGkZV/bZG7w==", + "license": "MIT", + "dependencies": { + "@svgr/core": "^8.1.0", + "@svgr/plugin-jsx": "^8.1.0", + "@svgr/plugin-svgo": "^8.1.0", + "path-dirname": "^1.0.2" + }, + "peerDependencies": { + "react-native": ">=0.59.0", + "react-native-svg": ">=12.0.0" + } + }, + "node_modules/react-native/node_modules/@react-native-community/cli": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.0.tgz", + "integrity": "sha512-XeQohi2E+S2+MMSz97QcEZ/bWpi8sfKiQg35XuYeJkc32Til2g0b97jRpn0/+fV0BInHoG1CQYWwHA7opMsrHg==", + "license": "MIT", + "dependencies": { + "@react-native-community/cli-clean": "12.3.0", + "@react-native-community/cli-config": "12.3.0", + "@react-native-community/cli-debugger-ui": "12.3.0", + "@react-native-community/cli-doctor": "12.3.0", + "@react-native-community/cli-hermes": "12.3.0", + "@react-native-community/cli-plugin-metro": "12.3.0", + "@react-native-community/cli-server-api": "12.3.0", + "@react-native-community/cli-tools": "12.3.0", + "@react-native-community/cli-types": "12.3.0", + "chalk": "^4.1.2", + "commander": "^9.4.1", + "deepmerge": "^4.3.0", + "execa": "^5.0.0", + "find-up": "^4.1.0", + "fs-extra": "^8.1.0", + "graceful-fs": "^4.1.3", + "prompts": "^2.4.2", + "semver": "^7.5.2" + }, + "bin": { + "react-native": "build/bin.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/react-native/node_modules/@react-native-community/cli-clean": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-12.3.0.tgz", + "integrity": "sha512-iAgLCOWYRGh9ukr+eVQnhkV/OqN3V2EGd/in33Ggn/Mj4uO6+oUncXFwB+yjlyaUNz6FfjudhIz09yYGSF+9sg==", + "license": "MIT", + "dependencies": { + "@react-native-community/cli-tools": "12.3.0", + "chalk": "^4.1.2", + "execa": "^5.0.0" + } + }, + "node_modules/react-native/node_modules/@react-native-community/cli-config": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-12.3.0.tgz", + "integrity": "sha512-BrTn5ndFD9uOxO8kxBQ32EpbtOvAsQExGPI7SokdI4Zlve70FziLtTq91LTlTUgMq1InVZn/jJb3VIDk6BTInQ==", + "license": "MIT", + "dependencies": { + "@react-native-community/cli-tools": "12.3.0", + "chalk": "^4.1.2", + "cosmiconfig": "^5.1.0", + "deepmerge": "^4.3.0", + "glob": "^7.1.3", + "joi": "^17.2.1" + } + }, + "node_modules/react-native/node_modules/@react-native-community/cli-debugger-ui": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.0.tgz", + "integrity": "sha512-w3b0iwjQlk47GhZWHaeTG8kKH09NCMUJO729xSdMBXE8rlbm4kHpKbxQY9qKb6NlfWSJN4noGY+FkNZS2rRwnQ==", + "license": "MIT", + "dependencies": { + "serve-static": "^1.13.1" + } + }, + "node_modules/react-native/node_modules/@react-native-community/cli-doctor": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-12.3.0.tgz", + "integrity": "sha512-BPCwNNesoQMkKsxB08Ayy6URgGQ8Kndv6mMhIvJSNdST3J1+x3ehBHXzG9B9Vfi+DrTKRb8lmEl/b/7VkDlPkA==", + "license": "MIT", + "dependencies": { + "@react-native-community/cli-config": "12.3.0", + "@react-native-community/cli-platform-android": "12.3.0", + "@react-native-community/cli-platform-ios": "12.3.0", + "@react-native-community/cli-tools": "12.3.0", + "chalk": "^4.1.2", + "command-exists": "^1.2.8", + "deepmerge": "^4.3.0", + "envinfo": "^7.10.0", + "execa": "^5.0.0", + "hermes-profile-transformer": "^0.0.6", + "ip": "^1.1.5", + "node-stream-zip": "^1.9.1", + "ora": "^5.4.1", + "semver": "^7.5.2", + "strip-ansi": "^5.2.0", + "wcwidth": "^1.0.1", + "yaml": "^2.2.1" + } + }, + "node_modules/react-native/node_modules/@react-native-community/cli-hermes": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-12.3.0.tgz", + "integrity": "sha512-G6FxpeZBO4AimKZwtWR3dpXRqTvsmEqlIkkxgwthdzn3LbVjDVIXKpVYU9PkR5cnT+KuAUxO0WwthrJ6Nmrrlg==", + "license": "MIT", + "dependencies": { + "@react-native-community/cli-platform-android": "12.3.0", + "@react-native-community/cli-tools": "12.3.0", + "chalk": "^4.1.2", + "hermes-profile-transformer": "^0.0.6", + "ip": "^1.1.5" + } + }, + "node_modules/react-native/node_modules/@react-native-community/cli-platform-android": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-12.3.0.tgz", + "integrity": "sha512-VU1NZw63+GLU2TnyQ919bEMThpHQ/oMFju9MCfrd3pyPJz4Sn+vc3NfnTDUVA5Z5yfLijFOkHIHr4vo/C9bjnw==", + "license": "MIT", + "dependencies": { + "@react-native-community/cli-tools": "12.3.0", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-xml-parser": "^4.2.4", + "glob": "^7.1.3", + "logkitty": "^0.7.1" + } + }, + "node_modules/react-native/node_modules/@react-native-community/cli-platform-ios": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-12.3.0.tgz", + "integrity": "sha512-H95Sgt3wT7L8V75V0syFJDtv4YgqK5zbu69ko4yrXGv8dv2EBi6qZP0VMmkqXDamoPm9/U7tDTdbcf26ctnLfg==", + "license": "MIT", + "dependencies": { + "@react-native-community/cli-tools": "12.3.0", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-xml-parser": "^4.0.12", + "glob": "^7.1.3", + "ora": "^5.4.1" + } + }, + "node_modules/react-native/node_modules/@react-native-community/cli-plugin-metro": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-12.3.0.tgz", + "integrity": "sha512-tYNHIYnNmxrBcsqbE2dAnLMzlKI3Cp1p1xUgTrNaOMsGPDN1epzNfa34n6Nps3iwKElSL7Js91CzYNqgTalucA==", + "license": "MIT" + }, + "node_modules/react-native/node_modules/@react-native-community/cli-server-api": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-12.3.0.tgz", + "integrity": "sha512-Rode8NrdyByC+lBKHHn+/W8Zu0c+DajJvLmOWbe2WY/ECvnwcd9MHHbu92hlT2EQaJ9LbLhGrSbQE3cQy9EOCw==", + "license": "MIT", + "dependencies": { + "@react-native-community/cli-debugger-ui": "12.3.0", + "@react-native-community/cli-tools": "12.3.0", + "compression": "^1.7.1", + "connect": "^3.6.5", + "errorhandler": "^1.5.1", + "nocache": "^3.0.1", + "pretty-format": "^26.6.2", + "serve-static": "^1.13.1", + "ws": "^7.5.1" + } + }, + "node_modules/react-native/node_modules/@react-native-community/cli-server-api/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/react-native/node_modules/@react-native-community/cli-tools": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-12.3.0.tgz", + "integrity": "sha512-2GafnCr8D88VdClwnm9KZfkEb+lzVoFdr/7ybqhdeYM0Vnt/tr2N+fM1EQzwI1DpzXiBzTYemw8GjRq+Utcz2Q==", + "license": "MIT", + "dependencies": { + "appdirsjs": "^1.2.4", + "chalk": "^4.1.2", + "find-up": "^5.0.0", + "mime": "^2.4.1", + "node-fetch": "^2.6.0", + "open": "^6.2.0", + "ora": "^5.4.1", + "semver": "^7.5.2", + "shell-quote": "^1.7.3", + "sudo-prompt": "^9.0.0" + } + }, + "node_modules/react-native/node_modules/@react-native-community/cli-tools/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-native/node_modules/@react-native-community/cli-types": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-12.3.0.tgz", + "integrity": "sha512-MgOkmrXH4zsGxhte4YqKL7d+N8ZNEd3w1wo56MZlhu5WabwCJh87wYpU5T8vyfujFLYOFuFK5jjlcbs8F4/WDw==", + "license": "MIT", + "dependencies": { + "joi": "^17.2.1" + } + }, + "node_modules/react-native/node_modules/@react-native/codegen": { + "version": "0.73.3", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.73.3.tgz", + "integrity": "sha512-sxslCAAb8kM06vGy9Jyh4TtvjhcP36k/rvj2QE2Jdhdm61KvfafCATSIsOfc0QvnduWFcpXUPvAVyYwuv7PYDg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.0", + "flow-parser": "^0.206.0", + "glob": "^7.1.1", + "invariant": "^2.2.4", + "jscodeshift": "^0.14.0", + "mkdirp": "^0.5.1", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" + } + }, + "node_modules/react-native/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-native/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-native/node_modules/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==", + "license": "MIT" + }, + "node_modules/react-native/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/react-native/node_modules/ws": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "license": "MIT", + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-shallow-renderer": { + "version": "16.15.0", + "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", + "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1", + "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-test-renderer": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.2.0.tgz", + "integrity": "sha512-JWD+aQ0lh2gvh4NM3bBM42Kx+XybOxCpgYK7F8ugAlpaTSnWsX+39Z4XkOykGZAHrjwwTZT3x3KxswVWxHPUqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "react-is": "^18.2.0", + "react-shallow-renderer": "^16.15.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-test-renderer/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-test-renderer/node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readline": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", + "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==", + "license": "BSD" + }, + "node_modules/recast": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.21.5.tgz", + "integrity": "sha512-hjMmLaUXAm1hIuTqOdeYObMslq/q+Xff6QE3Y2P+uoHAg2nmVlLBps2hzh1UJDdMtDTMXOFewK6ky51JQIeECg==", + "license": "MIT", + "dependencies": { + "ast-types": "0.15.2", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/recast/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/recast/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.0.2" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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==", + "license": "ISC" + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.24.0-canary-efb381bbf-20230505", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.24.0-canary-efb381bbf-20230505.tgz", + "integrity": "sha512-ABvovCDe/k9IluqSh4/ISoq8tIJnW8euVAWYt5j/bg6dRnqwQwiGO1F/V4AyK96NGF/FB04FhOUDuWj8IKfABA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", + "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/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==", + "license": "ISC" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", + "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/slice-ansi/node_modules/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==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/slice-ansi/node_modules/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==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/snake-case/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "license": "MIT" + }, + "node_modules/stacktrace-parser": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz", + "integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.7.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/stacktrace-parser/node_modules/type-fest": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-natural-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", + "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==", + "dev": true, + "license": "MIT" + }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/sudo-prompt": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", + "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT" + }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "license": "MIT" + }, + "node_modules/svgo": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", + "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", + "license": "MIT", + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/svgo/node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/svgo/node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "license": "CC0-1.0" + }, + "node_modules/temp": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", + "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==", + "license": "MIT", + "dependencies": { + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/temp/node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/terser": { + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", + "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/terser/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/throat": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "license": "MIT" + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/through2/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/through2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/through2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/through2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "license": "BSD-3-Clause" + }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", + "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-latest-callback": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.3.tgz", + "integrity": "sha512-7vI3fBuyRcP91pazVboc4qu+6ZqM8izPWX9k7cRnT8hbD5svslcknsh3S9BUhaK11OmgTV4oWZZVSeQAiV53SQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vlq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", + "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", + "license": "MIT" + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/warn-once": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/warn-once/-/warn-once-0.1.1.tgz", + "integrity": "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==", + "license": "MIT" + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "license": "MIT" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", + "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "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" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/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==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/VPNclient_react_native/package.json b/VPNclient_react_native/package.json new file mode 100644 index 00000000..24624b11 --- /dev/null +++ b/VPNclient_react_native/package.json @@ -0,0 +1,42 @@ +{ + "name": "@my-org/react-native-vpn-client", + "version": "0.0.1", + "description": "A React Native plugin for managing VPN connections", + "main": "index.js", + "scripts": { + "lint": "eslint .", + "test": "jest" + }, + "dependencies": { + "@react-native-community/netinfo": "^9.4.1", + "react-native-safe-area-context": "^4.8.2", + "react-native-screens": "^3.29.0", + "react-native-svg-transformer": "^1.5.0" + }, + "peerDependencies": { + "react": ">=18.2.0", + "react-native": ">=0.73.1" + }, + "devDependencies": { + "@babel/core": "^7.20.0", + "@babel/preset-env": "^7.20.0", + "@babel/runtime": "^7.20.0", + "@react-native-community/cli": "^12.0.0", + "@react-native/babel-preset": "0.73.18", + "@react-native/eslint-config": "0.73.2", + "@react-native/metro-config": "0.73.2", + "@react-native/typescript-config": "0.73.1", + "@types/react": "^18.2.6", + "@types/react-test-renderer": "^18.0.0", + "babel-jest": "^29.6.3", + "eslint": "^8.19.0", + "jest": "^29.6.3", + "prettier": "2.8.8", + "react-test-renderer": "18.2.0", + "typescript": "5.0.4" + }, + "engines": { + "node": ">=18" + } +} + diff --git a/VPNclient_react_native/src/native/android/VpnClientModule.java b/VPNclient_react_native/src/native/android/VpnClientModule.java new file mode 100644 index 00000000..ac98bc35 --- /dev/null +++ b/VPNclient_react_native/src/native/android/VpnClientModule.java @@ -0,0 +1,90 @@ +package com.vpnclient; + +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.modules.core.DeviceEventManagerModule; + +import android.util.Log; +import java.nio.charset.StandardCharsets; + +import com.vpnclient.vpnclient.VpnStatusDelegate; + +public class VpnClientModule extends ReactContextBaseJavaModule implements VpnStatusDelegate { + private static final String TAG = "VpnClientModule"; + private ReactApplicationContext reactContext; + + public VpnClientModule(ReactApplicationContext reactContext) { + super(reactContext); + this.reactContext = reactContext; + com.vpnclient.vpnclient.Vpnclient.INSTANCE.setVpnStatusDelegate(this); + } + + @Override + public String getName() { + return "VpnClientManager"; + } + + private void sendEvent(String eventName, Object params) { + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit(eventName, params); + } + + @ReactMethod + public void connect(String url, String config, String configType, Promise promise) { + Log.i(TAG, "Starting VPN using url: " + url); + Log.i(TAG, "Starting VPN using config: " + config); + Log.i(TAG, "Starting VPN using configType: " + configType); + try { + byte[] byteArray = null; + if (config != null) { + byteArray = config.getBytes(StandardCharsets.UTF_8); + } + if (config != null && config.length() > 0 && configType.equals("ovpn")) { + Log.i(TAG, "Connecting to ovpn..."); + com.vpnclient.vpnclient.Vpnclient.INSTANCE.startVpn(url, byteArray); + } else { + Log.i(TAG, "Connecting without config..."); + com.vpnclient.vpnclient.Vpnclient.INSTANCE.startVpn(url, null); + } + promise.resolve(null); + }catch (Exception e){ + Log.e(TAG, "Error starting vpn", e); + promise.reject("Error", "Error starting vpn"); + } + } + + @ReactMethod + public void disconnect(Promise promise) { + Log.i(TAG, "Stopping VPN"); + try { + com.vpnclient.vpnclient.Vpnclient.INSTANCE.stopVpn(); + promise.resolve(null); + } catch (Exception e) { + Log.e(TAG, "Error stopping vpn", e); + promise.reject("Error", "Error stopping vpn"); + } + } + + //VpnStatusDelegate + + @Override + public void onVpnStatusChanged(String status) { + Log.i(TAG, "VPN Status Changed: " + status); + WritableMap params = Arguments.createMap(); + params.putString("status", status); + sendEvent("onVpnStatusChanged", params); + } + + @Override + public void onVpnError(String error) { + Log.e(TAG, "VPN Error: " + error); + WritableMap params = Arguments.createMap(); + params.putString("error", error); + sendEvent("onVpnError", params); + } +} \ No newline at end of file diff --git a/VPNclient_react_native/src/native/android/VpnClientPackage.java b/VPNclient_react_native/src/native/android/VpnClientPackage.java new file mode 100644 index 00000000..f0ecfbd5 --- /dev/null +++ b/VPNclient_react_native/src/native/android/VpnClientPackage.java @@ -0,0 +1,21 @@ +package com.vpnclient; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +import java.util.Arrays; +import java.util.List; + +public class VpnClientPackage implements ReactPackage { + @Override + public List createNativeModules(ReactApplicationContext reactContext) { + return Arrays.asList(new VpnClientModule(reactContext)); + } + + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Arrays.asList(); + } +} \ No newline at end of file diff --git a/VPNclient_react_native/src/native/ios/VpnClientManager.m b/VPNclient_react_native/src/native/ios/VpnClientManager.m new file mode 100644 index 00000000..36409269 --- /dev/null +++ b/VPNclient_react_native/src/native/ios/VpnClientManager.m @@ -0,0 +1,123 @@ +objectivec +#import "VpnClientManager.h" +#import +#import +#import + +@interface VpnClientManager () + +@property (nonatomic, assign) BOOL isConnected; +@property (nonatomic, assign) BOOL isConnecting; +@property (nonatomic, assign) BOOL isDisconnecting; +@property (nonatomic, strong) RCTPromiseResolveBlock connectResolve; +@property (nonatomic, strong) RCTPromiseRejectBlock connectReject; +@end + + +@implementation VpnClientManager + +RCT_EXPORT_MODULE(); + +- (NSArray *)supportedEvents { + return @[@"onVpnStatusChanged", @"onVpnError"]; +} + +RCT_EXPORT_METHOD(connect:(NSString *)url config:(NSString *)config + resolver:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) { + + self.connectResolve = resolve; + self.connectReject = reject; + RCTLogInfo(@"Starting VPN using url: %@", url); + RCTLogInfo(@"Starting VPN using config: %@", config); + + if(self.isConnecting || self.isConnected){ + reject(@"connection_error", @"Already connecting", nil); + return; + } + if(self.isDisconnecting){ + reject(@"disconnecting_error", @"Disconnecting...", nil); + return; + } + self.isConnecting = YES; + + const char *urlChar = [url UTF8String]; + const char *configChar = [config UTF8String]; + + setVpnStatusDelegate(self); + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + if(config != (id)[NSNull null] && config.length > 0){ + startVpn(urlChar, configChar); + }else{ + startVpn(urlChar, NULL); + } + + }); +} + + +RCT_EXPORT_METHOD(disconnect:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) { + + if(!self.isConnected){ + reject(@"not_connected", @"Not connected", nil); + return; + } + if(self.isConnecting){ + reject(@"connecting_error", @"Connecting...", nil); + return; + } + if(self.isDisconnecting){ + reject(@"disconnecting_error", @"Already disconnecting...", nil); + return; + } + self.isDisconnecting = YES; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + RCTLogInfo(@"Stopping VPN"); + stopVpn(); + dispatch_async(dispatch_get_main_queue(), ^{ + resolve(nil); + self.isDisconnecting = NO; + }); + }); + +} + +//VpnStatusDelegate +-(void)onVpnStatusChanged:(const char *)status{ + NSString* statusString = [[NSString alloc] initWithUTF8String:status]; + + if ([statusString isEqualToString:@"connected"]) { + self.isConnected = YES; + self.isConnecting = NO; + } else if ([statusString isEqualToString:@"disconnected"]) { + self.isConnected = NO; + self.isConnecting = NO; + }else if ([statusString isEqualToString:@"connecting"]) { + self.isConnecting = YES; + } + + [self sendEventWithName:@"onVpnStatusChanged" body:@{@"status": statusString}]; + NSLog(@"VPN Status: %@", statusString); + + if (self.connectResolve) { + self.connectResolve(statusString); + self.connectResolve = nil; + } +} + +-(void)onVpnError:(const char *)error{ + NSString* errorString = [[NSString alloc] initWithUTF8String:error]; + [self sendEventWithName:@"onVpnError" body:@{@"error": errorString}]; + NSLog(@"VPN Error: %@", errorString); +} +-(void)vpnConnectionError{ + if(self.connectReject){ + self.connectReject(@"vpn_error", @"VPN error", nil); + self.connectReject = nil; + } +} + +@end \ No newline at end of file diff --git a/VPNclient_react_native/src/screens/HomeScreen.js b/VPNclient_react_native/src/screens/HomeScreen.js new file mode 100644 index 00000000..d2742c7b --- /dev/null +++ b/VPNclient_react_native/src/screens/HomeScreen.js @@ -0,0 +1,106 @@ +import React, { useState, useEffect } from 'react'; +import { View, Text, StyleSheet, TextInput, Alert } from 'react-native'; +import ConnectionButton from '../components/ConnectionButton'; +import ServerList from '../components/ServerList'; +import VpnManager from '../vpn/VpnManager'; + +function HomeScreen() { + const [isConnected, setIsConnected] = useState(false); + const [connectionStatus, setConnectionStatus] = useState('Disconnected'); + const [servers, setServers] = useState([]); + const [vpnUrl, setVpnUrl] = useState(''); + const [errorMessage, setErrorMessage] = useState(''); + + const handleConnect = async () => { + try { + setErrorMessage(''); + if (isConnected) { + setConnectionStatus('Disconnecting...'); + await VpnManager.disconnect(); + setConnectionStatus('Disconnected'); + setIsConnected(false); + } else { + setConnectionStatus('Connecting...'); + await VpnManager.connect(vpnUrl, null); + setIsConnected(true); + setConnectionStatus('Connected'); + } + const status = await VpnManager.getStatus(); + setIsConnected(status); + } catch (error) { + console.error('Connection error:', error); + setIsConnected(false); + setConnectionStatus('Error'); + setErrorMessage(error.message || 'Unknown error'); + Alert.alert('Error', errorMessage); + } + }; + + useEffect(() => { + const loadServers = async () => { + const serverList = await VpnManager.getServerList(); + setServers(serverList); + }; + + loadServers(); + const statusSubscription = VpnManager.onStatusChanged.subscribe( + (status) => { + setConnectionStatus(status); + if(status === "connected"){ + setIsConnected(true); + }else{ + setIsConnected(false); + } + } + ); + + const errorSubscription = VpnManager.onError.subscribe( + (error) => { + setErrorMessage(error.message); + setConnectionStatus("Error"); + setIsConnected(false); + Alert.alert('Error', error.message); + } + ); + + return () => { + statusSubscription.unsubscribe(); + errorSubscription.unsubscribe(); + }; + }, []); + + return ( + + {`Status: ${connectionStatus}`} + + + {}} /> + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + input: { + borderWidth: 1, + borderColor: 'gray', + width: 300, + padding: 10, + marginVertical: 10, + }, + status: { + marginBottom: 20, + fontSize: 18, + }, +}); + +export default HomeScreen; \ No newline at end of file diff --git a/VPNclient_react_native/src/vpn/VpnManager.js b/VPNclient_react_native/src/vpn/VpnManager.js new file mode 100644 index 00000000..a860905a --- /dev/null +++ b/VPNclient_react_native/src/vpn/VpnManager.js @@ -0,0 +1,134 @@ +import { NativeModules, NativeEventEmitter } from 'react-native'; +import V2RayCore from './V2RayCore'; +import WireGuardCore from './WireGuardCore'; +import OpenVPNCore from './OpenVPNCore'; + +const { VpnClientManager } = NativeModules; + +const vpnClientEmitter = new NativeEventEmitter(VpnClientManager); + +class VpnManager { + constructor() { + this.vpnCore = null; + this.currentUrl = ''; + this.config = null; + this.configType = null; + this.isConnected = false; + this.statusListeners = []; + this.errorListeners = []; + + this.setupEventListeners(); + } + + setupEventListeners() { + this.statusSubscription = vpnClientEmitter.addListener( + 'onVpnStatusChanged', + (event) => { + this.statusListeners.forEach((listener) => listener(event.status)); + if (event.status === 'connected') { + this.isConnected = true; + } else if (event.status === 'disconnected') { + this.isConnected = false; + } + } + ); + this.errorSubscription = vpnClientEmitter.addListener('onVpnError', (event) => { + this.errorListeners.forEach((listener) => listener(event.error)); + this.isConnected = false; + }); + } + + async connect(url, config, configType = null) { + this.currentUrl = url; + this.config = config; + this.configType = configType; + this.vpnCore = this.createCore(url); + if (!this.vpnCore) { + throw new Error('Invalid URL'); + } + await this.vpnCore.connect(url, config, configType); + } + + async disconnect() { + if (this.vpnCore) { + await this.vpnCore.disconnect(); + } + } + + getServerList() { + return [ + { + address: 'vless server', + url: 'vless://e811a4b3-79ff-4015-b568-c8537f303c2a@vless-server.com:443?path=%2F&security=tls&encryption=none&host=vless-server.com&type=ws&sni=vless-server.com#vless-server', + }, + { + address: 'vmess server', + url: 'vmess://eyJ2IjoiMiIsInBzIjoidm1lc3Mtc2VydmVyIiwiYWRkIjoidm1lc3Mtc2VydmVyLmNvbSIsImFpZCI6IjAiLCJwcnQiOjQ0MywidHlwZSI6IndzIiwidiI6IjIiLCJ0bHMiOiJ0bHMiLCJwYXRoIjoiLyIsImhvc3QiOiJ2bWVzcy1zZXJ2ZXIuY29tIiwicHMiOiJ2bWVzcy1zZXJ2ZXIiLCJpZCI6IjE4MzZhYTVjLTY3MzItNDlkNy05YmFmLTlkYTk2N2Y2NDlmYSIsIm5ldCI6IndzIn0=', + }, + { + address: 'wg server', + url: 'wg://[2001:db8:1::1]:51820#wg-server', + }, + { + address: 'openvpn server', + url: 'openvpn-server.ovpn', + config: + 'client\n'+ + 'dev tun\n'+ + 'proto udp\n'+ + 'remote openvpn-server.com 1194\n'+ + 'resolv-retry infinite\n'+ + 'nobind\n'+ + 'persist-key\n'+ + 'persist-tun\n'+ + 'verb 3\n', + }, + ]; + } + + + + async disconnect() { + if (this.vpnCore) { + await this.vpnCore.disconnect(); + this.isConnected = false; + } + } + createCore(url) { + if (url.startsWith('vless://') || url.startsWith('vmess://')) { + return new V2RayCore(VpnClientManager); + } else if (url.startsWith('wg://')) { + return new WireGuardCore(VpnClientManager); + } else if (url.endsWith('.ovpn')) { + return new OpenVPNCore(VpnClientManager); + } + return null; + } + + async getStatus() { + return this.isConnected; + } + + addStatusListener(listener) { + this.statusListeners.push(listener); + } + + removeStatusListener(listener) { + this.statusListeners = this.statusListeners.filter((l) => l !== listener); + } + + addErrorListener(listener) { + this.errorListeners.push(listener); + } + + removeErrorListener(listener) { + this.errorListeners = this.errorListeners.filter((l) => l !== listener); + } + + clearListeners() { + this.statusSubscription.remove(); + this.errorSubscription.remove(); + } +} + +export default new VpnManager(); From 4bf6e5d1a245341495df1174eb98e48d80a885c3 Mon Sep 17 00:00:00 2001 From: Phakin Kongkha Date: Sun, 4 May 2025 19:25:25 +0700 Subject: [PATCH 70/77] Add node_modules and .env to gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index b852846a..5fcb766f 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,7 @@ migrate_working_dir/ .flutter-plugins .flutter-plugins-dependencies build/ + +# Node.js +node_modules +.env From 3cc3deea31667c17416ae85b219e65694a2de5f2 Mon Sep 17 00:00:00 2001 From: Phakin Kongkha Date: Tue, 6 May 2025 14:20:42 +0700 Subject: [PATCH 71/77] Reformat code --- .gitignore | 5 +- .vscode/settings.json | 4 - ...n.kt => VpnclientEngineFlutterPlugin.dart} | 74 ++++--- .../VpnclientEngineFlutterPlugin.kt | 49 +++-- .../VpnclientEngineFlutterPluginTest.kt | 18 +- .../plugin_integration_test.dart | 1 - example/lib/main.dart | 40 ++-- example/test/widget_test.dart | 4 +- lib/main.dart | 8 +- lib/platforms/android.dart | 1 + lib/platforms/ios.dart | 1 + lib/vpnclient_engine/core.dart | 124 ++++++------ lib/vpnclient_engine/engine.dart | 181 ++++++++++-------- lib/vpnclient_engine/ping.dart | 1 + lib/vpnclient_engine/subscriptions.dart | 1 + lib/vpnclient_engine_flutter.dart | 17 +- 16 files changed, 286 insertions(+), 243 deletions(-) delete mode 100644 .vscode/settings.json rename android/src/main/kotlin/click/vpnclient/engine/{VpnclientEngineFlutterPlugin.kt => VpnclientEngineFlutterPlugin.dart} (67%) diff --git a/.gitignore b/.gitignore index 5fcb766f..f93b0fe2 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,7 @@ migrate_working_dir/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. -#.vscode/ +.vscode/ # Flutter/Dart/Pub related # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. @@ -35,3 +35,6 @@ build/ # Node.js node_modules .env + +# FVM +.fvm diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 03adc8d2..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "IDX.aI.enableInlineCompletion": true, - "IDX.aI.enableCodebaseIndexing": true -} \ No newline at end of file diff --git a/android/src/main/kotlin/click/vpnclient/engine/VpnclientEngineFlutterPlugin.kt b/android/src/main/kotlin/click/vpnclient/engine/VpnclientEngineFlutterPlugin.dart similarity index 67% rename from android/src/main/kotlin/click/vpnclient/engine/VpnclientEngineFlutterPlugin.kt rename to android/src/main/kotlin/click/vpnclient/engine/VpnclientEngineFlutterPlugin.dart index 9faa0ea2..2b06641a 100644 --- a/android/src/main/kotlin/click/vpnclient/engine/VpnclientEngineFlutterPlugin.kt +++ b/android/src/main/kotlin/click/vpnclient/engine/VpnclientEngineFlutterPlugin.dart @@ -6,29 +6,33 @@ import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter.dart'; import 'package:flutter/services.dart'; import 'package:flutter_v2ray/flutter_v2ray.dart'; - class AndroidVpnclientEngineFlutter extends VpnclientEngineFlutterPlatform { - static const MethodChannel _channel = MethodChannel('vpnclient_engine_flutter'); + static const MethodChannel _channel = MethodChannel( + 'vpnclient_engine_flutter', + ); final FlutterV2ray _flutterV2ray = FlutterV2ray( - onStatusChanged: (status) { - switch (status) { - case V2RayStatus.connected: - _connectionStatusSubject.add(ConnectionStatus.connected); - break; - case V2RayStatus.connecting: - _connectionStatusSubject.add(ConnectionStatus.connecting); - break; - case V2RayStatus.disconnected: - _connectionStatusSubject.add(ConnectionStatus.disconnected); - break; - case V2RayStatus.error: - _connectionStatusSubject.add(ConnectionStatus.error); - break; - } - }); + onStatusChanged: (status) { + switch (status) { + case V2RayStatus.connected: + _connectionStatusSubject.add(ConnectionStatus.connected); + break; + case V2RayStatus.connecting: + _connectionStatusSubject.add(ConnectionStatus.connecting); + break; + case V2RayStatus.disconnected: + _connectionStatusSubject.add(ConnectionStatus.disconnected); + break; + case V2RayStatus.error: + _connectionStatusSubject.add(ConnectionStatus.error); + break; + } + }, + ); - static final _connectionStatusSubject = StreamController.broadcast(); - static final _statusStream = _connectionStatusSubject.stream.asBroadcastStream(); + static final _connectionStatusSubject = + StreamController.broadcast(); + static final _statusStream = + _connectionStatusSubject.stream.asBroadcastStream(); static void registerWith() { VpnclientEngineFlutterPlatform.instance = AndroidVpnclientEngineFlutter(); @@ -43,11 +47,14 @@ class AndroidVpnclientEngineFlutter extends VpnclientEngineFlutterPlatform { } else if (url.startsWith('wg://')) { _connectionStatusSubject.add(ConnectionStatus.connecting); await _startWireguard(url); - } else if (url.startsWith('ovpn://')){ + } else if (url.startsWith('ovpn://')) { _connectionStatusSubject.add(ConnectionStatus.connecting); await _startOpenvpn(url); - }else { - VPNclientEngine.emitError(ErrorCode.unknownError, 'Invalid URL protocol'); + } else { + VPNclientEngine.emitError( + ErrorCode.unknownError, + 'Invalid URL protocol', + ); _connectionStatusSubject.add(ConnectionStatus.error); } } catch (e) { @@ -71,7 +78,8 @@ class AndroidVpnclientEngineFlutter extends VpnclientEngineFlutterPlatform { _connectionStatusSubject.add(ConnectionStatus.error); } } - Future _startWireguard(String url) async { + + Future _startWireguard(String url) async { final parser = FlutterV2ray.parseFromURL(url); if (await _flutterV2ray.requestPermission()) { await _flutterV2ray.startV2Ray( @@ -86,16 +94,20 @@ class AndroidVpnclientEngineFlutter extends VpnclientEngineFlutterPlatform { _connectionStatusSubject.add(ConnectionStatus.error); } } - Future _startOpenvpn(String url) async { - VPNclientEngine.emitError(ErrorCode.unknownError, 'OpenVPN not implemented yet'); - _connectionStatusSubject.add(ConnectionStatus.error); - } + + Future _startOpenvpn(String url) async { + VPNclientEngine.emitError( + ErrorCode.unknownError, + 'OpenVPN not implemented yet', + ); + _connectionStatusSubject.add(ConnectionStatus.error); + } @override Future disconnect() async { try { _connectionStatusSubject.add(ConnectionStatus.disconnected); - if(await _flutterV2ray.isRunning()){ + if (await _flutterV2ray.isRunning()) { await _flutterV2ray.stopV2Ray(); } } catch (e) { @@ -109,6 +121,6 @@ class AndroidVpnclientEngineFlutter extends VpnclientEngineFlutterPlatform { return await _flutterV2ray.requestPermission(); } catch (e) { return false; - } + } } -} \ No newline at end of file +} diff --git a/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt b/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt index 9ced071f..474c2c63 100644 --- a/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt +++ b/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt @@ -1,27 +1,23 @@ package click.vpnclient.engine.flutter.vpnclient_engine_flutter import android.content.Context +import android.util.Log +import go.Seq import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result -import go.Seq -import android.util.Log - - - - - - /** * VpnclientEngineFlutterPlugin * This class handles the communication between Flutter and native Android code * for managing VPN connections. */ -class VpnclientEngineFlutterPlugin : FlutterPlugin, MethodCallHandler { - /// The MethodChannel that will handle the communication between Flutter and native Android +class VpnclientEngineFlutterPlugin : + FlutterPlugin, + MethodCallHandler { + // / The MethodChannel that will handle the communication between Flutter and native Android private lateinit var channel: MethodChannel private lateinit var context: Context private val TAG = "VpnclientEngineFlutterPlugin" @@ -34,7 +30,10 @@ class VpnclientEngineFlutterPlugin : FlutterPlugin, MethodCallHandler { context = flutterPluginBinding.applicationContext } - override fun onMethodCall(call: MethodCall, result: Result) { + override fun onMethodCall( + call: MethodCall, + result: Result, + ) { when (call.method) { "startVPN" -> startVPN(call, result) "stopVPN" -> stopVPN(call, result) @@ -61,7 +60,10 @@ class VpnclientEngineFlutterPlugin : FlutterPlugin, MethodCallHandler { * @param call MethodCall containing the configuration. * @param result Result to send the success or error back to Flutter. */ - private fun startVPN(call: MethodCall, result: Result) { + private fun startVPN( + call: MethodCall, + result: Result, + ) { val config = call.argument("config") ?: return result.error("NO_CONFIG", "Missing config", null) try { initSingbox(config) @@ -77,7 +79,10 @@ class VpnclientEngineFlutterPlugin : FlutterPlugin, MethodCallHandler { * Stop the VPN connection. * @param result Result to send the success back to Flutter. */ - private fun stopVPN(call: MethodCall, result: Result) { + private fun stopVPN( + call: MethodCall, + result: Result, + ) { try { singboxCore?.stop() singboxCore = null // Release sing-box instance after stopping @@ -88,7 +93,10 @@ class VpnclientEngineFlutterPlugin : FlutterPlugin, MethodCallHandler { } } - private fun getStatus(call: MethodCall, result: Result) { + private fun getStatus( + call: MethodCall, + result: Result, + ) { try { val status = singboxCore?.getStatus() ?: "stopped" result.success(status) @@ -97,10 +105,11 @@ class VpnclientEngineFlutterPlugin : FlutterPlugin, MethodCallHandler { result.error( "STATUS_ERROR", "Failed to get sing-box status", - e.message + e.message, ) } } + private fun getPlatformVersion(result: Result) { result.success("Android ${android.os.Build.VERSION.RELEASE}") } @@ -110,9 +119,9 @@ class VpnclientEngineFlutterPlugin : FlutterPlugin, MethodCallHandler { } } - - -class SingBoxCore(config: String) { +class SingBoxCore( + config: String, +) { private val TAG = "SingBoxCore" init { @@ -158,7 +167,5 @@ class SingBoxCore(config: String) { } } - fun getStatus(): String { - return Singbox.getStatus() - } + fun getStatus(): String = Singbox.getStatus() } diff --git a/android/src/test/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPluginTest.kt b/android/src/test/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPluginTest.kt index 1f43e8b1..08dd18e5 100644 --- a/android/src/test/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPluginTest.kt +++ b/android/src/test/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPluginTest.kt @@ -2,8 +2,8 @@ package click.vpnclient.engine.flutter.vpnclient_engine_flutter import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel -import kotlin.test.Test import org.mockito.Mockito +import kotlin.test.Test /* * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation. @@ -14,14 +14,14 @@ import org.mockito.Mockito */ internal class VpnclientEngineFlutterPluginTest { - @Test - fun onMethodCall_getPlatformVersion_returnsExpectedValue() { - val plugin = VpnclientEngineFlutterPlugin() + @Test + fun onMethodCall_getPlatformVersion_returnsExpectedValue() { + val plugin = VpnclientEngineFlutterPlugin() - val call = MethodCall("getPlatformVersion", null) - val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java) - plugin.onMethodCall(call, mockResult) + val call = MethodCall("getPlatformVersion", null) + val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java) + plugin.onMethodCall(call, mockResult) - Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE) - } + Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE) + } } diff --git a/example/integration_test/plugin_integration_test.dart b/example/integration_test/plugin_integration_test.dart index a67cfc5c..83325265 100644 --- a/example/integration_test/plugin_integration_test.dart +++ b/example/integration_test/plugin_integration_test.dart @@ -6,7 +6,6 @@ // For more information about Flutter integration tests, please see // https://flutter.dev/to/integration-testing - import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; diff --git a/example/lib/main.dart b/example/lib/main.dart index 98a5fcfa..3a0ee6bd 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -14,9 +14,7 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( title: 'VPN Client Engine Demo', - theme: ThemeData( - primarySwatch: Colors.blue, - ), + theme: ThemeData(primarySwatch: Colors.blue), home: const VPNClientDemo(), ); } @@ -35,9 +33,13 @@ class VPNClientDemoState extends State { String _pingResult = 'Not Pinging'; List _routingRules = []; List _servers = []; - SessionStatistics _sessionStatistics = SessionStatistics(dataInBytes: 0, dataOutBytes: 0); + SessionStatistics _sessionStatistics = SessionStatistics( + dataInBytes: 0, + dataOutBytes: 0, + ); - final TextEditingController _subscriptionUrlController = TextEditingController(); + final TextEditingController _subscriptionUrlController = + TextEditingController(); final List _loadedSubscriptions = []; @override @@ -58,7 +60,8 @@ class VPNClientDemoState extends State { void _updatePingResult(PingResult result) { setState(() { - _pingResult = 'Ping: sub=${result.subscriptionIndex}, server=${result.serverIndex}, latency=${result.latencyInMs} ms'; + _pingResult = + 'Ping: sub=${result.subscriptionIndex}, server=${result.serverIndex}, latency=${result.latencyInMs} ms'; }); } @@ -74,7 +77,6 @@ class VPNClientDemoState extends State { }); } - void _connectToServer() async { await VPNclientEngine.connect(subscriptionIndex: 0, serverIndex: 0); setState(() { @@ -116,17 +118,15 @@ class VPNClientDemoState extends State { setState(() { _loadedSubscriptions.add(_subscriptionUrlController.text); }); - await VPNclientEngine.loadSubscriptions(subscriptionLinks: [_subscriptionUrlController.text]); + await VPNclientEngine.loadSubscriptions( + subscriptionLinks: [_subscriptionUrlController.text], + ); } - - @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text('VPN Client Engine Demo'), - ), + appBar: AppBar(title: const Text('VPN Client Engine Demo')), body: SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Column( @@ -167,7 +167,9 @@ class VPNClientDemoState extends State { itemBuilder: (context, index) { return ListTile( title: Text(_servers[index].address), - subtitle: Text('Latency: ${_servers[index].latency ?? 'N/A'}, Location: ${_servers[index].location ?? 'N/A'}'), + subtitle: Text( + 'Latency: ${_servers[index].latency ?? 'N/A'}, Location: ${_servers[index].location ?? 'N/A'}', + ), ); }, ), @@ -186,7 +188,9 @@ class VPNClientDemoState extends State { final rule = _routingRules[index]; return ListTile( title: Text('Rule ${index + 1}'), - subtitle: Text('App Name: ${rule.appName ?? 'N/A'}, Domain: ${rule.domain ?? 'N/A'}, Action: ${rule.action}'), + subtitle: Text( + 'App Name: ${rule.appName ?? 'N/A'}, Domain: ${rule.domain ?? 'N/A'}, Action: ${rule.action}', + ), ); }, ), @@ -210,9 +214,7 @@ class VPNClientDemoState extends State { physics: const NeverScrollableScrollPhysics(), itemCount: _loadedSubscriptions.length, itemBuilder: (context, index) { - return ListTile( - title: Text(_loadedSubscriptions[index]), - ); + return ListTile(title: Text(_loadedSubscriptions[index])); }, ), const SizedBox(height: 20), @@ -241,11 +243,9 @@ class VPNClientDemoState extends State { }, child: const Text('Disable kill switch'), ), - ], ), ), ); } } - diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart index 6774e59b..e5dde923 100644 --- a/example/test/widget_test.dart +++ b/example/test/widget_test.dart @@ -18,8 +18,8 @@ void main() { // Verify that platform version is retrieved. expect( find.byWidgetPredicate( - (Widget widget) => widget is Text && - widget.data!.startsWith('Running on:'), + (Widget widget) => + widget is Text && widget.data!.startsWith('Running on:'), ), findsOneWidget, ); diff --git a/lib/main.dart b/lib/main.dart index b40c3ebb..e3f6d800 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,6 @@ import 'dart:async'; import 'vpnclient_engine/engine.dart'; - - void main() async { // Initialize the Engine VPNclientEngine.initialize(); @@ -11,7 +9,9 @@ void main() async { VPNclientEngine.ClearSubscriptions(); // Add subscription - VPNclientEngine.addSubscription(subscriptionURL: "https://pastebin.com/raw/ZCYiJ98W"); + VPNclientEngine.addSubscription( + subscriptionURL: "https://pastebin.com/raw/ZCYiJ98W", + ); //VPNclientEngine.addSubscriptions(subscriptionURLs: ["https://pastebin.com/raw/ZCYiJ98W"]); // Update subscription @@ -45,4 +45,4 @@ void main() async { //Disconnect await VPNclientEngine.disconnect(); -} \ No newline at end of file +} diff --git a/lib/platforms/android.dart b/lib/platforms/android.dart index e69de29b..8b137891 100644 --- a/lib/platforms/android.dart +++ b/lib/platforms/android.dart @@ -0,0 +1 @@ + diff --git a/lib/platforms/ios.dart b/lib/platforms/ios.dart index e69de29b..8b137891 100644 --- a/lib/platforms/ios.dart +++ b/lib/platforms/ios.dart @@ -0,0 +1 @@ + diff --git a/lib/vpnclient_engine/core.dart b/lib/vpnclient_engine/core.dart index a58fa861..20719bb4 100644 --- a/lib/vpnclient_engine/core.dart +++ b/lib/vpnclient_engine/core.dart @@ -9,12 +9,7 @@ import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter_platform_inter import 'package:flutter_v2ray/flutter_v2ray.dart'; import 'package:rxdart/rxdart.dart'; -enum ConnectionStatus { - connecting, - connected, - disconnected, - error, -} +enum ConnectionStatus { connecting, connected, disconnected, error } enum ErrorCode { invalidCredentials, @@ -23,18 +18,9 @@ enum ErrorCode { unknownError, } -enum ProxyType { - socks5, - http, -} +enum ProxyType { socks5, http } -enum Action { - block, - allow, - routeThroughVPN, - direct, - proxy, -} +enum Action { block, allow, routeThroughVPN, direct, proxy } class Server { final String address; @@ -55,11 +41,7 @@ class SubscriptionDetails { final int? dataLimit; final int? usedData; - SubscriptionDetails({ - this.expiryDate, - this.dataLimit, - this.usedData, - }); + SubscriptionDetails({this.expiryDate, this.dataLimit, this.usedData}); } class SessionStatistics { @@ -117,16 +99,20 @@ class RoutingRule { class WireGuardCore implements VpnCore { @override - Future connect({required int subscriptionIndex, required int serverIndex}) { + Future connect({ + required int subscriptionIndex, + required int serverIndex, + }) { // TODO: implement connect throw UnimplementedError(); } - - } abstract class VpnCore { - Future connect({required int subscriptionIndex, required int serverIndex}); + Future connect({ + required int subscriptionIndex, + required int serverIndex, + }); Future disconnect(); String getConnectionStatus(); void setRoutingRules({required List rules}); @@ -138,8 +124,6 @@ abstract class VpnCore { void setKillSwitch({required bool enable}); } - - class V2RayCore implements VpnCore { final FlutterV2ray _flutterV2ray = FlutterV2ray( onStatusChanged: (status) { @@ -228,10 +212,11 @@ class V2RayCore implements VpnCore { } print('Parsed JSON subscription: ${servers.length} servers loaded'); } else { - servers = content - .split('\n') - .where((line) => line.trim().isNotEmpty) - .toList(); + servers = + content + .split('\n') + .where((line) => line.trim().isNotEmpty) + .toList(); print('Parsed NEWLINE subscription: ${servers.length} servers loaded'); } @@ -250,8 +235,10 @@ class V2RayCore implements VpnCore { } @override - Future connect( - {required int subscriptionIndex, required int serverIndex}) async { + Future connect({ + required int subscriptionIndex, + required int serverIndex, + }) async { try { if (subscriptionIndex < 0 || subscriptionIndex >= _subscriptionServers.length) { @@ -266,7 +253,8 @@ class V2RayCore implements VpnCore { await _flutterV2ray.initializeV2Ray(); - final serverAddress = _subscriptionServers[subscriptionIndex][serverIndex]; + final serverAddress = + _subscriptionServers[subscriptionIndex][serverIndex]; V2RayURL parser = FlutterV2ray.parseFromURL(serverAddress); _connectionStatusSubject.add(ConnectionStatus.connecting); @@ -314,8 +302,7 @@ class V2RayCore implements VpnCore { _emitError(ErrorCode.unknownError, 'Invalid subscription index'); return; } - if (index < 0 || - index >= _subscriptionServers[subscriptionIndex].length) { + if (index < 0 || index >= _subscriptionServers[subscriptionIndex].length) { print('Invalid server index'); _emitError(ErrorCode.unknownError, 'Invalid server index'); return; @@ -324,31 +311,40 @@ class V2RayCore implements VpnCore { print('Pinging server: $serverAddress'); try { final ping = Ping(serverAddress, count: 3); - final pingData = - await ping.stream.firstWhere((data) => data.response != null); + final pingData = await ping.stream.firstWhere( + (data) => data.response != null, + ); if (pingData.response != null) { final latency = pingData.response!.time!.inMilliseconds; final result = PingResult( - subscriptionIndex: subscriptionIndex, - serverIndex: index, - latencyInMs: latency); + subscriptionIndex: subscriptionIndex, + serverIndex: index, + latencyInMs: latency, + ); _pingResultSubject.add(result); print( - 'Ping result: sub=${result.subscriptionIndex}, server=${result.serverIndex}, latency=${result.latencyInMs} ms'); + 'Ping result: sub=${result.subscriptionIndex}, server=${result.serverIndex}, latency=${result.latencyInMs} ms', + ); } else { print('Ping failed: No response'); - _pingResultSubject.add(PingResult( + _pingResultSubject.add( + PingResult( subscriptionIndex: subscriptionIndex, serverIndex: index, - latencyInMs: -1)); + latencyInMs: -1, + ), + ); _emitError(ErrorCode.serverUnavailable, 'Ping failed: No response'); } } catch (e) { print('Ping error: $e'); - _pingResultSubject.add(PingResult( + _pingResultSubject.add( + PingResult( subscriptionIndex: subscriptionIndex, serverIndex: index, - latencyInMs: -1)); + latencyInMs: -1, + ), + ); _emitError(ErrorCode.unknownError, 'Ping error: $e'); } } @@ -362,22 +358,30 @@ class V2RayCore implements VpnCore { List getServerList() { return [ Server( - address: 'server1.com', latency: 50, location: 'USA', isPreferred: true), + address: 'server1.com', + latency: 50, + location: 'USA', + isPreferred: true, + ), Server( - address: 'server2.com', - latency: 100, - location: 'UK', - isPreferred: false), + address: 'server2.com', + latency: 100, + location: 'UK', + isPreferred: false, + ), Server( - address: 'server3.com', - latency: 75, - location: 'Canada', - isPreferred: false), + address: 'server3.com', + latency: 75, + location: 'Canada', + isPreferred: false, + ), ]; } @override - Future loadSubscriptions({required List subscriptionLinks}) async { + Future loadSubscriptions({ + required List subscriptionLinks, + }) async { print('loadSubscriptions: ${subscriptionLinks.join(", ")}'); _subscriptions.addAll(subscriptionLinks); print('Subscriptions added: ${subscriptionLinks.join(", ")}'); @@ -408,7 +412,11 @@ class V2RayCore implements VpnCore { class OpenVPNCore implements VpnCore { @override - Future connect({required int subscriptionIndex, required int serverIndex}) { + Future connect({ + required int subscriptionIndex, + required int serverIndex, + }) { // TODO: implement connect throw UnimplementedError(); } +} diff --git a/lib/vpnclient_engine/engine.dart b/lib/vpnclient_engine/engine.dart index 91b901ea..c84e4fe6 100644 --- a/lib/vpnclient_engine/engine.dart +++ b/lib/vpnclient_engine/engine.dart @@ -1,40 +1,12 @@ import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; +import 'package:flutter_v2ray/flutter_v2ray.dart'; import 'package:dart_ping/dart_ping.dart'; import 'package:rxdart/rxdart.dart'; import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter.dart'; import 'package:vpnclient_engine_flutter/vpnclient_engine/core.dart'; - - -enum ConnectionStatus { - connecting, - connected, - disconnected, - error, -} - -enum ErrorCode { - invalidCredentials, - serverUnavailable, - subscriptionExpired, - unknownError, -} - -enum ProxyType { - socks5, - http, -} - -enum Action { - block, - allow, - routeThroughVPN, - direct, - proxy, -} - class Server { final String address; final int? latency; @@ -54,11 +26,7 @@ class SubscriptionDetails { final int? dataLimit; final int? usedData; - SubscriptionDetails({ - this.expiryDate, - this.dataLimit, - this.usedData, - }); + SubscriptionDetails({this.expiryDate, this.dataLimit, this.usedData}); } class SessionStatistics { @@ -114,18 +82,14 @@ class RoutingRule { RoutingRule({this.appName, this.domain, required this.action}); } - - - class VPNclientEngine { static List> _subscriptionServers = []; static Map _connections = {}; static List _subscriptions = []; - - static final _connectionStatusSubject = BehaviorSubject(); - static Stream get onConnectionStatusChanged => _connectionStatusSubject.stream; + static Stream get onConnectionStatusChanged => + _connectionStatusSubject.stream; static final _errorSubject = BehaviorSubject(); static Stream get onError => _errorSubject.stream; @@ -136,31 +100,32 @@ class VPNclientEngine { static final _pingResultSubject = BehaviorSubject(); static Stream get onPingResult => _pingResultSubject.stream; - static final _subscriptionLoadedSubject = BehaviorSubject(); - static Stream get onSubscriptionLoaded => _subscriptionLoadedSubject.stream; + static final _subscriptionLoadedSubject = + BehaviorSubject(); + static Stream get onSubscriptionLoaded => + _subscriptionLoadedSubject.stream; static final _dataUsageUpdatedSubject = BehaviorSubject(); - static Stream get onDataUsageUpdated => _dataUsageUpdatedSubject.stream; + static Stream get onDataUsageUpdated => + _dataUsageUpdatedSubject.stream; - static final _routingRulesAppliedSubject = BehaviorSubject>(); - static Stream> get onRoutingRulesApplied => _routingRulesAppliedSubject.stream; + static final _routingRulesAppliedSubject = + BehaviorSubject>(); + static Stream> get onRoutingRulesApplied => + _routingRulesAppliedSubject.stream; static final _killSwitchTriggeredSubject = BehaviorSubject(); - static Stream get onKillSwitchTriggered => _killSwitchTriggeredSubject.stream; + static Stream get onKillSwitchTriggered => + _killSwitchTriggeredSubject.stream; static VpnCore _vpnCore = V2RayCore(); - - static void _emitError(ErrorCode code, String message) { _errorSubject.add(ErrorDetails(errorCode: code, errorMessage: message)); } - - static List _subscriptions = []; - static void initialize() { print('VPNclient Engine initialized'); if (_vpnCore == null) { @@ -184,7 +149,9 @@ class VPNclientEngine { print('Subscriptions added: ${subscriptionURLs.join(", ")}'); } - static Future updateSubscription({required int subscriptionIndex}) async { + static Future updateSubscription({ + required int subscriptionIndex, + }) async { if (subscriptionIndex < 0 || subscriptionIndex >= _subscriptions.length) { print('Invalid subscription index'); return; @@ -215,7 +182,11 @@ class VPNclientEngine { print('Parsed JSON subscription: ${servers.length} servers loaded'); } else { // NEWLINE format - servers = content.split('\n').where((line) => line.trim().isNotEmpty).toList(); + servers = + content + .split('\n') + .where((line) => line.trim().isNotEmpty) + .toList(); print('Parsed NEWLINE subscription: ${servers.length} servers loaded'); } @@ -241,26 +212,31 @@ class VPNclientEngine { ProxyConfig? proxyConfig, }) async { final url = _subscriptionServers[subscriptionIndex][serverIndex]; - - if (url.startsWith('vless://') || url.startsWith('vmess://') || url.startsWith('v2ray://')) { - _vpnCore = V2RayCore(); + + if (url.startsWith('vless://') || + url.startsWith('vmess://') || + url.startsWith('v2ray://')) { + _vpnCore = V2RayCore(); } else if (url.startsWith('wg://')) { - _vpnCore = WireGuardCore(); + _vpnCore = WireGuardCore(); } else if (url.startsWith('openvpn://') || url.endsWith('.ovpn')) { - _vpnCore = OpenVPNCore(); + _vpnCore = OpenVPNCore(); } else { - _emitError(ErrorCode.unknownError, 'Unsupported URL format'); - return; + _emitError(ErrorCode.unknownError, 'Unsupported URL format'); + return; } - if (serverIndex < 0 || serverIndex >= _subscriptionServers[subscriptionIndex].length) { + if (serverIndex < 0 || + serverIndex >= _subscriptionServers[subscriptionIndex].length) { _emitError(ErrorCode.unknownError, 'Invalid server index'); return; } - await _vpnCore.connect(Server(address: _subscriptionServers[subscriptionIndex][serverIndex]), proxyConfig); + await _vpnCore.connect( + Server(address: _subscriptionServers[subscriptionIndex][serverIndex]), + proxyConfig, + ); } - static Future disconnect() async { if (_vpnCore == null) { _emitError(ErrorCode.unknownError, 'VPN core is not initialized.'); @@ -268,11 +244,8 @@ class VPNclientEngine { } await _vpnCore!.disconnect(); - - } - static void setRoutingRules({required List rules}) { for (var rule in rules) { if (rule.appName != null) { @@ -283,8 +256,12 @@ class VPNclientEngine { } } - static void pingServer({required int subscriptionIndex, required int index}) async { - if (subscriptionIndex < 0 || subscriptionIndex >= _subscriptionServers.length) { + static void pingServer({ + required int subscriptionIndex, + required int index, + }) async { + if (subscriptionIndex < 0 || + subscriptionIndex >= _subscriptionServers.length) { print('Invalid subscription index'); return; } @@ -294,22 +271,42 @@ class VPNclientEngine { } final serverAddress = _subscriptionServers[subscriptionIndex][index]; print('Pinging server: $serverAddress'); - + try { final ping = Ping(serverAddress, count: 3); - final pingData = await ping.stream.firstWhere((data) => data.response != null); + final pingData = await ping.stream.firstWhere( + (data) => data.response != null, + ); if (pingData.response != null) { final latency = pingData.response!.time!.inMilliseconds; - final result = PingResult(subscriptionIndex: subscriptionIndex, serverIndex: index, latencyInMs: latency); + final result = PingResult( + subscriptionIndex: subscriptionIndex, + serverIndex: index, + latencyInMs: latency, + ); _pingResultSubject.add(result); - print('Ping result: sub=${result.subscriptionIndex}, server=${result.serverIndex}, latency=${result.latencyInMs} ms'); + print( + 'Ping result: sub=${result.subscriptionIndex}, server=${result.serverIndex}, latency=${result.latencyInMs} ms', + ); } else { print('Ping failed: No response'); - _pingResultSubject.add(PingResult(subscriptionIndex: subscriptionIndex, serverIndex: index, latencyInMs: -1)); // Indicate error with -1 + _pingResultSubject.add( + PingResult( + subscriptionIndex: subscriptionIndex, + serverIndex: index, + latencyInMs: -1, + ), + ); // Indicate error with -1 } } catch (e) { print('Ping error: $e'); - _pingResultSubject.add(PingResult(subscriptionIndex: subscriptionIndex, serverIndex: index, latencyInMs: -1)); + _pingResultSubject.add( + PingResult( + subscriptionIndex: subscriptionIndex, + serverIndex: index, + latencyInMs: -1, + ), + ); } } @@ -322,19 +319,38 @@ class VPNclientEngine { //TODO: //Fetches the list of available VPN servers. return [ - Server(address: 'server1.com', latency: 50, location: 'USA', isPreferred: true), - Server(address: 'server2.com', latency: 100, location: 'UK', isPreferred: false), - Server(address: 'server3.com', latency: 75, location: 'Canada', isPreferred: false), + Server( + address: 'server1.com', + latency: 50, + location: 'USA', + isPreferred: true, + ), + Server( + address: 'server2.com', + latency: 100, + location: 'UK', + isPreferred: false, + ), + Server( + address: 'server3.com', + latency: 75, + location: 'Canada', + isPreferred: false, + ), ]; } - static Future loadSubscriptions({required List subscriptionLinks}) async { + static Future loadSubscriptions({ + required List subscriptionLinks, + }) async { print('loadSubscriptions: ${subscriptionLinks.join(", ")}'); _subscriptions.addAll(subscriptionLinks); for (var element in subscriptionLinks) { - addSubscription(subscriptionURL: element); - await updateSubscription(subscriptionIndex: _subscriptions.indexOf(element)); - } + addSubscription(subscriptionURL: element); + await updateSubscription( + subscriptionIndex: _subscriptions.indexOf(element), + ); + } } static SessionStatistics getSessionStatistics() { @@ -353,5 +369,4 @@ class VPNclientEngine { static void setKillSwitch({required bool enable}) { print('setKillSwitch: $enable'); } - -} \ No newline at end of file +} diff --git a/lib/vpnclient_engine/ping.dart b/lib/vpnclient_engine/ping.dart index e69de29b..8b137891 100644 --- a/lib/vpnclient_engine/ping.dart +++ b/lib/vpnclient_engine/ping.dart @@ -0,0 +1 @@ + diff --git a/lib/vpnclient_engine/subscriptions.dart b/lib/vpnclient_engine/subscriptions.dart index e69de29b..8b137891 100644 --- a/lib/vpnclient_engine/subscriptions.dart +++ b/lib/vpnclient_engine/subscriptions.dart @@ -0,0 +1 @@ + diff --git a/lib/vpnclient_engine_flutter.dart b/lib/vpnclient_engine_flutter.dart index 713301a1..0c783555 100644 --- a/lib/vpnclient_engine_flutter.dart +++ b/lib/vpnclient_engine_flutter.dart @@ -25,9 +25,11 @@ abstract class VpnclientEngineFlutterPlatform { } else { _instance = VpnclientEngineFlutterPlatform(); print( - 'VPNclientEngineFlutter: Warning: Platform not yet supported, fallback to default implementation'); - print('Please report this platform ${Platform.operatingSystem} on https://github.com/VPNclient/vpnclient_engine_flutter/issues'); - + 'VPNclientEngineFlutter: Warning: Platform not yet supported, fallback to default implementation', + ); + print( + 'Please report this platform ${Platform.operatingSystem} on https://github.com/VPNclient/vpnclient_engine_flutter/issues', + ); } } return _instance!; @@ -35,23 +37,20 @@ abstract class VpnclientEngineFlutterPlatform { Future getPlatformVersion(); - Future connect({ - required String url, - }); + Future connect({required String url}); Future disconnect(); void sendStatus(ConnectionStatus status) { - print("default: $status"); + print("default: $status"); } void sendError(ErrorCode errorCode, String errorMessage) { - print("default: $errorCode $errorMessage"); + print("default: $errorCode $errorMessage"); } } class VpnclientEngineFlutter extends VpnclientEngineFlutterPlatform { - @override Future connect({required String url}) async { return Future.value(); From 60661f9988cbc663040a1776bec12ff8b56cb0e7 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Wed, 18 Jun 2025 07:13:56 +0700 Subject: [PATCH 72/77] engines.dart --- lib/engines.dart | 125 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 lib/engines.dart diff --git a/lib/engines.dart b/lib/engines.dart new file mode 100644 index 00000000..ead7ed07 --- /dev/null +++ b/lib/engines.dart @@ -0,0 +1,125 @@ +// Enum for available engines +enum VpnEngine { flutterV2ray, singBox, libXray } + +// Factory for creating the required engine +class VpnEngineFactory { + static VpnCore create(VpnEngine engine) { + switch (engine) { + case VpnEngine.singBox: + return SingBoxCore(); + case VpnEngine.libXray: + return LibXrayCore(); + case VpnEngine.flutterV2ray: + default: + return V2RayCore(); + } + } +} + +// Stub for SingBox engine +class SingBoxCore implements VpnCore { + @override + Future connect({required int subscriptionIndex, required int serverIndex}) async { + // TODO: Implement connection using sing-box engine + throw UnimplementedError(); + } + @override + Future disconnect() async { + // TODO: Implement disconnect using sing-box engine + throw UnimplementedError(); + } + @override + String getConnectionStatus() { + // TODO: Implement status retrieval + throw UnimplementedError(); + } + @override + void setRoutingRules({required List rules}) { + // TODO: Implement routing rules + throw UnimplementedError(); + } + @override + SessionStatistics getSessionStatistics() { + // TODO: Implement statistics + throw UnimplementedError(); + } + @override + Future loadSubscriptions({required List subscriptionLinks}) async { + // TODO: Implement subscription loading + throw UnimplementedError(); + } + @override + void pingServer({required int subscriptionIndex, required int index}) { + // TODO: Implement ping + throw UnimplementedError(); + } + @override + List getServerList() { + // TODO: Implement server list + throw UnimplementedError(); + } + @override + void setAutoConnect({required bool enable}) { + // TODO: Implement auto-connect + throw UnimplementedError(); + } + @override + void setKillSwitch({required bool enable}) { + // TODO: Implement Kill Switch + throw UnimplementedError(); + } +} + +// Stub for LibXray engine +class LibXrayCore implements VpnCore { + @override + Future connect({required int subscriptionIndex, required int serverIndex}) async { + // TODO: Implement connection using libxray engine + throw UnimplementedError(); + } + @override + Future disconnect() async { + // TODO: Implement disconnect using libxray engine + throw UnimplementedError(); + } + @override + String getConnectionStatus() { + // TODO: Implement status retrieval + throw UnimplementedError(); + } + @override + void setRoutingRules({required List rules}) { + // TODO: Implement routing rules + throw UnimplementedError(); + } + @override + SessionStatistics getSessionStatistics() { + // TODO: Implement statistics + throw UnimplementedError(); + } + @override + Future loadSubscriptions({required List subscriptionLinks}) async { + // TODO: Implement subscription loading + throw UnimplementedError(); + } + @override + void pingServer({required int subscriptionIndex, required int index}) { + // TODO: Implement ping + throw UnimplementedError(); + } + @override + List getServerList() { + // TODO: Implement server list + throw UnimplementedError(); + } + @override + void setAutoConnect({required bool enable}) { + // TODO: Implement auto-connect + throw UnimplementedError(); + } + @override + void setKillSwitch({required bool enable}) { + // TODO: Implement Kill Switch + throw UnimplementedError(); + } +} \ No newline at end of file From 699c3d2101a9c15cde6ec64759848560d7757fc1 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Wed, 18 Jun 2025 07:14:53 +0700 Subject: [PATCH 73/77] some --- ios/vpnclient_engine_flutter.podspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/vpnclient_engine_flutter.podspec b/ios/vpnclient_engine_flutter.podspec index fb92c34c..a167fe44 100644 --- a/ios/vpnclient_engine_flutter.podspec +++ b/ios/vpnclient_engine_flutter.podspec @@ -6,8 +6,8 @@ Pod::Spec.new do |s| s.license = { :type => 'MIT' } s.author = { 'Your Name' => 'your.email@example.com' } s.source = { :path => '.' } - s.dependency 'PacketTunnelProvider' + # s.dependency 'PacketTunnelProvider' # Removed because pod not found s.ios.deployment_target = '11.0' s.dependency 'Flutter' s.source_files = 'Classes/**/*' -end \ No newline at end of file +end \ No newline at end of file From 221273a2d7e9c7d49db1143359c2180880c77af9 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Thu, 19 Jun 2025 07:38:13 +0700 Subject: [PATCH 74/77] Refactor iOS plugin: remove FlutterV2ray, add engine selection (sing-box/libxray), update Dart engine logic --- example/ios/Podfile.lock | 34 ++++ example/ios/Runner.xcodeproj/project.pbxproj | 112 +++++++++++ .../xcshareddata/xcschemes/Runner.xcscheme | 2 + .../contents.xcworkspacedata | 3 + example/pubspec.lock | 20 +- .../VpnclientEngineFlutterPlugin.swift | 179 ++++++++---------- lib/engines.dart | 99 ++++++++-- pubspec.lock | 16 +- pubspec.yaml | 2 +- 9 files changed, 332 insertions(+), 135 deletions(-) create mode 100644 example/ios/Podfile.lock diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock new file mode 100644 index 00000000..7b0d40f8 --- /dev/null +++ b/example/ios/Podfile.lock @@ -0,0 +1,34 @@ +PODS: + - Flutter (1.0.0) + - flutter_v2ray (0.0.1): + - Flutter + - integration_test (0.0.1): + - Flutter + - vpnclient_engine_flutter (0.0.1): + - Flutter + +DEPENDENCIES: + - Flutter (from `Flutter`) + - flutter_v2ray (from `.symlinks/plugins/flutter_v2ray/ios`) + - integration_test (from `.symlinks/plugins/integration_test/ios`) + - vpnclient_engine_flutter (from `.symlinks/plugins/vpnclient_engine_flutter/ios`) + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + flutter_v2ray: + :path: ".symlinks/plugins/flutter_v2ray/ios" + integration_test: + :path: ".symlinks/plugins/integration_test/ios" + vpnclient_engine_flutter: + :path: ".symlinks/plugins/vpnclient_engine_flutter/ios" + +SPEC CHECKSUMS: + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + flutter_v2ray: 1190bb389b67a1dc9f28ece1d4b308101e38395e + integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e + vpnclient_engine_flutter: e73bc2cc4b57bcb1ea64169bc7782f20a5968217 + +PODFILE CHECKSUM: 4305caec6b40dde0ae97be1573c53de1882a07e5 + +COCOAPODS: 1.16.2 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index e5a05d55..72b1749c 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -10,10 +10,12 @@ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 549312AB3E4C28415C039B2D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF9FDFE25A7B6CE1ADD947F0 /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + EF4861AD329AD8A16F38CB75 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8CDB8441AF8DCBA761B411BC /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -40,14 +42,19 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 139A60ED5FF0124202BD05EB /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 703D18707E2A66E4D633E317 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 71C81AE1C01B5946E1E29BC9 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 8CDB8441AF8DCBA761B411BC /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8D8DB00BB5D704869ECAAAFB /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -55,19 +62,45 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + AD22A1F7239154861B7BAE26 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + B64263435795029D42658DAE /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + BF9FDFE25A7B6CE1ADD947F0 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 0EC2684C3BD64471EC68DF99 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + EF4861AD329AD8A16F38CB75 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 549312AB3E4C28415C039B2D /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1B53C7F1AFBF5427CE2EB961 /* Pods */ = { + isa = PBXGroup; + children = ( + B64263435795029D42658DAE /* Pods-Runner.debug.xcconfig */, + 139A60ED5FF0124202BD05EB /* Pods-Runner.release.xcconfig */, + 703D18707E2A66E4D633E317 /* Pods-Runner.profile.xcconfig */, + AD22A1F7239154861B7BAE26 /* Pods-RunnerTests.debug.xcconfig */, + 71C81AE1C01B5946E1E29BC9 /* Pods-RunnerTests.release.xcconfig */, + 8D8DB00BB5D704869ECAAAFB /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; 331C8082294A63A400263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( @@ -94,6 +127,8 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, + 1B53C7F1AFBF5427CE2EB961 /* Pods */, + CF793EE8563AB6830D198C74 /* Frameworks */, ); sourceTree = ""; }; @@ -121,6 +156,15 @@ path = Runner; sourceTree = ""; }; + CF793EE8563AB6830D198C74 /* Frameworks */ = { + isa = PBXGroup; + children = ( + BF9FDFE25A7B6CE1ADD947F0 /* Pods_Runner.framework */, + 8CDB8441AF8DCBA761B411BC /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -128,8 +172,10 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + D8B0D55A9E48EDAB7208ED1B /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, + 0EC2684C3BD64471EC68DF99 /* Frameworks */, ); buildRules = ( ); @@ -145,12 +191,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + CD1C9E40EF7D6F6A01231E85 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 7CE431F21214F3306EE6B1D3 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -238,6 +286,23 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 7CE431F21214F3306EE6B1D3 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -253,6 +318,50 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + CD1C9E40EF7D6F6A01231E85 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + D8B0D55A9E48EDAB7208ED1B /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -379,6 +488,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = AD22A1F7239154861B7BAE26 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -396,6 +506,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 71C81AE1C01B5946E1E29BC9 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -411,6 +522,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 8D8DB00BB5D704869ECAAAFB /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 15cada48..e3773d42 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" shouldUseLaunchSchemeArgsEnv = "YES"> + + diff --git a/example/pubspec.lock b/example/pubspec.lock index 4e4e96d6..e777d20c 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.0" boolean_selector: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" file: dependency: transitive description: @@ -139,10 +139,10 @@ packages: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: @@ -304,10 +304,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.3.1" + version: "15.0.0" vpnclient_engine_flutter: dependency: "direct main" description: @@ -327,10 +327,10 @@ packages: dependency: transitive description: name: webdriver - sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" + sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade" url: "https://pub.dev" source: hosted - version: "3.0.4" + version: "3.1.0" sdks: dart: ">=3.7.2 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/ios/Classes/VpnclientEngineFlutterPlugin.swift b/ios/Classes/VpnclientEngineFlutterPlugin.swift index 5fbf06d9..d4388afc 100644 --- a/ios/Classes/VpnclientEngineFlutterPlugin.swift +++ b/ios/Classes/VpnclientEngineFlutterPlugin.swift @@ -1,37 +1,34 @@ import Foundation import NetworkExtension import Flutter -import FlutterV2ray -/// Plugin class to handle VPN connections in the Flutter app. -import Flutter import UIKit -import NetworkExtension -import flutter_v2ray_plugin + +/// Plugin class to handle VPN connections in the Flutter app. public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { private var tunnelProvider: NETunnelProviderManager? - + private var channel: FlutterMethodChannel? + private var isVpnRunning: Bool = false + private var tunnelManager: NETunnelProviderManager? + private var currentEngine: String? = nil + private var currentConfig: String? = nil + public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel(name: "vpnclient_engine_flutter", binaryMessenger: registrar.messenger()) let instance = VpnclientEngineFlutterPlugin() registrar.addMethodCallDelegate(instance, channel: channel) instance.channel = channel } - private var channel: FlutterMethodChannel? - private let v2rayPlugin = FlutterV2rayPlugin.sharedInstance() - private var isVpnRunning = false - private var currentConfig: V2RayURL? - private var tunnelManager: NETunnelProviderManager? - private var isVpnRunning: Bool = false - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { case "connect": - guard let arguments = call.arguments as? [String: Any] else { + guard let arguments = call.arguments as? [String: Any], + let engine = arguments["engine"] as? String, + let config = arguments["config"] as? String else { result(FlutterError(code: "ARGUMENT_ERROR", message: "Invalid arguments", details: nil)) return } - self.connect(arguments: arguments, result: result) + self.connect(engine: engine, config: config, result: result) case "disconnect": self.disconnect(result: result) case "requestPermissions": @@ -46,141 +43,131 @@ public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { result(FlutterMethodNotImplemented) } } - - private func connect(arguments: [String: Any], result: @escaping FlutterResult) { - guard let link = arguments["link"] as? String else { - result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing or invalid config", details: nil)) - return - } - if link.starts(with: "vless://") || link.starts(with: "vmess://") { - var parsedConfig: V2RayURL - do { - parsedConfig = try FlutterV2ray.parseFromURL(link) - } catch { - sendError(errorCode: "PARSE_ERROR", errorMessage: "Failed to parse config: \(error)") - result(FlutterError(code: "PARSE_ERROR", message: "Failed to parse config: \(error)", details: nil)) - return - } - - currentConfig = parsedConfig - startV2Ray(config: parsedConfig, result: result) - } else if link.starts(with: "wg://") { - var parsedConfig: V2RayURL - do { - parsedConfig = try FlutterV2ray.parseFromURL(link) - } catch { - sendError(errorCode: "PARSE_ERROR", errorMessage: "Failed to parse config: \(error)") - result(FlutterError(code: "PARSE_ERROR", message: "Failed to parse config: \(error)", details: nil)) - return - } - - currentConfig = parsedConfig - startV2Ray(config: parsedConfig, result: result) - } else { - sendError(errorCode: "UNKNOWN_PROTOCOL", errorMessage: "Unknown protocol") - result(FlutterError(code: "UNKNOWN_PROTOCOL", message: "Unknown protocol", details: nil)) + + private func connect(engine: String, config: String, result: @escaping FlutterResult) { + self.currentEngine = engine + self.currentConfig = config + switch engine { + case "singbox": + startSingBox(config: config, result: result) + case "libxray": + // TODO: Реализовать запуск libxray через NETunnelProviderManager или другой механизм + result(FlutterError(code: "NOT_IMPLEMENTED", message: "libxray support is not implemented yet", details: nil)) + default: + result(FlutterError(code: "UNKNOWN_ENGINE", message: "Unknown engine: \(engine)", details: nil)) } } - private func startV2Ray(config: V2RayURL, result: @escaping FlutterResult) { - let fullConfig = config.getFullConfiguration() - if fullConfig.isEmpty { - sendError(errorCode: "CONFIG_ERROR", errorMessage: "Invalid V2Ray config") - result(FlutterError(code: "CONFIG_ERROR", message: "Invalid V2Ray config", details: nil)) - return - } - v2rayPlugin.startV2Ray(remark: config.remark, config: fullConfig, blockedApps: nil, bypassSubnets: nil, proxyOnly: false) { err in - if let err = err { - DispatchQueue.main.async { - self.sendError(errorCode: "VPN_ERROR", errorMessage: err) - result(FlutterError(code: "VPN_START_FAILED", message: "Failed to start VPN: \(err)", details: nil)) - } + private func startSingBox(config: String, result: @escaping FlutterResult) { + // Пример запуска через NETunnelProviderManager + NETunnelProviderManager.loadAllFromPreferences { managers, error in + if let error = error { + result(FlutterError(code: "LOAD_ERROR", message: "Failed to load VPN configurations: \(error.localizedDescription)", details: nil)) + return + } + let manager: NETunnelProviderManager + if let managers = managers, let firstManager = managers.first { + manager = firstManager } else { - DispatchQueue.main.async { - self.sendConnectionStatus(status: "connected") - self.isVpnRunning = true - result(nil) + manager = NETunnelProviderManager() + manager.localizedDescription = "VPNClientEngine (sing-box)" + let protocolConfiguration = NETunnelProviderProtocol() + protocolConfiguration.providerBundleIdentifier = "click.vpnclient.engine.singbox" + manager.protocolConfiguration = protocolConfiguration + } + manager.isEnabled = true + // Передаем конфиг в providerConfiguration + if let proto = manager.protocolConfiguration as? NETunnelProviderProtocol { + proto.providerConfiguration = ["config": config] + } + manager.saveToPreferences { error in + if let error = error { + result(FlutterError(code: "SAVE_ERROR", message: "Failed to save VPN configuration: \(error.localizedDescription)", details: nil)) + return + } + manager.loadFromPreferences { error in + if let error = error { + result(FlutterError(code: "LOAD_PREF_ERROR", message: "Failed to load VPN preferences: \(error.localizedDescription)", details: nil)) + return + } + do { + try manager.connection.startVPNTunnel() + self.isVpnRunning = true + self.sendConnectionStatus(status: "connected") + result(true) + } catch let startError { + self.sendError(errorCode: "START_ERROR", errorMessage: "Failed to start VPN: \(startError)") + result(FlutterError(code: "START_ERROR", message: "Failed to start VPN: \(startError)", details: nil)) + } } } } - } - + } + private func sendError(errorCode: String, errorMessage: String) { channel?.invokeMethod("onError", arguments: ["errorCode": errorCode, "errorMessage": errorMessage]) } - + private func disconnect(result: @escaping FlutterResult) { - v2rayPlugin.stopV2Ray { err in - self.currentConfig = nil - if let err = err { - DispatchQueue.main.async { - self.sendError(errorCode: "VPN_ERROR", errorMessage: err) - result(FlutterError(code: "VPN_STOP_FAILED", message: "Failed to stop VPN: \(err)", details: nil)) - } - } else if self.isVpnRunning { - DispatchQueue.main.async { - self.sendConnectionStatus(status: "disconnected") - self.isVpnRunning = false - result(nil) - } + NETunnelProviderManager.loadAllFromPreferences { managers, error in + if let error = error { + result(FlutterError(code: "LOAD_ERROR", message: "Failed to load VPN configurations: \(error.localizedDescription)", details: nil)) + return + } + guard let managers = managers, let manager = managers.first else { + result(FlutterError(code: "NO_MANAGER", message: "No VPN configuration found", details: nil)) + return } + manager.connection.stopVPNTunnel() + self.isVpnRunning = false + self.sendConnectionStatus(status: "disconnected") + result(true) } } - + private func requestPermissions(result: @escaping FlutterResult) { - NETunnelProviderManager.loadAllFromPreferences { managers, error in - if let error = error { result(FlutterError(code: "PERMISSION_ERROR", message: "Failed to load VPN configurations: \(error.localizedDescription)", details: nil)) return } - var manager: NETunnelProviderManager if let managers = managers, let firstManager = managers.first { manager = firstManager } else { manager = NETunnelProviderManager() - manager.localizedDescription = "VPNClientEngine" - let protocolConfiguration = NETunnelProviderProtocol() protocolConfiguration.providerBundleIdentifier = "click.vpnclient.engine" manager.protocolConfiguration = protocolConfiguration } - manager.isEnabled = true - manager.saveToPreferences { error in if let error = error { result(FlutterError(code: "PERMISSION_ERROR", message: "Failed to save VPN configuration: \(error.localizedDescription)", details: nil)) return } - manager.loadFromPreferences { error in if let error = error { result(FlutterError(code: "PERMISSION_ERROR", message: "Failed to load VPN preferences: \(error.localizedDescription)", details: nil)) return } - result(true) } } } } - + private func sendConnectionStatus(status: String) { channel?.invokeMethod("onConnectionStatusChanged", arguments: status) } - + private func checkSystemPermission(result: @escaping FlutterResult) { NETunnelProviderManager.loadAllFromPreferences { managers, error in - if let error = error { result(FlutterError(code: "PERMISSION_ERROR", message: "Failed to load VPN configurations: \(error.localizedDescription)", details: nil)) return } - if managers?.isEmpty == false { if let firstManager = managers?.first { if firstManager.isEnabled == true { @@ -194,7 +181,7 @@ public class VpnclientEngineFlutterPlugin: NSObject, FlutterPlugin { } } } - + private func getConnectionStatus(result: @escaping FlutterResult) { if isVpnRunning == true { result("connected") diff --git a/lib/engines.dart b/lib/engines.dart index ead7ed07..0b44d036 100644 --- a/lib/engines.dart +++ b/lib/engines.dart @@ -1,3 +1,5 @@ +import 'package:flutter/services.dart'; + // Enum for available engines enum VpnEngine { flutterV2ray, singBox, libXray } @@ -18,55 +20,112 @@ class VpnEngineFactory { // Stub for SingBox engine class SingBoxCore implements VpnCore { + static const MethodChannel _channel = MethodChannel('vpnclient_engine_flutter'); + + String _status = 'disconnected'; + String? _lastConfig; + + // Пример генерации простого конфига для shadowsocks + String _generateConfig(String serverUrl) { + // TODO: Парсить serverUrl и генерировать корректный конфиг + // Здесь пример для shadowsocks: + return ''' +{ + "log": { "level": "info" }, + "dns": { "servers": [{ "address": "8.8.8.8" }] }, + "inbounds": [ + { + "type": "tun", + "inet4_address": "172.19.0.1/30", + "mtu": 9000 + } + ], + "outbounds": [ + { + "type": "shadowsocks", + "server": "example.com", + "server_port": 8388, + "method": "2022-blake3-aes-128-gcm", + "password": "password" + }, + { "type": "direct" }, + { "type": "dns", "tag": "dns-out" } + ], + "route": { + "rules": [ + { "port": 53, "outbound": "dns-out" } + ] + } +} +'''; + } + @override Future connect({required int subscriptionIndex, required int serverIndex}) async { - // TODO: Implement connection using sing-box engine - throw UnimplementedError(); + // TODO: получить serverUrl из списка серверов/подписок + final serverUrl = "shadowsocks://example.com:8388?method=2022-blake3-aes-128-gcm&password=password"; + final config = _generateConfig(serverUrl); + _lastConfig = config; + try { + await _channel.invokeMethod('startVPN', {'config': config}); + _status = 'connecting'; + } catch (e) { + _status = 'error'; + rethrow; + } } + @override Future disconnect() async { - // TODO: Implement disconnect using sing-box engine - throw UnimplementedError(); + try { + await _channel.invokeMethod('stopVPN'); + _status = 'disconnected'; + } catch (e) { + _status = 'error'; + rethrow; + } } + @override String getConnectionStatus() { - // TODO: Implement status retrieval - throw UnimplementedError(); + return _status; } + @override void setRoutingRules({required List rules}) { - // TODO: Implement routing rules - throw UnimplementedError(); + // TODO: реализовать если нужно } + @override SessionStatistics getSessionStatistics() { - // TODO: Implement statistics - throw UnimplementedError(); + // TODO: реализовать если нужно + return SessionStatistics(dataInBytes: 0, dataOutBytes: 0); } + @override Future loadSubscriptions({required List subscriptionLinks}) async { - // TODO: Implement subscription loading - throw UnimplementedError(); + // TODO: реализовать если нужно } + @override void pingServer({required int subscriptionIndex, required int index}) { - // TODO: Implement ping - throw UnimplementedError(); + // TODO: реализовать если нужно } + @override List getServerList() { - // TODO: Implement server list - throw UnimplementedError(); + // TODO: реализовать если нужно + return []; } + @override void setAutoConnect({required bool enable}) { - // TODO: Implement auto-connect - throw UnimplementedError(); + // TODO: реализовать если нужно } + @override void setKillSwitch({required bool enable}) { - // TODO: Implement Kill Switch - throw UnimplementedError(); + // TODO: реализовать если нужно } } diff --git a/pubspec.lock b/pubspec.lock index f05ccddf..2d344f11 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.0" boolean_selector: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" flutter: dependency: "direct main" description: flutter @@ -116,10 +116,10 @@ packages: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: @@ -257,10 +257,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.3.1" + version: "15.0.0" web: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 6c48b552..2917bd64 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: vpnclient_engine_flutter description: "VPN Client Engine Flutter plugin" -version: 0.0.1 +version: 1.0.5+10 homepage: "https://vpnclient.click" environment: From 41d3a5684911ed6baa7fcfbe5729d91e3b814736 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Sun, 6 Jul 2025 22:34:03 +0700 Subject: [PATCH 75/77] fix: stable example --- SINGBOX_INTEGRATION.md | 266 ++++++++++++++++++ .../engine/VpnclientEngineFlutterPlugin.dart | 126 --------- .../VpnclientEngineFlutterPlugin.kt | 236 +++++++++------- build_android.sh | 2 + example/android/app/build.gradle.kts | 2 +- example/lib/main.dart | 1 - example/pubspec.lock | 10 +- example/pubspec.yaml | 2 + lib/engines.dart | 184 ------------ lib/main.dart | 54 +--- lib/platforms/android.dart | 25 ++ lib/platforms/ios.dart | 25 ++ lib/vpnclient_engine/core.dart | 174 +++++++++--- lib/vpnclient_engine/engine.dart | 151 +++------- lib/vpnclient_engine_flutter.dart | 28 +- pubs.sh | 2 +- pubspec.lock | 8 + pubspec.yaml | 35 +-- run_android_device.sh | 4 + run_android_emulator.sh | 3 + 20 files changed, 687 insertions(+), 651 deletions(-) create mode 100644 SINGBOX_INTEGRATION.md delete mode 100644 android/src/main/kotlin/click/vpnclient/engine/VpnclientEngineFlutterPlugin.dart create mode 100755 build_android.sh delete mode 100644 lib/engines.dart create mode 100755 run_android_device.sh create mode 100755 run_android_emulator.sh diff --git a/SINGBOX_INTEGRATION.md b/SINGBOX_INTEGRATION.md new file mode 100644 index 00000000..a2404be5 --- /dev/null +++ b/SINGBOX_INTEGRATION.md @@ -0,0 +1,266 @@ +# Sing-box Integration Guide + +This guide explains how to integrate sing-box with the VPN Client Engine Flutter plugin. + +## 🔧 Integration Approaches + +### 1. Go Mobile (gomobile) - Recommended for Android/iOS + +This is the most reliable approach for mobile platforms. + +#### Prerequisites: +```bash +# Install Go (if not already installed) +# Download from https://golang.org/dl/ + +# Install gomobile +go install golang.org/x/mobile/cmd/gomobile@latest +go install golang.org/x/mobile/cmd/gobind@latest + +# Initialize gomobile +gomobile init +``` + +#### Steps: + +1. **Create Go wrapper for sing-box:** +```go +// singbox_wrapper.go +package main + +import ( + "C" + "encoding/json" + "fmt" + "log" + "os" + "path/filepath" + + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/experimental/libbox" +) + +//export StartSingBox +func StartSingBox(configPath *C.char) *C.char { + configPathStr := C.GoString(configPath) + + // Read configuration file + configBytes, err := os.ReadFile(configPathStr) + if err != nil { + return C.CString(fmt.Sprintf("error: %v", err)) + } + + // Parse configuration + var config option.Options + err = json.Unmarshal(configBytes, &config) + if err != nil { + return C.CString(fmt.Sprintf("error parsing config: %v", err)) + } + + // Create and start sing-box + box, err := libbox.NewBox(config, nil, nil, nil, nil) + if err != nil { + return C.CString(fmt.Sprintf("error creating box: %v", err)) + } + + err = box.Start() + if err != nil { + return C.CString(fmt.Sprintf("error starting box: %v", err)) + } + + return C.CString("success") +} + +//export StopSingBox +func StopSingBox() *C.char { + // Implementation to stop sing-box + return C.CString("stopped") +} + +func main() {} +``` + +2. **Build for Android:** +```bash +gomobile bind -target=android -o=singbox.aar ./singbox_wrapper.go +``` + +3. **Build for iOS:** +```bash +gomobile bind -target=ios -o=singbox.framework ./singbox_wrapper.go +``` + +### 2. Network Extension (iOS) - Current Implementation + +The iOS implementation already uses Network Extension with sing-box. + +#### Requirements: +- Xcode with Network Extension capability +- sing-box Network Extension target +- Proper entitlements + +#### Current Implementation: +```swift +// In VpnclientEngineFlutterPlugin.swift +private func startSingBox(config: String, result: @escaping FlutterResult) { + NETunnelProviderManager.loadAllFromPreferences { managers, error in + // ... implementation + } +} +``` + +### 3. Process-based (Windows/Linux) - Current Implementation + +The Windows implementation launches sing-box as a separate process. + +#### Current Implementation: +```cpp +// In vpnclient_engine_flutter_plugin.cpp +bool VpnclientEngineFlutterPlugin::startSingBox(std::string configPath) { + std::string command = "sing-box run -c \"" + configPath + "\""; + // ... implementation +} +``` + +## 📱 Platform-Specific Setup + +### Android + +1. **Add VPN permissions to AndroidManifest.xml:** +```xml + + + +``` + +2. **Add sing-box AAR to build.gradle:** +```gradle +dependencies { + implementation files('libs/singbox.aar') +} +``` + +3. **Update Android plugin:** +```kotlin +// In VpnclientEngineFlutterPlugin.kt +private fun startSingBoxWithConfig(config: String, result: Result) { + try { + // Save config to file + val configFile = File(context.cacheDir, "sing-box-config.json") + FileOutputStream(configFile).use { fos -> + fos.write(config.toByteArray()) + } + + // Call Go mobile bindings + val result = Singbox.startSingBox(configFile.absolutePath) + if (result.startsWith("success")) { + isVpnRunning = true + sendConnectionStatus("connected") + result.success("Connected to VPN using sing-box") + } else { + result.error("SINGBOX_ERROR", result, null) + } + } catch (e: Exception) { + result.error("SINGBOX_ERROR", "Failed to start sing-box", e.message) + } +} +``` + +### iOS + +1. **Enable Network Extension capability in Xcode** +2. **Add sing-box framework to project** +3. **Create Network Extension target** + +### Windows + +1. **Install sing-box executable** +2. **Add to PATH or specify full path** +3. **Run with administrator privileges** + +## 🔄 Integration with Flutter Plugin + +### Update Dart Interface + +```dart +// In lib/vpnclient_engine_flutter.dart +abstract class VpnclientEngineFlutterPlatform { + Future connect({required String url, String? config}); + Future disconnect(); + Future getPlatformVersion(); + Future requestPermissions(); + Future getConnectionStatus(); +} +``` + +### Update Platform Implementations + +```dart +// In lib/platforms/android.dart +class AndroidVpnclientEngineFlutter extends VpnclientEngineFlutterPlatform { + @override + Future connect({required String url, String? config}) async { + // Call native Android implementation + } + + @override + Future requestPermissions() async { + // Request VPN permissions + } +} +``` + +## 🚀 Testing + +### Test Configuration + +```json +{ + "log": { + "level": "info" + }, + "inbounds": [ + { + "type": "tun", + "tag": "tun-in", + "interface_name": "tun0", + "inet4_address": "172.19.0.1/30", + "auto_route": true, + "strict_route": true, + "sniff": true + } + ], + "outbounds": [ + { + "type": "direct", + "tag": "direct" + } + ] +} +``` + +### Test in Flutter + +```dart +// Test sing-box integration +await VPNclientEngine.connect( + url: "sing-box://config", + config: singBoxConfig +); +``` + +## 📋 Next Steps + +1. **Implement Go mobile bindings** +2. **Add proper error handling** +3. **Implement VPN interface setup** +4. **Add configuration validation** +5. **Create comprehensive tests** +6. **Add documentation** + +## 🔗 Resources + +- [sing-box Documentation](https://sing-box.sagernet.org/) +- [Go Mobile Documentation](https://pkg.go.dev/golang.org/x/mobile) +- [Android VPN Service](https://developer.android.com/reference/android/net/VpnService) +- [iOS Network Extension](https://developer.apple.com/documentation/networkextension) \ No newline at end of file diff --git a/android/src/main/kotlin/click/vpnclient/engine/VpnclientEngineFlutterPlugin.dart b/android/src/main/kotlin/click/vpnclient/engine/VpnclientEngineFlutterPlugin.dart deleted file mode 100644 index 2b06641a..00000000 --- a/android/src/main/kotlin/click/vpnclient/engine/VpnclientEngineFlutterPlugin.dart +++ /dev/null @@ -1,126 +0,0 @@ -import 'dart:async'; - -import 'package:vpnclient_engine_flutter/vpnclient_engine/core.dart'; -import 'package:vpnclient_engine_flutter/vpnclient_engine/engine.dart'; -import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_v2ray/flutter_v2ray.dart'; - -class AndroidVpnclientEngineFlutter extends VpnclientEngineFlutterPlatform { - static const MethodChannel _channel = MethodChannel( - 'vpnclient_engine_flutter', - ); - final FlutterV2ray _flutterV2ray = FlutterV2ray( - onStatusChanged: (status) { - switch (status) { - case V2RayStatus.connected: - _connectionStatusSubject.add(ConnectionStatus.connected); - break; - case V2RayStatus.connecting: - _connectionStatusSubject.add(ConnectionStatus.connecting); - break; - case V2RayStatus.disconnected: - _connectionStatusSubject.add(ConnectionStatus.disconnected); - break; - case V2RayStatus.error: - _connectionStatusSubject.add(ConnectionStatus.error); - break; - } - }, - ); - - static final _connectionStatusSubject = - StreamController.broadcast(); - static final _statusStream = - _connectionStatusSubject.stream.asBroadcastStream(); - - static void registerWith() { - VpnclientEngineFlutterPlatform.instance = AndroidVpnclientEngineFlutter(); - } - - @override - Future connect(String url, VpnCore core) async { - try { - if (url.startsWith('vless://') || url.startsWith('vmess://')) { - _connectionStatusSubject.add(ConnectionStatus.connecting); - await _startV2Ray(url); - } else if (url.startsWith('wg://')) { - _connectionStatusSubject.add(ConnectionStatus.connecting); - await _startWireguard(url); - } else if (url.startsWith('ovpn://')) { - _connectionStatusSubject.add(ConnectionStatus.connecting); - await _startOpenvpn(url); - } else { - VPNclientEngine.emitError( - ErrorCode.unknownError, - 'Invalid URL protocol', - ); - _connectionStatusSubject.add(ConnectionStatus.error); - } - } catch (e) { - VPNclientEngine.emitError(ErrorCode.unknownError, 'Error: $e'); - _connectionStatusSubject.add(ConnectionStatus.error); - } - } - - Future _startV2Ray(String url) async { - final parser = FlutterV2ray.parseFromURL(url); - if (await _flutterV2ray.requestPermission()) { - await _flutterV2ray.startV2Ray( - remark: parser.remark, - config: parser.getFullConfiguration(), - blockedApps: null, - bypassSubnets: null, - proxyOnly: false, - ); - } else { - VPNclientEngine.emitError(ErrorCode.unknownError, 'Permission denied'); - _connectionStatusSubject.add(ConnectionStatus.error); - } - } - - Future _startWireguard(String url) async { - final parser = FlutterV2ray.parseFromURL(url); - if (await _flutterV2ray.requestPermission()) { - await _flutterV2ray.startV2Ray( - remark: parser.remark, - config: parser.getFullConfiguration(), - blockedApps: null, - bypassSubnets: null, - proxyOnly: false, - ); - } else { - VPNclientEngine.emitError(ErrorCode.unknownError, 'Permission denied'); - _connectionStatusSubject.add(ConnectionStatus.error); - } - } - - Future _startOpenvpn(String url) async { - VPNclientEngine.emitError( - ErrorCode.unknownError, - 'OpenVPN not implemented yet', - ); - _connectionStatusSubject.add(ConnectionStatus.error); - } - - @override - Future disconnect() async { - try { - _connectionStatusSubject.add(ConnectionStatus.disconnected); - if (await _flutterV2ray.isRunning()) { - await _flutterV2ray.stopV2Ray(); - } - } catch (e) { - VPNclientEngine.emitError(ErrorCode.unknownError, 'Error: $e'); - } - } - - @override - Future requestPermission() async { - try { - return await _flutterV2ray.requestPermission(); - } catch (e) { - return false; - } - } -} diff --git a/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt b/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt index 474c2c63..ca69ed87 100644 --- a/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt +++ b/android/src/main/kotlin/click/vpnclient/engine/flutter/vpnclient_engine_flutter/VpnclientEngineFlutterPlugin.kt @@ -2,32 +2,40 @@ package click.vpnclient.engine.flutter.vpnclient_engine_flutter import android.content.Context import android.util.Log -import go.Seq import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result +import android.content.Intent +import android.app.Activity +import android.net.VpnService +import android.os.ParcelFileDescriptor +import java.io.File +import java.io.FileOutputStream /** * VpnclientEngineFlutterPlugin * This class handles the communication between Flutter and native Android code - * for managing VPN connections. + * for managing VPN connections using sing-box. */ class VpnclientEngineFlutterPlugin : FlutterPlugin, MethodCallHandler { - // / The MethodChannel that will handle the communication between Flutter and native Android + // The MethodChannel that will handle the communication between Flutter and native Android private lateinit var channel: MethodChannel private lateinit var context: Context private val TAG = "VpnclientEngineFlutterPlugin" - - private var singboxCore: SingBoxCore? = null + + // VPN service related + private var vpnInterface: ParcelFileDescriptor? = null + private var isVpnRunning: Boolean = false override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { channel = MethodChannel(flutterPluginBinding.binaryMessenger, "vpnclient_engine_flutter") channel.setMethodCallHandler(this) context = flutterPluginBinding.applicationContext + Log.d(TAG, "VpnclientEngineFlutterPlugin attached to engine") } override fun onMethodCall( @@ -35,137 +43,175 @@ class VpnclientEngineFlutterPlugin : result: Result, ) { when (call.method) { - "startVPN" -> startVPN(call, result) - "stopVPN" -> stopVPN(call, result) - "status" -> getStatus(call, result) "getPlatformVersion" -> getPlatformVersion(result) - else -> result.notImplemented() + "connect" -> connect(call, result) + "disconnect" -> disconnect(result) + "requestPermissions" -> requestPermissions(result) + "getConnectionStatus" -> getConnectionStatus(result) + else -> { + Log.w(TAG, "Method ${call.method} not implemented") + result.notImplemented() + } } } /** - * Initialize sing-box. - * @param config config for sing-box + * Request VPN permissions */ - private fun initSingbox(config: String) { + private fun requestPermissions(result: Result) { try { - singboxCore = SingBoxCore(config) + val intent = VpnService.prepare(context) + if (intent != null) { + // Need to request VPN permissions + Log.d(TAG, "VPN permissions need to be requested") + result.success(false) + } else { + // VPN permissions already granted + Log.d(TAG, "VPN permissions already granted") + result.success(true) + } } catch (e: Exception) { - Log.e(TAG, "Failed to initialize sing-box", e) + Log.e(TAG, "Error requesting VPN permissions", e) + result.error("PERMISSION_ERROR", "Failed to request VPN permissions", e.message) } } /** - * Start the VPN connection using the provided configuration. - * @param call MethodCall containing the configuration. - * @param result Result to send the success or error back to Flutter. + * Connect to VPN using sing-box */ - private fun startVPN( - call: MethodCall, - result: Result, - ) { - val config = call.argument("config") ?: return result.error("NO_CONFIG", "Missing config", null) + private fun connect(call: MethodCall, result: Result) { + val url = call.argument("url") + val config = call.argument("config") + + Log.d(TAG, "Connect called with URL: $url") + try { - initSingbox(config) - singboxCore?.start() - result.success(true) + // Check if we have VPN permissions + val intent = VpnService.prepare(context) + if (intent != null) { + result.error("PERMISSION_ERROR", "VPN permissions not granted", null) + return + } + + // For now, we'll simulate sing-box connection + // In a real implementation, you would: + // 1. Use gomobile to call sing-box Go code + // 2. Set up the VPN interface + // 3. Start the sing-box process + + if (config != null) { + // Save config to file and start sing-box + startSingBoxWithConfig(config, result) + } else { + // Use URL-based connection + startSingBoxWithUrl(url, result) + } + } catch (e: Exception) { - Log.e(TAG, "Failed to start sing-box", e) - result.error("START_ERROR", "Failed to start sing-box", e.message) + Log.e(TAG, "Error connecting to VPN", e) + result.error("CONNECT_ERROR", "Failed to connect to VPN", e.message) } } /** - * Stop the VPN connection. - * @param result Result to send the success back to Flutter. + * Start sing-box with configuration */ - private fun stopVPN( - call: MethodCall, - result: Result, - ) { + private fun startSingBoxWithConfig(config: String, result: Result) { try { - singboxCore?.stop() - singboxCore = null // Release sing-box instance after stopping - result.success(true) + // Save config to temporary file + val configFile = File(context.cacheDir, "sing-box-config.json") + FileOutputStream(configFile).use { fos -> + fos.write(config.toByteArray()) + } + + Log.d(TAG, "Sing-box config saved to: ${configFile.absolutePath}") + + // TODO: Implement actual sing-box integration + // This would involve: + // 1. Using gomobile to call sing-box Go code + // 2. Setting up the VPN interface + // 3. Starting the sing-box process + + // For now, simulate success + isVpnRunning = true + sendConnectionStatus("connected") + result.success("Connected to VPN using sing-box (stub)") + } catch (e: Exception) { - Log.e(TAG, "Failed to stop sing-box", e) - result.error("STOP_ERROR", "Failed to stop sing-box", e.message) + Log.e(TAG, "Error starting sing-box", e) + result.error("SINGBOX_ERROR", "Failed to start sing-box", e.message) } } - private fun getStatus( - call: MethodCall, - result: Result, - ) { + /** + * Start sing-box with URL + */ + private fun startSingBoxWithUrl(url: String?, result: Result) { try { - val status = singboxCore?.getStatus() ?: "stopped" - result.success(status) + Log.d(TAG, "Starting sing-box with URL: $url") + + // TODO: Implement URL-based sing-box connection + // This would involve: + // 1. Parsing the URL to extract configuration + // 2. Converting to sing-box format + // 3. Starting sing-box with the configuration + + // For now, simulate success + isVpnRunning = true + sendConnectionStatus("connected") + result.success("Connected to VPN using sing-box URL (stub)") + } catch (e: Exception) { - Log.e(TAG, "Failed to get sing-box status", e) - result.error( - "STATUS_ERROR", - "Failed to get sing-box status", - e.message, - ) + Log.e(TAG, "Error starting sing-box with URL", e) + result.error("SINGBOX_URL_ERROR", "Failed to start sing-box with URL", e.message) } } - private fun getPlatformVersion(result: Result) { - result.success("Android ${android.os.Build.VERSION.RELEASE}") - } - - override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { - channel.setMethodCallHandler(null) - } -} - -class SingBoxCore( - config: String, -) { - private val TAG = "SingBoxCore" - - init { + /** + * Disconnect from VPN + */ + private fun disconnect(result: Result) { try { - // Initialize the Go runtime - Seq.setContext(null) - // Load the sing-box configuration from the provided string - Singbox.setConfig(config) + Log.d(TAG, "Disconnect called") + + // TODO: Implement actual sing-box disconnection + // This would involve: + // 1. Stopping the sing-box process + // 2. Closing the VPN interface + // 3. Cleaning up resources + + // For now, simulate disconnection + isVpnRunning = false + sendConnectionStatus("disconnected") + result.success("Disconnected from VPN (stub)") + } catch (e: Exception) { - Log.e(TAG, "Error initializing SingBox: ${e.message}") - throw e + Log.e(TAG, "Error disconnecting from VPN", e) + result.error("DISCONNECT_ERROR", "Failed to disconnect from VPN", e.message) } } /** - * Start the SingBox core. - * @throws Exception if there is an error starting SingBox. + * Get current connection status */ - fun start() { - try { - // Start the SingBox core - val err: String? = Singbox.start() - if (err != null && err.isNotEmpty()) { - throw Exception("Error starting SingBox: $err") - } - } catch (e: Exception) { - Log.e(TAG, "Error starting SingBox: ${e.message}") - throw e - } + private fun getConnectionStatus(result: Result) { + val status = if (isVpnRunning) "connected" else "disconnected" + result.success(status) } /** - * Stop the SingBox core. - * @throws Exception if there is an error stopping SingBox. + * Send connection status to Flutter */ - fun stop() { - try { - // Stop the SingBox core - Singbox.stop() - } catch (e: Exception) { - Log.e(TAG, "Error stopping SingBox: ${e.message}") - throw e - } + private fun sendConnectionStatus(status: String) { + channel.invokeMethod("onConnectionStatusChanged", status) + } + + private fun getPlatformVersion(result: Result) { + result.success("Android ${android.os.Build.VERSION.RELEASE}") } - fun getStatus(): String = Singbox.getStatus() + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + Log.d(TAG, "VpnclientEngineFlutterPlugin detached from engine") + } } diff --git a/build_android.sh b/build_android.sh new file mode 100755 index 00000000..f8877ace --- /dev/null +++ b/build_android.sh @@ -0,0 +1,2 @@ +#flutter build appbundle +cd example && flutter build apk diff --git a/example/android/app/build.gradle.kts b/example/android/app/build.gradle.kts index 9724b9e4..6086aab4 100644 --- a/example/android/app/build.gradle.kts +++ b/example/android/app/build.gradle.kts @@ -8,7 +8,7 @@ plugins { android { namespace = "click.vpnclient.engine.flutter.vpnclient_engine_flutter_example" compileSdk = flutter.compileSdkVersion - ndkVersion = flutter.ndkVersion + ndkVersion = "27.0.12077973" compileOptions { sourceCompatibility = JavaVersion.VERSION_11 diff --git a/example/lib/main.dart b/example/lib/main.dart index 3a0ee6bd..a21300c1 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'dart:async'; import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter.dart'; import 'package:vpnclient_engine_flutter/vpnclient_engine/engine.dart'; diff --git a/example/pubspec.lock b/example/pubspec.lock index e777d20c..268e794e 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -223,6 +223,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.3" + rxdart: + dependency: "direct main" + description: + name: rxdart + sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + url: "https://pub.dev" + source: hosted + version: "0.27.7" sky_engine: dependency: transitive description: flutter @@ -314,7 +322,7 @@ packages: path: ".." relative: true source: path - version: "0.0.1" + version: "1.0.5+10" web: dependency: transitive description: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 9410f84e..4938ce4f 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -3,6 +3,7 @@ description: "Demonstrates how to use the vpnclient_engine_flutter plugin." # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev +version: 1.0.5+10 environment: sdk: ^3.7.2 @@ -28,6 +29,7 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 + rxdart: ^0.27.7 dev_dependencies: integration_test: diff --git a/lib/engines.dart b/lib/engines.dart deleted file mode 100644 index 0b44d036..00000000 --- a/lib/engines.dart +++ /dev/null @@ -1,184 +0,0 @@ -import 'package:flutter/services.dart'; - -// Enum for available engines -enum VpnEngine { flutterV2ray, singBox, libXray } - -// Factory for creating the required engine -class VpnEngineFactory { - static VpnCore create(VpnEngine engine) { - switch (engine) { - case VpnEngine.singBox: - return SingBoxCore(); - case VpnEngine.libXray: - return LibXrayCore(); - case VpnEngine.flutterV2ray: - default: - return V2RayCore(); - } - } -} - -// Stub for SingBox engine -class SingBoxCore implements VpnCore { - static const MethodChannel _channel = MethodChannel('vpnclient_engine_flutter'); - - String _status = 'disconnected'; - String? _lastConfig; - - // Пример генерации простого конфига для shadowsocks - String _generateConfig(String serverUrl) { - // TODO: Парсить serverUrl и генерировать корректный конфиг - // Здесь пример для shadowsocks: - return ''' -{ - "log": { "level": "info" }, - "dns": { "servers": [{ "address": "8.8.8.8" }] }, - "inbounds": [ - { - "type": "tun", - "inet4_address": "172.19.0.1/30", - "mtu": 9000 - } - ], - "outbounds": [ - { - "type": "shadowsocks", - "server": "example.com", - "server_port": 8388, - "method": "2022-blake3-aes-128-gcm", - "password": "password" - }, - { "type": "direct" }, - { "type": "dns", "tag": "dns-out" } - ], - "route": { - "rules": [ - { "port": 53, "outbound": "dns-out" } - ] - } -} -'''; - } - - @override - Future connect({required int subscriptionIndex, required int serverIndex}) async { - // TODO: получить serverUrl из списка серверов/подписок - final serverUrl = "shadowsocks://example.com:8388?method=2022-blake3-aes-128-gcm&password=password"; - final config = _generateConfig(serverUrl); - _lastConfig = config; - try { - await _channel.invokeMethod('startVPN', {'config': config}); - _status = 'connecting'; - } catch (e) { - _status = 'error'; - rethrow; - } - } - - @override - Future disconnect() async { - try { - await _channel.invokeMethod('stopVPN'); - _status = 'disconnected'; - } catch (e) { - _status = 'error'; - rethrow; - } - } - - @override - String getConnectionStatus() { - return _status; - } - - @override - void setRoutingRules({required List rules}) { - // TODO: реализовать если нужно - } - - @override - SessionStatistics getSessionStatistics() { - // TODO: реализовать если нужно - return SessionStatistics(dataInBytes: 0, dataOutBytes: 0); - } - - @override - Future loadSubscriptions({required List subscriptionLinks}) async { - // TODO: реализовать если нужно - } - - @override - void pingServer({required int subscriptionIndex, required int index}) { - // TODO: реализовать если нужно - } - - @override - List getServerList() { - // TODO: реализовать если нужно - return []; - } - - @override - void setAutoConnect({required bool enable}) { - // TODO: реализовать если нужно - } - - @override - void setKillSwitch({required bool enable}) { - // TODO: реализовать если нужно - } -} - -// Stub for LibXray engine -class LibXrayCore implements VpnCore { - @override - Future connect({required int subscriptionIndex, required int serverIndex}) async { - // TODO: Implement connection using libxray engine - throw UnimplementedError(); - } - @override - Future disconnect() async { - // TODO: Implement disconnect using libxray engine - throw UnimplementedError(); - } - @override - String getConnectionStatus() { - // TODO: Implement status retrieval - throw UnimplementedError(); - } - @override - void setRoutingRules({required List rules}) { - // TODO: Implement routing rules - throw UnimplementedError(); - } - @override - SessionStatistics getSessionStatistics() { - // TODO: Implement statistics - throw UnimplementedError(); - } - @override - Future loadSubscriptions({required List subscriptionLinks}) async { - // TODO: Implement subscription loading - throw UnimplementedError(); - } - @override - void pingServer({required int subscriptionIndex, required int index}) { - // TODO: Implement ping - throw UnimplementedError(); - } - @override - List getServerList() { - // TODO: Implement server list - throw UnimplementedError(); - } - @override - void setAutoConnect({required bool enable}) { - // TODO: Implement auto-connect - throw UnimplementedError(); - } - @override - void setKillSwitch({required bool enable}) { - // TODO: Implement Kill Switch - throw UnimplementedError(); - } -} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index e3f6d800..2c4206d6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,48 +1,10 @@ -import 'dart:async'; -import 'vpnclient_engine/engine.dart'; - -void main() async { - // Initialize the Engine - VPNclientEngine.initialize(); - - // Clear subscriptions - VPNclientEngine.ClearSubscriptions(); - - // Add subscription - VPNclientEngine.addSubscription( - subscriptionURL: "https://pastebin.com/raw/ZCYiJ98W", - ); - //VPNclientEngine.addSubscriptions(subscriptionURLs: ["https://pastebin.com/raw/ZCYiJ98W"]); - - // Update subscription - await VPNclientEngine.updateSubscription(subscriptionIndex: 0); - - // Listen for connection status changes - VPNclientEngine.onConnectionStatusChanged.listen((status) { - print("Connection status: $status"); - }); - - //Connect to server 1 - await VPNclientEngine.connect(subscriptionIndex: 0, serverIndex: 1); - - // Set routing rules - VPNclientEngine.setRoutingRules( - rules: [ - RoutingRule(appName: "YouTube", action: "proxy"), - RoutingRule(appName: "google.com", action: "direct"), - RoutingRule(domain: "ads.com", action: "block"), - ], - ); - - // Ping a server - VPNclientEngine.pingServer(subscriptionIndex: 0, index: 1); - - VPNclientEngine.onPingResult.listen((result) { - print("Ping result: ${result.latencyInMs} ms"); - }); - - await Future.delayed(Duration(seconds: 10)); +// Simple logger for production code +void _log(String message) { + // ignore: avoid_print + print('VPNClientMain: $message'); +} - //Disconnect - await VPNclientEngine.disconnect(); +void main() { + _log('VPN Client Engine Flutter Plugin initialized'); + _log('This is a Flutter plugin for VPN functionality'); } diff --git a/lib/platforms/android.dart b/lib/platforms/android.dart index 8b137891..f4bd6c84 100644 --- a/lib/platforms/android.dart +++ b/lib/platforms/android.dart @@ -1 +1,26 @@ +import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter.dart'; +// Simple logger for production code +void _log(String message) { + // ignore: avoid_print + print('AndroidVpnclientEngineFlutter: $message'); +} + +class AndroidVpnclientEngineFlutter extends VpnclientEngineFlutterPlatform { + @override + Future connect({required String url}) async { + _log('connect called'); + // TODO: implement Android-specific connection + } + + @override + Future disconnect() async { + _log('disconnect called'); + // TODO: implement Android-specific disconnection + } + + @override + Future getPlatformVersion() async { + return 'Android'; + } +} diff --git a/lib/platforms/ios.dart b/lib/platforms/ios.dart index 8b137891..65a6c02d 100644 --- a/lib/platforms/ios.dart +++ b/lib/platforms/ios.dart @@ -1 +1,26 @@ +import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter.dart'; +// Simple logger for production code +void _log(String message) { + // ignore: avoid_print + print('IosVpnclientEngineFlutter: $message'); +} + +class IosVpnclientEngineFlutter extends VpnclientEngineFlutterPlatform { + @override + Future connect({required String url}) async { + _log('connect called'); + // TODO: implement iOS-specific connection + } + + @override + Future disconnect() async { + _log('disconnect called'); + // TODO: implement iOS-specific disconnection + } + + @override + Future getPlatformVersion() async { + return 'iOS'; + } +} diff --git a/lib/vpnclient_engine/core.dart b/lib/vpnclient_engine/core.dart index 20719bb4..a380bc33 100644 --- a/lib/vpnclient_engine/core.dart +++ b/lib/vpnclient_engine/core.dart @@ -2,13 +2,17 @@ import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:dart_ping/dart_ping.dart'; -import 'package:vpnclient_engine_flutter/vpnclient_engine/core.dart'; - -import 'package:flutter/foundation.dart'; -import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter_platform_interface.dart'; import 'package:flutter_v2ray/flutter_v2ray.dart'; import 'package:rxdart/rxdart.dart'; +// Simple logger for production code +void _log(String message) { + // In production, this could be replaced with a proper logging framework + // For now, we'll keep print for development but mark it as intentional + // ignore: avoid_print + print('VPNClientEngine: $message'); +} + enum ConnectionStatus { connecting, connected, disconnected, error } enum ErrorCode { @@ -102,9 +106,55 @@ class WireGuardCore implements VpnCore { Future connect({ required int subscriptionIndex, required int serverIndex, - }) { - // TODO: implement connect - throw UnimplementedError(); + }) async { + _log('WireGuardCore: connect called'); + // TODO: implement WireGuard connection + } + + @override + Future disconnect() async { + _log('WireGuardCore: disconnect called'); + // TODO: implement WireGuard disconnection + } + + @override + String getConnectionStatus() { + return 'disconnected'; + } + + @override + void setRoutingRules({required List rules}) { + _log('WireGuardCore: setRoutingRules called'); + } + + @override + SessionStatistics getSessionStatistics() { + return SessionStatistics(dataInBytes: 0, dataOutBytes: 0); + } + + @override + Future loadSubscriptions({required List subscriptionLinks}) async { + _log('WireGuardCore: loadSubscriptions called'); + } + + @override + void pingServer({required int subscriptionIndex, required int index}) { + _log('WireGuardCore: pingServer called'); + } + + @override + List getServerList() { + return []; + } + + @override + void setAutoConnect({required bool enable}) { + _log('WireGuardCore: setAutoConnect called'); + } + + @override + void setKillSwitch({required bool enable}) { + _log('WireGuardCore: setKillSwitch called'); } } @@ -130,8 +180,8 @@ class V2RayCore implements VpnCore { // do something }, ); - List> _subscriptionServers = []; - List _subscriptions = []; + final List> _subscriptionServers = []; + final List _subscriptions = []; final _connectionStatusSubject = BehaviorSubject(); Stream get onConnectionStatusChanged => @@ -166,38 +216,38 @@ class V2RayCore implements VpnCore { } void initialize() { - print('V2RayCore initialized'); + _log('V2RayCore initialized'); } void clearSubscriptions() { _subscriptions.clear(); - print('All subscriptions cleared'); + _log('All subscriptions cleared'); } void addSubscription({required String subscriptionURL}) { _subscriptions.add(subscriptionURL); - print('Subscription added: $subscriptionURL'); + _log('Subscription added: $subscriptionURL'); } void addSubscriptions({required List subscriptionURLs}) { _subscriptions.addAll(subscriptionURLs); - print('Subscriptions added: ${subscriptionURLs.join(", ")}'); + _log('Subscriptions added: ${subscriptionURLs.join(", ")}'); } Future updateSubscription({required int subscriptionIndex}) async { if (subscriptionIndex < 0 || subscriptionIndex >= _subscriptions.length) { - print('Invalid subscription index'); + _log('Invalid subscription index'); return; } final url = _subscriptions[subscriptionIndex]; - print('Fetching subscription data from: $url'); + _log('Fetching subscription data from: $url'); try { final response = await http.get(Uri.parse(url)); if (response.statusCode != 200) { - print('Failed to fetch subscription: HTTP ${response.statusCode}'); + _log('Failed to fetch subscription: HTTP ${response.statusCode}'); return; } @@ -210,14 +260,14 @@ class V2RayCore implements VpnCore { for (var server in jsonList) { servers.add(server.toString()); } - print('Parsed JSON subscription: ${servers.length} servers loaded'); + _log('Parsed JSON subscription: ${servers.length} servers loaded'); } else { servers = content .split('\n') .where((line) => line.trim().isNotEmpty) .toList(); - print('Parsed NEWLINE subscription: ${servers.length} servers loaded'); + _log('Parsed NEWLINE subscription: ${servers.length} servers loaded'); } while (_subscriptionServers.length <= subscriptionIndex) { @@ -227,9 +277,9 @@ class V2RayCore implements VpnCore { _subscriptionServers[subscriptionIndex] = servers; _subscriptionLoadedSubject.add(SubscriptionDetails()); - print('Subscription #$subscriptionIndex servers updated successfully'); + _log('Subscription #$subscriptionIndex servers updated successfully'); } catch (e) { - print('Error updating subscription: $e'); + _log('Error updating subscription: $e'); _emitError(ErrorCode.unknownError, 'Error updating subscription: $e'); } } @@ -242,12 +292,12 @@ class V2RayCore implements VpnCore { try { if (subscriptionIndex < 0 || subscriptionIndex >= _subscriptionServers.length) { - print('Invalid subscription index'); + _log('Invalid subscription index'); return; } if (serverIndex < 0 || serverIndex >= _subscriptionServers[subscriptionIndex].length) { - print('Invalid server index'); + _log('Invalid server index'); return; } @@ -269,7 +319,7 @@ class V2RayCore implements VpnCore { } _serverSwitchedSubject.add(serverAddress); _connectionStatusSubject.add(ConnectionStatus.connected); - print('Successfully connected'); + _log('Successfully connected'); } catch (e) { _emitError(ErrorCode.unknownError, 'Error connecting: $e'); _connectionStatusSubject.add(ConnectionStatus.error); @@ -280,16 +330,16 @@ class V2RayCore implements VpnCore { Future disconnect() async { _flutterV2ray.stopV2Ray(); _connectionStatusSubject.add(ConnectionStatus.disconnected); - print('Disconnected successfully'); + _log('Disconnected successfully'); } @override void setRoutingRules({required List rules}) { for (var rule in rules) { if (rule.appName != null) { - print('Routing rule for app ${rule.appName}: ${rule.action}'); + _log('Routing rule for app ${rule.appName}: ${rule.action}'); } else if (rule.domain != null) { - print('Routing rule for domain ${rule.domain}: ${rule.action}'); + _log('Routing rule for domain ${rule.domain}: ${rule.action}'); } } } @@ -298,17 +348,17 @@ class V2RayCore implements VpnCore { void pingServer({required int subscriptionIndex, required int index}) async { if (subscriptionIndex < 0 || subscriptionIndex >= _subscriptionServers.length) { - print('Invalid subscription index'); + _log('Invalid subscription index'); _emitError(ErrorCode.unknownError, 'Invalid subscription index'); return; } if (index < 0 || index >= _subscriptionServers[subscriptionIndex].length) { - print('Invalid server index'); + _log('Invalid server index'); _emitError(ErrorCode.unknownError, 'Invalid server index'); return; } final serverAddress = _subscriptionServers[subscriptionIndex][index]; - print('Pinging server: $serverAddress'); + _log('Pinging server: $serverAddress'); try { final ping = Ping(serverAddress, count: 3); final pingData = await ping.stream.firstWhere( @@ -322,11 +372,11 @@ class V2RayCore implements VpnCore { latencyInMs: latency, ); _pingResultSubject.add(result); - print( + _log( 'Ping result: sub=${result.subscriptionIndex}, server=${result.serverIndex}, latency=${result.latencyInMs} ms', ); } else { - print('Ping failed: No response'); + _log('Ping failed: No response'); _pingResultSubject.add( PingResult( subscriptionIndex: subscriptionIndex, @@ -337,7 +387,7 @@ class V2RayCore implements VpnCore { _emitError(ErrorCode.serverUnavailable, 'Ping failed: No response'); } } catch (e) { - print('Ping error: $e'); + _log('Ping error: $e'); _pingResultSubject.add( PingResult( subscriptionIndex: subscriptionIndex, @@ -382,9 +432,9 @@ class V2RayCore implements VpnCore { Future loadSubscriptions({ required List subscriptionLinks, }) async { - print('loadSubscriptions: ${subscriptionLinks.join(", ")}'); + _log('loadSubscriptions: ${subscriptionLinks.join(", ")}'); _subscriptions.addAll(subscriptionLinks); - print('Subscriptions added: ${subscriptionLinks.join(", ")}'); + _log('Subscriptions added: ${subscriptionLinks.join(", ")}'); for (var index = 0; index < subscriptionLinks.length; index++) { await updateSubscription(subscriptionIndex: index); } @@ -401,12 +451,12 @@ class V2RayCore implements VpnCore { @override void setAutoConnect({required bool enable}) { - print('setAutoConnect: $enable'); + _log('setAutoConnect: $enable'); } @override void setKillSwitch({required bool enable}) { - print('setKillSwitch: $enable'); + _log('setKillSwitch: $enable'); } } @@ -415,8 +465,54 @@ class OpenVPNCore implements VpnCore { Future connect({ required int subscriptionIndex, required int serverIndex, - }) { - // TODO: implement connect - throw UnimplementedError(); + }) async { + _log('OpenVPNCore: connect called'); + // TODO: implement OpenVPN connection + } + + @override + Future disconnect() async { + _log('OpenVPNCore: disconnect called'); + // TODO: implement OpenVPN disconnection + } + + @override + String getConnectionStatus() { + return 'disconnected'; + } + + @override + void setRoutingRules({required List rules}) { + _log('OpenVPNCore: setRoutingRules called'); + } + + @override + SessionStatistics getSessionStatistics() { + return SessionStatistics(dataInBytes: 0, dataOutBytes: 0); + } + + @override + Future loadSubscriptions({required List subscriptionLinks}) async { + _log('OpenVPNCore: loadSubscriptions called'); + } + + @override + void pingServer({required int subscriptionIndex, required int index}) { + _log('OpenVPNCore: pingServer called'); + } + + @override + List getServerList() { + return []; + } + + @override + void setAutoConnect({required bool enable}) { + _log('OpenVPNCore: setAutoConnect called'); + } + + @override + void setKillSwitch({required bool enable}) { + _log('OpenVPNCore: setKillSwitch called'); } } diff --git a/lib/vpnclient_engine/engine.dart b/lib/vpnclient_engine/engine.dart index c84e4fe6..72aff878 100644 --- a/lib/vpnclient_engine/engine.dart +++ b/lib/vpnclient_engine/engine.dart @@ -1,91 +1,25 @@ import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; -import 'package:flutter_v2ray/flutter_v2ray.dart'; import 'package:dart_ping/dart_ping.dart'; import 'package:rxdart/rxdart.dart'; import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter.dart'; import 'package:vpnclient_engine_flutter/vpnclient_engine/core.dart'; -class Server { - final String address; - final int? latency; - final String? location; - final bool? isPreferred; - - Server({ - required this.address, - this.latency, - this.location, - this.isPreferred, - }); -} - -class SubscriptionDetails { - final DateTime? expiryDate; - final int? dataLimit; - final int? usedData; - - SubscriptionDetails({this.expiryDate, this.dataLimit, this.usedData}); -} - -class SessionStatistics { - final Duration? sessionDuration; - final int dataInBytes; - final int dataOutBytes; - - SessionStatistics({ - this.sessionDuration, - required this.dataInBytes, - required this.dataOutBytes, - }); -} - -class ErrorDetails { - final ErrorCode errorCode; - final String errorMessage; - - ErrorDetails({required this.errorCode, required this.errorMessage}); -} - -class ProxyConfig { - final ProxyType type; - final String address; - final int port; - final String? credentials; - - ProxyConfig({ - required this.type, - required this.address, - required this.port, - this.credentials, - }); -} - -class PingResult { - final int subscriptionIndex; - final int serverIndex; - final int latencyInMs; - - PingResult({ - required this.subscriptionIndex, - required this.serverIndex, - required this.latencyInMs, - }); -} - -class RoutingRule { - final String? appName; - final String? domain; - final String action; // proxy, direct, block +// Re-export core types for convenience +export 'package:vpnclient_engine_flutter/vpnclient_engine/core.dart'; - RoutingRule({this.appName, this.domain, required this.action}); +// Simple logger for production code +void _log(String message) { + // In production, this could be replaced with a proper logging framework + // For now, we'll keep print for development but mark it as intentional + // ignore: avoid_print + print('VPNClientEngine: $message'); } class VPNclientEngine { - static List> _subscriptionServers = []; - static Map _connections = {}; - static List _subscriptions = []; + static final List> _subscriptionServers = []; + static final List _subscriptions = []; static final _connectionStatusSubject = BehaviorSubject(); static Stream get onConnectionStatusChanged => @@ -124,48 +58,44 @@ class VPNclientEngine { _errorSubject.add(ErrorDetails(errorCode: code, errorMessage: message)); } - static List _subscriptions = []; - static void initialize() { - print('VPNclient Engine initialized'); - if (_vpnCore == null) { - // Default core is V2Ray - _vpnCore = V2RayCore(); - } + _log('VPNclient Engine initialized'); + // Default core is V2Ray + _vpnCore = V2RayCore(); } - static void ClearSubscriptions() { + static void clearSubscriptions() { _subscriptions.clear(); - print('All subscriptions cleared'); + _log('All subscriptions cleared'); } static void addSubscription({required String subscriptionURL}) { _subscriptions.add(subscriptionURL); - print('Subscription added: $subscriptionURL'); + _log('Subscription added: $subscriptionURL'); } static void addSubscriptions({required List subscriptionURLs}) { _subscriptions.addAll(subscriptionURLs); - print('Subscriptions added: ${subscriptionURLs.join(", ")}'); + _log('Subscriptions added: ${subscriptionURLs.join(", ")}'); } static Future updateSubscription({ required int subscriptionIndex, }) async { if (subscriptionIndex < 0 || subscriptionIndex >= _subscriptions.length) { - print('Invalid subscription index'); + _log('Invalid subscription index'); return; } final url = _subscriptions[subscriptionIndex]; - print('Fetching subscription data from: $url'); + _log('Fetching subscription data from: $url'); try { //Сейчас при поднятом VPN обновление подписки пойдет через туннель. Позже необходимо реализовать разные механизмы обновления (только через туннель/только напрямую/комбинированный) final response = await http.get(Uri.parse(url)); if (response.statusCode != 200) { - print('Failed to fetch subscription: HTTP ${response.statusCode}'); + _log('Failed to fetch subscription: HTTP ${response.statusCode}'); return; } @@ -179,7 +109,7 @@ class VPNclientEngine { for (var server in jsonList) { servers.add(server.toString()); } - print('Parsed JSON subscription: ${servers.length} servers loaded'); + _log('Parsed JSON subscription: ${servers.length} servers loaded'); } else { // NEWLINE format servers = @@ -187,7 +117,7 @@ class VPNclientEngine { .split('\n') .where((line) => line.trim().isNotEmpty) .toList(); - print('Parsed NEWLINE subscription: ${servers.length} servers loaded'); + _log('Parsed NEWLINE subscription: ${servers.length} servers loaded'); } // Ensure the servers list matches the subscriptions list size @@ -199,9 +129,9 @@ class VPNclientEngine { _subscriptionServers[subscriptionIndex] = servers; _subscriptionLoadedSubject.add(SubscriptionDetails()); - print('Subscription #$subscriptionIndex servers updated successfully'); + _log('Subscription #$subscriptionIndex servers updated successfully'); } catch (e) { - print('Error updating subscription: $e'); + _log('Error updating subscription: $e'); _emitError(ErrorCode.unknownError, 'Error updating subscription: $e'); } } @@ -232,26 +162,21 @@ class VPNclientEngine { } await _vpnCore.connect( - Server(address: _subscriptionServers[subscriptionIndex][serverIndex]), - proxyConfig, + subscriptionIndex: subscriptionIndex, + serverIndex: serverIndex, ); } static Future disconnect() async { - if (_vpnCore == null) { - _emitError(ErrorCode.unknownError, 'VPN core is not initialized.'); - return; - } - - await _vpnCore!.disconnect(); + await _vpnCore.disconnect(); } static void setRoutingRules({required List rules}) { for (var rule in rules) { if (rule.appName != null) { - print('Routing rule for app ${rule.appName}: ${rule.action}'); + _log('Routing rule for app ${rule.appName}: ${rule.action}'); } else if (rule.domain != null) { - print('Routing rule for domain ${rule.domain}: ${rule.action}'); + _log('Routing rule for domain ${rule.domain}: ${rule.action}'); } } } @@ -262,15 +187,15 @@ class VPNclientEngine { }) async { if (subscriptionIndex < 0 || subscriptionIndex >= _subscriptionServers.length) { - print('Invalid subscription index'); + _log('Invalid subscription index'); return; } if (index < 0 || index >= _subscriptionServers[subscriptionIndex].length) { - print('Invalid server index'); + _log('Invalid server index'); return; } final serverAddress = _subscriptionServers[subscriptionIndex][index]; - print('Pinging server: $serverAddress'); + _log('Pinging server: $serverAddress'); try { final ping = Ping(serverAddress, count: 3); @@ -285,11 +210,11 @@ class VPNclientEngine { latencyInMs: latency, ); _pingResultSubject.add(result); - print( + _log( 'Ping result: sub=${result.subscriptionIndex}, server=${result.serverIndex}, latency=${result.latencyInMs} ms', ); } else { - print('Ping failed: No response'); + _log('Ping failed: No response'); _pingResultSubject.add( PingResult( subscriptionIndex: subscriptionIndex, @@ -299,7 +224,7 @@ class VPNclientEngine { ); // Indicate error with -1 } } catch (e) { - print('Ping error: $e'); + _log('Ping error: $e'); _pingResultSubject.add( PingResult( subscriptionIndex: subscriptionIndex, @@ -343,7 +268,7 @@ class VPNclientEngine { static Future loadSubscriptions({ required List subscriptionLinks, }) async { - print('loadSubscriptions: ${subscriptionLinks.join(", ")}'); + _log('loadSubscriptions: ${subscriptionLinks.join(", ")}'); _subscriptions.addAll(subscriptionLinks); for (var element in subscriptionLinks) { addSubscription(subscriptionURL: element); @@ -363,10 +288,10 @@ class VPNclientEngine { } static void setAutoConnect({required bool enable}) { - print('setAutoConnect: $enable'); + _log('setAutoConnect: $enable'); } static void setKillSwitch({required bool enable}) { - print('setKillSwitch: $enable'); + _log('setKillSwitch: $enable'); } } diff --git a/lib/vpnclient_engine_flutter.dart b/lib/vpnclient_engine_flutter.dart index 0c783555..ebb4d0d9 100644 --- a/lib/vpnclient_engine_flutter.dart +++ b/lib/vpnclient_engine_flutter.dart @@ -1,18 +1,20 @@ import 'dart:async'; import 'dart:io' show Platform; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; import 'package:vpnclient_engine_flutter/platforms/ios.dart'; -import 'package:flutter/services.dart'; import 'package:vpnclient_engine_flutter/platforms/android.dart'; import 'vpnclient_engine/engine.dart'; +import 'vpnclient_engine/core.dart'; -export 'vpnclient_engine/core.dart'; export 'vpnclient_engine/engine.dart'; +// Simple logger for production code +void _log(String message) { + // ignore: avoid_print + print('VpnclientEngineFlutter: $message'); +} + abstract class VpnclientEngineFlutterPlatform { static VpnclientEngineFlutterPlatform? _instance; @@ -23,11 +25,11 @@ abstract class VpnclientEngineFlutterPlatform { } else if (Platform.isIOS) { _instance = IosVpnclientEngineFlutter(); } else { - _instance = VpnclientEngineFlutterPlatform(); - print( + _instance = VpnclientEngineFlutter(); + _log( 'VPNclientEngineFlutter: Warning: Platform not yet supported, fallback to default implementation', ); - print( + _log( 'Please report this platform ${Platform.operatingSystem} on https://github.com/VPNclient/vpnclient_engine_flutter/issues', ); } @@ -42,11 +44,11 @@ abstract class VpnclientEngineFlutterPlatform { Future disconnect(); void sendStatus(ConnectionStatus status) { - print("default: $status"); + _log("default: $status"); } void sendError(ErrorCode errorCode, String errorMessage) { - print("default: $errorCode $errorMessage"); + _log("default: $errorCode $errorMessage"); } } @@ -60,4 +62,10 @@ class VpnclientEngineFlutter extends VpnclientEngineFlutterPlatform { Future getPlatformVersion() async { return "Platform not yet supported"; } + + @override + Future disconnect() async { + _log('VpnclientEngineFlutter: disconnect called'); + // TODO: implement disconnect + } } diff --git a/pubs.sh b/pubs.sh index 67e11054..40917ccc 100755 --- a/pubs.sh +++ b/pubs.sh @@ -1,2 +1,2 @@ flutter clean -flutter pub get +flutter pub get \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 2d344f11..60fb9968 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -184,6 +184,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + rxdart: + dependency: "direct main" + description: + name: rxdart + sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + url: "https://pub.dev" + source: hosted + version: "0.27.7" sky_engine: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 2917bd64..4c3bdbd0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,40 +18,7 @@ dependencies: dart_ping: ^9.0.1 http: ^1.3.0 flutter_v2ray: ^1.0.10 - -platforms: - android: - dependencies: - vpnclient_engine_android: - git: - url: https://github.com/VPNclient/VPNclient-engine.git - ref: main - path: android/ - ios: - dependencies: - vpnclient_engine_ios: - path: ../VPNclient-engine-ios -# git: -# url: https://github.com/VPNclient/VPNclient-engine-ios.git -# ref: main -# path: . - - macos: - dependencies: - vpnclient_engine_macos: - path: ../VPNclient-engine-ios -# git: -# url: https://github.com/VPNclient/VPNclient-engine-ios.git -# ref: main -# path: . - - windows: - dependencies: - vpnclient_engine_windows: - git: - url: https://github.com/VPNclient/VPNclient-engine.git - ref: main - path: windows/ + rxdart: ^0.27.7 dev_dependencies: flutter_test: diff --git a/run_android_device.sh b/run_android_device.sh new file mode 100755 index 00000000..1e9cc692 --- /dev/null +++ b/run_android_device.sh @@ -0,0 +1,4 @@ +flutter devices +#./pubs.sh +#flutter run -d android-arm64 +cd example && flutter run -d 2fa17dc4 \ No newline at end of file diff --git a/run_android_emulator.sh b/run_android_emulator.sh new file mode 100755 index 00000000..9546c9c4 --- /dev/null +++ b/run_android_emulator.sh @@ -0,0 +1,3 @@ +flutter emulators +#./pubs.sh +cd example && flutter run -d emulator-5554 \ No newline at end of file From adfcadf2a8d2de02702213348e240e93a266e374 Mon Sep 17 00:00:00 2001 From: Anton Dodonov Date: Mon, 7 Jul 2025 11:25:17 +0700 Subject: [PATCH 76/77] fix:subscriptionlist --- CHANGELOG.md | 28 ++ example/lib/main.dart | 625 +++++++++++++++++++++++-------- lib/vpnclient_engine/engine.dart | 88 +++-- 3 files changed, 568 insertions(+), 173 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41cc7d81..e875ba1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,31 @@ ## 0.0.1 * TODO: Describe initial release. + +## 0.0.2 + +### Added +* Complete UI/UX redesign of the example app with modern Material 3 design +* Tabbed navigation with Home, Servers, and Settings pages +* Visual connection status indicator with color-coded states +* Dedicated servers page showing all available servers from subscriptions +* Server cards with connection status, latency, and location information +* Real-time ping functionality for server latency testing +* Session statistics display with formatted data usage +* Subscription management with loading states and error handling +* Settings page with auto-connect and kill switch toggles +* Loading indicators and user feedback via snackbars +* Dark theme support +* Responsive design optimized for mobile devices + +### Improved +* Better information hierarchy and user flow +* Clear separation of connection controls and server management +* Enhanced visual feedback for connection states +* Improved error handling and user notifications +* More intuitive navigation between different app sections + +### Fixed +* getServerList method now returns actual servers from loaded subscriptions instead of hardcoded test data +* Server address extraction from subscription URLs with location detection +* Proper server count display matching loaded subscription data diff --git a/example/lib/main.dart b/example/lib/main.dart index a21300c1..ed138fcb 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -13,7 +13,16 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( title: 'VPN Client Engine Demo', - theme: ThemeData(primarySwatch: Colors.blue), + theme: ThemeData( + primarySwatch: Colors.blue, + useMaterial3: true, + brightness: Brightness.light, + ), + darkTheme: ThemeData( + primarySwatch: Colors.blue, + useMaterial3: true, + brightness: Brightness.dark, + ), home: const VPNClientDemo(), ); } @@ -29,25 +38,27 @@ class VPNClientDemo extends StatefulWidget { class VPNClientDemoState extends State { ConnectionStatus _connectionStatus = ConnectionStatus.disconnected; String _currentServer = 'Not Connected'; - String _pingResult = 'Not Pinging'; - List _routingRules = []; List _servers = []; + List _loadedSubscriptions = []; SessionStatistics _sessionStatistics = SessionStatistics( dataInBytes: 0, dataOutBytes: 0, ); + bool _isLoading = false; - final TextEditingController _subscriptionUrlController = - TextEditingController(); - final List _loadedSubscriptions = []; + final TextEditingController _subscriptionUrlController = TextEditingController(); + final PageController _pageController = PageController(); @override void initState() { super.initState(); + _initializeEngine(); + } + + void _initializeEngine() { VPNclientEngine.initialize(); VPNclientEngine.onConnectionStatusChanged.listen(_updateConnectionStatus); VPNclientEngine.onPingResult.listen(_updatePingResult); - VPNclientEngine.onRoutingRulesApplied.listen(_updateRoutingRules); VPNclientEngine.onDataUsageUpdated.listen(_updateSessionStatistics); } @@ -58,192 +69,504 @@ class VPNClientDemoState extends State { } void _updatePingResult(PingResult result) { + // Update server latency in the list setState(() { - _pingResult = - 'Ping: sub=${result.subscriptionIndex}, server=${result.serverIndex}, latency=${result.latencyInMs} ms'; + if (result.serverIndex < _servers.length) { + // Create a new Server instance with updated latency + _servers[result.serverIndex] = Server( + address: _servers[result.serverIndex].address, + latency: result.latencyInMs, + location: _servers[result.serverIndex].location, + isPreferred: _servers[result.serverIndex].isPreferred, + ); + } }); } - void _updateRoutingRules(List rules) { + void _updateSessionStatistics(SessionStatistics stats) { setState(() { - _routingRules = rules; + _sessionStatistics = stats; }); } - void _updateSessionStatistics(SessionStatistics stats) { + Future _loadSubscription() async { + if (_subscriptionUrlController.text.isEmpty) { + _showSnackBar('Please enter a subscription URL'); + return; + } + setState(() { - _sessionStatistics = stats; + _isLoading = true; }); + + try { + await VPNclientEngine.loadSubscriptions( + subscriptionLinks: [_subscriptionUrlController.text], + ); + + setState(() { + _loadedSubscriptions.add(_subscriptionUrlController.text); + _subscriptionUrlController.clear(); + }); + + await _refreshServers(); + _showSnackBar('Subscription loaded successfully'); + } catch (e) { + _showSnackBar('Failed to load subscription: $e'); + } finally { + setState(() { + _isLoading = false; + }); + } } - void _connectToServer() async { - await VPNclientEngine.connect(subscriptionIndex: 0, serverIndex: 0); + Future _refreshServers() async { setState(() { - _currentServer = 'Connecting...'; + _servers = VPNclientEngine.getServerList(); }); } - void _disconnectFromServer() async { - await VPNclientEngine.disconnect(); + Future _connectToServer(int serverIndex) async { + if (_loadedSubscriptions.isEmpty) { + _showSnackBar('Please load a subscription first'); + return; + } + setState(() { - _currentServer = 'Not Connected'; + _isLoading = true; }); + + try { + await VPNclientEngine.connect(subscriptionIndex: 0, serverIndex: serverIndex); + setState(() { + _currentServer = _servers[serverIndex].address; + }); + _showSnackBar('Connecting to ${_servers[serverIndex].address}'); + } catch (e) { + _showSnackBar('Failed to connect: $e'); + } finally { + setState(() { + _isLoading = false; + }); + } } - void _pingServer() { - VPNclientEngine.pingServer(subscriptionIndex: 0, index: 0); + Future _disconnect() async { setState(() { - _pingResult = 'Pinging...'; + _isLoading = true; }); - } - void _loadServers() { - _servers = VPNclientEngine.getServerList(); - setState(() {}); + try { + await VPNclientEngine.disconnect(); + setState(() { + _currentServer = 'Not Connected'; + }); + _showSnackBar('Disconnected'); + } catch (e) { + _showSnackBar('Failed to disconnect: $e'); + } finally { + setState(() { + _isLoading = false; + }); + } } - void _applyRoutingRules() { - List rules = [ - RoutingRule(appName: "YouTube", action: "proxy"), - RoutingRule(appName: "google.com", action: "direct"), - RoutingRule(domain: "ads.com", action: "block"), - ]; - VPNclientEngine.setRoutingRules(rules: rules); + void _pingServer(int serverIndex) { + VPNclientEngine.pingServer(subscriptionIndex: 0, index: serverIndex); } - void _loadSubscription() async { - if (_subscriptionUrlController.text.isEmpty) return; - - setState(() { - _loadedSubscriptions.add(_subscriptionUrlController.text); - }); - await VPNclientEngine.loadSubscriptions( - subscriptionLinks: [_subscriptionUrlController.text], + void _showSnackBar(String message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(message)), ); } @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('VPN Client Engine Demo')), - body: SingleChildScrollView( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Connection Status: $_connectionStatus'), - Text('Current Server: $_currentServer'), - Text(_pingResult), - ElevatedButton( - onPressed: _connectToServer, - child: const Text('Connect'), - ), - ElevatedButton( - onPressed: _disconnectFromServer, - child: const Text('Disconnect'), - ), - const SizedBox(height: 20), - Text('Session Statistics'), - Text('Session Duration: ${_sessionStatistics.sessionDuration}'), - Text('Data In: ${_sessionStatistics.dataInBytes} bytes'), - Text('Data Out: ${_sessionStatistics.dataOutBytes} bytes'), - const SizedBox(height: 20), - ElevatedButton( - onPressed: _pingServer, - child: const Text('Ping Server'), - ), - const SizedBox(height: 20), - ElevatedButton( - onPressed: _loadServers, - child: const Text('Load Servers'), - ), - const SizedBox(height: 10), - Text('Loaded Servers:'), - ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: _servers.length, - itemBuilder: (context, index) { - return ListTile( - title: Text(_servers[index].address), - subtitle: Text( - 'Latency: ${_servers[index].latency ?? 'N/A'}, Location: ${_servers[index].location ?? 'N/A'}', + appBar: AppBar( + title: const Text('VPN Client Engine'), + elevation: 0, + backgroundColor: Colors.transparent, + ), + body: PageView( + controller: _pageController, + children: [ + _buildHomePage(), + _buildServersPage(), + _buildSettingsPage(), + ], + ), + bottomNavigationBar: BottomNavigationBar( + currentIndex: 0, + onTap: (index) => _pageController.animateToPage( + index, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ), + items: const [ + BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'), + BottomNavigationBarItem(icon: Icon(Icons.list), label: 'Servers'), + BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'Settings'), + ], + ), + ); + } + + Widget _buildHomePage() { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + // Connection Status Card + Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + Row( + children: [ + Container( + width: 12, + height: 12, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _getStatusColor(), + ), + ), + const SizedBox(width: 8), + Text( + _getStatusText(), + style: Theme.of(context).textTheme.titleMedium, + ), + ], ), - ); - }, - ), - const SizedBox(height: 20), - ElevatedButton( - onPressed: _applyRoutingRules, - child: const Text('Apply Routing Rules'), - ), - const SizedBox(height: 10), - Text('Routing Rules Applied:'), - ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: _routingRules.length, - itemBuilder: (context, index) { - final rule = _routingRules[index]; - return ListTile( - title: Text('Rule ${index + 1}'), - subtitle: Text( - 'App Name: ${rule.appName ?? 'N/A'}, Domain: ${rule.domain ?? 'N/A'}, Action: ${rule.action}', + const SizedBox(height: 8), + Text( + _currentServer, + style: Theme.of(context).textTheme.bodyMedium, ), - ); - }, - ), - const SizedBox(height: 20), - Text('Load Subscription'), - TextField( - controller: _subscriptionUrlController, - decoration: const InputDecoration( - hintText: 'Enter subscription URL', + ], ), ), - const SizedBox(height: 10), - ElevatedButton( - onPressed: _loadSubscription, - child: const Text('Load Subscription'), - ), - const SizedBox(height: 10), - Text('Loaded Subscriptions:'), - ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: _loadedSubscriptions.length, - itemBuilder: (context, index) { - return ListTile(title: Text(_loadedSubscriptions[index])); - }, + ), + const SizedBox(height: 16), + + // Connection Controls + Row( + children: [ + Expanded( + child: ElevatedButton.icon( + onPressed: _connectionStatus == ConnectionStatus.connected + ? null + : _isLoading + ? null + : () => _connectToServer(0), + icon: const Icon(Icons.power_settings_new), + label: const Text('Connect'), + ), + ), + const SizedBox(width: 16), + Expanded( + child: OutlinedButton.icon( + onPressed: _connectionStatus == ConnectionStatus.connected + ? _disconnect + : null, + icon: const Icon(Icons.stop), + label: const Text('Disconnect'), + ), + ), + ], + ), + const SizedBox(height: 24), + + // Statistics Card + Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Session Statistics', + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 8), + _buildStatRow('Duration', _formatDuration(_sessionStatistics.sessionDuration)), + _buildStatRow('Data In', _formatBytes(_sessionStatistics.dataInBytes)), + _buildStatRow('Data Out', _formatBytes(_sessionStatistics.dataOutBytes)), + ], + ), ), - const SizedBox(height: 20), - ElevatedButton( - onPressed: () { - VPNclientEngine.setAutoConnect(enable: true); - }, - child: const Text('Enable auto connect'), + ), + const SizedBox(height: 24), + + // Subscription Management + Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Subscription Management', + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 16), + TextField( + controller: _subscriptionUrlController, + decoration: const InputDecoration( + hintText: 'Enter subscription URL', + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 8), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: _isLoading ? null : _loadSubscription, + child: _isLoading + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Text('Load Subscription'), + ), + ), + ], + ), ), - ElevatedButton( - onPressed: () { - VPNclientEngine.setAutoConnect(enable: false); - }, - child: const Text('Disable auto connect'), + ), + ], + ), + ); + } + + Widget _buildServersPage() { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Available Servers', + style: Theme.of(context).textTheme.headlineSmall, + ), + TextButton.icon( + onPressed: _refreshServers, + icon: const Icon(Icons.refresh), + label: const Text('Refresh'), + ), + ], + ), + const SizedBox(height: 16), + if (_servers.isEmpty) + Expanded( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.cloud_off, + size: 64, + color: Colors.grey[400], + ), + const SizedBox(height: 16), + Text( + 'No servers available', + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 8), + Text( + 'Load a subscription to see available servers', + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + ), + ) + else + Expanded( + child: ListView.builder( + itemCount: _servers.length, + itemBuilder: (context, index) { + final server = _servers[index]; + final isConnected = _currentServer == server.address; + + return Card( + margin: const EdgeInsets.only(bottom: 8), + child: ListTile( + leading: Icon( + isConnected ? Icons.check_circle : Icons.cloud, + color: isConnected ? Colors.green : null, + ), + title: Text(server.address), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (server.location != null) + Text('Location: ${server.location}'), + if (server.latency != null) + Text('Latency: ${server.latency}ms'), + ], + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: () => _pingServer(index), + icon: const Icon(Icons.speed), + tooltip: 'Ping Server', + ), + IconButton( + onPressed: isConnected + ? null + : () => _connectToServer(index), + icon: Icon( + isConnected ? Icons.check : Icons.power_settings_new, + ), + tooltip: isConnected ? 'Connected' : 'Connect', + ), + ], + ), + ), + ); + }, + ), ), - const SizedBox(height: 20), - ElevatedButton( - onPressed: () { - VPNclientEngine.setKillSwitch(enable: true); - }, - child: const Text('Enable kill switch'), + ], + ), + ); + } + + Widget _buildSettingsPage() { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Settings', + style: Theme.of(context).textTheme.headlineSmall, + ), + const SizedBox(height: 16), + Card( + child: Column( + children: [ + ListTile( + leading: const Icon(Icons.auto_awesome), + title: const Text('Auto Connect'), + subtitle: const Text('Automatically connect on app launch'), + trailing: Switch( + value: false, // TODO: Implement state management + onChanged: (value) { + VPNclientEngine.setAutoConnect(enable: value); + }, + ), + ), + ListTile( + leading: const Icon(Icons.security), + title: const Text('Kill Switch'), + subtitle: const Text('Block internet when VPN disconnects'), + trailing: Switch( + value: false, // TODO: Implement state management + onChanged: (value) { + VPNclientEngine.setKillSwitch(enable: value); + }, + ), + ), + ], ), - ElevatedButton( - onPressed: () { - VPNclientEngine.setKillSwitch(enable: false); - }, - child: const Text('Disable kill switch'), + ), + const SizedBox(height: 16), + Card( + child: Column( + children: [ + ListTile( + leading: const Icon(Icons.route), + title: const Text('Routing Rules'), + subtitle: const Text('Configure app and domain routing'), + onTap: () => _showRoutingRulesDialog(), + ), + ], ), - ], - ), + ), + ], + ), + ); + } + + Widget _buildStatRow(String label, String value) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(label), + Text(value, style: const TextStyle(fontWeight: FontWeight.bold)), + ], + ), + ); + } + + Color _getStatusColor() { + switch (_connectionStatus) { + case ConnectionStatus.connected: + return Colors.green; + case ConnectionStatus.connecting: + return Colors.orange; + case ConnectionStatus.error: + return Colors.red; + case ConnectionStatus.disconnected: + default: + return Colors.grey; + } + } + + String _getStatusText() { + switch (_connectionStatus) { + case ConnectionStatus.connected: + return 'Connected'; + case ConnectionStatus.connecting: + return 'Connecting...'; + case ConnectionStatus.error: + return 'Error'; + case ConnectionStatus.disconnected: + default: + return 'Disconnected'; + } + } + + String _formatDuration(Duration? duration) { + if (duration == null) return '0:00'; + final minutes = duration.inMinutes; + final seconds = duration.inSeconds % 60; + return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; + } + + String _formatBytes(int bytes) { + if (bytes < 1024) return '$bytes B'; + if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB'; + if (bytes < 1024 * 1024 * 1024) return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB'; + return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB'; + } + + void _showRoutingRulesDialog() { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Routing Rules'), + content: const Text('Routing rules configuration will be implemented here.'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('OK'), + ), + ], ), ); } diff --git a/lib/vpnclient_engine/engine.dart b/lib/vpnclient_engine/engine.dart index 72aff878..60f99319 100644 --- a/lib/vpnclient_engine/engine.dart +++ b/lib/vpnclient_engine/engine.dart @@ -241,28 +241,72 @@ class VPNclientEngine { } static List getServerList() { - //TODO: - //Fetches the list of available VPN servers. - return [ - Server( - address: 'server1.com', - latency: 50, - location: 'USA', - isPreferred: true, - ), - Server( - address: 'server2.com', - latency: 100, - location: 'UK', - isPreferred: false, - ), - Server( - address: 'server3.com', - latency: 75, - location: 'Canada', - isPreferred: false, - ), - ]; + List servers = []; + + _log('getServerList: processing ${_subscriptionServers.length} subscriptions'); + + // Convert subscription servers to Server objects + for (int subIndex = 0; subIndex < _subscriptionServers.length; subIndex++) { + _log('getServerList: subscription $subIndex has ${_subscriptionServers[subIndex].length} servers'); + + for (int serverIndex = 0; serverIndex < _subscriptionServers[subIndex].length; serverIndex++) { + final serverUrl = _subscriptionServers[subIndex][serverIndex]; + _log('getServerList: processing server $serverIndex: $serverUrl'); + + // Extract server information from URL + String address = serverUrl; + String? location; + + // Try to extract location from URL if possible + if (serverUrl.contains('@')) { + final parts = serverUrl.split('@'); + if (parts.length > 1) { + final hostPart = parts[1].split(':')[0]; + address = hostPart; + _log('getServerList: extracted host: $address'); + + // Simple location detection based on common patterns + if (hostPart.contains('.us') || hostPart.contains('usa')) { + location = 'USA'; + } else if (hostPart.contains('.uk') || hostPart.contains('gb')) { + location = 'UK'; + } else if (hostPart.contains('.ca')) { + location = 'Canada'; + } else if (hostPart.contains('.de')) { + location = 'Germany'; + } else if (hostPart.contains('.nl')) { + location = 'Netherlands'; + } else if (hostPart.contains('.jp')) { + location = 'Japan'; + } else if (hostPart.contains('.sg')) { + location = 'Singapore'; + } else { + location = 'Unknown'; + } + } + } else { + // If no @ symbol, try to extract domain from the URL + if (serverUrl.contains('://')) { + final uri = Uri.parse(serverUrl); + address = uri.host; + _log('getServerList: extracted host from URI: $address'); + } + } + + final server = Server( + address: address, + latency: null, // Will be updated by ping + location: location, + isPreferred: false, + ); + + servers.add(server); + _log('getServerList: added server: ${server.address} (${server.location})'); + } + } + + _log('getServerList: returning ${servers.length} servers from subscriptions'); + return servers; } static Future loadSubscriptions({ From a4c1b0ad961eada225c1f66345f6af1501270aac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 08:53:32 +0000 Subject: [PATCH 77/77] Bump brace-expansion from 1.1.11 to 1.1.12 in /VPNclient_react_native Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.11 to 1.1.12. - [Release notes](https://github.com/juliangruber/brace-expansion/releases) - [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12) --- updated-dependencies: - dependency-name: brace-expansion dependency-version: 1.1.12 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- VPNclient_react_native/package-lock.json | 352 ++++++----------------- 1 file changed, 92 insertions(+), 260 deletions(-) diff --git a/VPNclient_react_native/package-lock.json b/VPNclient_react_native/package-lock.json index 7c3c7764..9d0cbe8a 100644 --- a/VPNclient_react_native/package-lock.json +++ b/VPNclient_react_native/package-lock.json @@ -9,10 +9,6 @@ "version": "0.0.1", "dependencies": { "@react-native-community/netinfo": "^9.4.1", - "@react-navigation/native": "^6.1.9", - "@react-navigation/stack": "^6.3.20", - "react": "18.2.0", - "react-native": "0.73.1", "react-native-safe-area-context": "^4.8.2", "react-native-screens": "^3.29.0", "react-native-svg-transformer": "^1.5.0" @@ -37,6 +33,10 @@ }, "engines": { "node": ">=18" + }, + "peerDependencies": { + "react": ">=18.2.0", + "react-native": ">=0.73.1" } }, "node_modules/@ampproject/remapping": { @@ -2111,19 +2111,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@egjs/hammerjs": { - "version": "2.0.17", - "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", - "integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/hammerjs": "^2.0.36" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz", @@ -2334,6 +2321,7 @@ "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -2484,6 +2472,7 @@ "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", "license": "MIT", + "peer": true, "dependencies": { "@jest/types": "^29.6.3" }, @@ -3166,6 +3155,7 @@ "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.73.1.tgz", "integrity": "sha512-2FgAbU7uKM5SbbW9QptPPZx8N9Ke2L7bsHb+EhAanZjFZunA9PaYtyjUQ1s7HD+zDVqOQIvjkpXSv7Kejd2tqg==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -3266,6 +3256,7 @@ "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.73.11.tgz", "integrity": "sha512-s0bprwljKS1Al8wOKathDDmRyF+70CcNE2G/aqZ7+L0NoOE0Uxxx/5P2BxlM2Mfht7O33B4SeMNiPdE/FqIubQ==", "license": "MIT", + "peer": true, "dependencies": { "@react-native-community/cli-server-api": "12.3.0", "@react-native-community/cli-tools": "12.3.0", @@ -3288,6 +3279,7 @@ "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.0.tgz", "integrity": "sha512-w3b0iwjQlk47GhZWHaeTG8kKH09NCMUJO729xSdMBXE8rlbm4kHpKbxQY9qKb6NlfWSJN4noGY+FkNZS2rRwnQ==", "license": "MIT", + "peer": true, "dependencies": { "serve-static": "^1.13.1" } @@ -3297,6 +3289,7 @@ "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-12.3.0.tgz", "integrity": "sha512-Rode8NrdyByC+lBKHHn+/W8Zu0c+DajJvLmOWbe2WY/ECvnwcd9MHHbu92hlT2EQaJ9LbLhGrSbQE3cQy9EOCw==", "license": "MIT", + "peer": true, "dependencies": { "@react-native-community/cli-debugger-ui": "12.3.0", "@react-native-community/cli-tools": "12.3.0", @@ -3314,6 +3307,7 @@ "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-12.3.0.tgz", "integrity": "sha512-2GafnCr8D88VdClwnm9KZfkEb+lzVoFdr/7ybqhdeYM0Vnt/tr2N+fM1EQzwI1DpzXiBzTYemw8GjRq+Utcz2Q==", "license": "MIT", + "peer": true, "dependencies": { "appdirsjs": "^1.2.4", "chalk": "^4.1.2", @@ -3332,6 +3326,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "license": "MIT", + "peer": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -3348,6 +3343,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "license": "MIT", + "peer": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -3363,6 +3359,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "license": "MIT", + "peer": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -3378,6 +3375,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "license": "ISC", + "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -3390,6 +3388,7 @@ "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.73.3.tgz", "integrity": "sha512-RgEKnWuoo54dh7gQhV7kvzKhXZEhpF9LlMdZolyhGxHsBqZ2gXdibfDlfcARFFifPIiaZ3lXuOVVa4ei+uPgTw==", "license": "BSD-3-Clause", + "peer": true, "engines": { "node": ">=18" } @@ -3399,6 +3398,7 @@ "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.73.8.tgz", "integrity": "sha512-oph4NamCIxkMfUL/fYtSsE+JbGOnrlawfQ0kKtDQ5xbOjPKotKoXqrs1eGwozNKv7FfQ393stk1by9a6DyASSg==", "license": "MIT", + "peer": true, "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.73.3", @@ -3421,6 +3421,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", + "peer": true, "dependencies": { "ms": "2.0.0" } @@ -3430,6 +3431,7 @@ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "license": "MIT", + "peer": true, "dependencies": { "is-docker": "^2.0.0" }, @@ -3441,13 +3443,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@react-native/dev-middleware/node_modules/open": { "version": "7.4.2", "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", "license": "MIT", + "peer": true, "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" @@ -3464,6 +3468,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", "license": "MIT", + "peer": true, "dependencies": { "async-limiter": "~1.0.0" } @@ -3512,6 +3517,7 @@ "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.73.5.tgz", "integrity": "sha512-Orrn8J/kqzEuXudl96XcZk84ZcdIpn1ojjwGSuaSQSXNcCYbOXyt0RwtW5kjCqjgSzGnOMsJNZc5FDXHVq/WzA==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -3667,7 +3673,8 @@ "version": "0.73.2", "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.73.2.tgz", "integrity": "sha512-bRBcb2T+I88aG74LMVHaKms2p/T8aQd8+BZ7LuuzXlRfog1bMWWn/C5i0HVuvW4RPtXQYgIlGiXVDy9Ir1So/w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@react-native/typescript-config": { "version": "0.73.1", @@ -3681,6 +3688,7 @@ "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.73.4.tgz", "integrity": "sha512-HpmLg1FrEiDtrtAbXiwCgXFYyloK/dOIPIuWW3fsqukwJEWAiTzm1nXGJ7xPU5XTHiWZ4sKup5Ebaj8z7iyWog==", "license": "MIT", + "peer": true, "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" @@ -3692,79 +3700,6 @@ "react-native": "*" } }, - "node_modules/@react-navigation/core": { - "version": "6.4.17", - "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-6.4.17.tgz", - "integrity": "sha512-Nd76EpomzChWAosGqWOYE3ItayhDzIEzzZsT7PfGcRFDgW5miHV2t4MZcq9YIK4tzxZjVVpYbIynOOQQd1e0Cg==", - "license": "MIT", - "dependencies": { - "@react-navigation/routers": "^6.1.9", - "escape-string-regexp": "^4.0.0", - "nanoid": "^3.1.23", - "query-string": "^7.1.3", - "react-is": "^16.13.0", - "use-latest-callback": "^0.2.1" - }, - "peerDependencies": { - "react": "*" - } - }, - "node_modules/@react-navigation/elements": { - "version": "1.3.31", - "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-1.3.31.tgz", - "integrity": "sha512-bUzP4Awlljx5RKEExw8WYtif8EuQni2glDaieYROKTnaxsu9kEIA515sXQgUDZU4Ob12VoL7+z70uO3qrlfXcQ==", - "license": "MIT", - "peerDependencies": { - "@react-navigation/native": "^6.0.0", - "react": "*", - "react-native": "*", - "react-native-safe-area-context": ">= 3.0.0" - } - }, - "node_modules/@react-navigation/native": { - "version": "6.1.18", - "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-6.1.18.tgz", - "integrity": "sha512-mIT9MiL/vMm4eirLcmw2h6h/Nm5FICtnYSdohq4vTLA2FF/6PNhByM7s8ffqoVfE5L0uAa6Xda1B7oddolUiGg==", - "license": "MIT", - "dependencies": { - "@react-navigation/core": "^6.4.17", - "escape-string-regexp": "^4.0.0", - "fast-deep-equal": "^3.1.3", - "nanoid": "^3.1.23" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, - "node_modules/@react-navigation/routers": { - "version": "6.1.9", - "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-6.1.9.tgz", - "integrity": "sha512-lTM8gSFHSfkJvQkxacGM6VJtBt61ip2XO54aNfswD+KMw6eeZ4oehl7m0me3CR9hnDE4+60iAZR8sAhvCiI3NA==", - "license": "MIT", - "dependencies": { - "nanoid": "^3.1.23" - } - }, - "node_modules/@react-navigation/stack": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@react-navigation/stack/-/stack-6.4.1.tgz", - "integrity": "sha512-upMEHOKMtuMu4c9gmoPlO/JqI6mDlSqwXg1aXKOTQLXAF8H5koOLRfrmi7AkdiE9A7lDXWUAZoGuD9O88cYvDQ==", - "license": "MIT", - "dependencies": { - "@react-navigation/elements": "^1.3.31", - "color": "^4.2.3", - "warn-once": "^0.1.0" - }, - "peerDependencies": { - "@react-navigation/native": "^6.0.0", - "react": "*", - "react-native": "*", - "react-native-gesture-handler": ">= 1.0.0", - "react-native-safe-area-context": ">= 3.0.0", - "react-native-screens": ">= 3.0.0" - } - }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -4294,13 +4229,6 @@ "@types/node": "*" } }, - "node_modules/@types/hammerjs": { - "version": "2.0.46", - "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz", - "integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==", - "license": "MIT", - "peer": true - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -4657,6 +4585,7 @@ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", "license": "MIT", + "peer": true, "dependencies": { "event-target-shim": "^5.0.0" }, @@ -4729,7 +4658,8 @@ "version": "1.4.10", "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/ansi-escapes": { "version": "4.3.2", @@ -4960,7 +4890,8 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/ast-types": { "version": "0.15.2", @@ -5003,7 +4934,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/available-typed-arrays": { "version": "1.0.7", @@ -5221,10 +5153,9 @@ "license": "ISC" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "license": "MIT", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5465,6 +5396,7 @@ "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", @@ -5483,6 +5415,7 @@ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "license": "MIT", + "peer": true, "dependencies": { "is-docker": "^2.0.0" }, @@ -5495,6 +5428,7 @@ "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-1.0.0.tgz", "integrity": "sha512-pgtgjNKZ7i5U++1g1PWv75umkHvhVTDOQIZ+sjeUX9483S7Y6MUvO0lrd7ShGlQlFHMN4SwKTCq/X8hWrbv2KA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", @@ -5509,6 +5443,7 @@ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "license": "MIT", + "peer": true, "dependencies": { "is-docker": "^2.0.0" }, @@ -5521,6 +5456,7 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "license": "MIT", + "peer": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -5641,19 +5577,6 @@ "dev": true, "license": "MIT" }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -5672,16 +5595,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "license": "MIT", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, "node_modules/colorette": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", @@ -6044,15 +5957,6 @@ "node": ">=0.10.0" } }, - "node_modules/decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, "node_modules/dedent": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", @@ -6152,6 +6056,7 @@ "resolved": "https://registry.npmjs.org/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-5.0.0.tgz", "integrity": "sha512-cIK8KYiiGVOFsKdPMmm1L3tA/Gl+JopXL6F5+C7x39MyPsQYnP57Im/D6bNUzcborD7fcMwiwZqcBdBXXZucYQ==", "license": "MIT", + "peer": true, "dependencies": { "@react-native/normalize-colors": "^0.73.0", "invariant": "^2.2.4", @@ -7139,6 +7044,7 @@ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -7202,6 +7108,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, "license": "MIT" }, "node_modules/fast-diff": { @@ -7317,15 +7224,6 @@ "node": ">=8" } }, - "node_modules/filter-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", - "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -7942,16 +7840,6 @@ "node": ">=8" } }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "react-is": "^16.7.0" - } - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -8134,7 +8022,8 @@ "version": "1.1.9", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/is-array-buffer": { "version": "3.0.5", @@ -8290,6 +8179,7 @@ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "license": "MIT", + "peer": true, "bin": { "is-docker": "cli.js" }, @@ -9697,7 +9587,8 @@ "version": "250231.0.0", "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-250231.0.0.tgz", "integrity": "sha512-rS46PvsjYmdmuz1OAWXY/1kCYG7pnf1TBqeTiOJr1iDz7s5DLxxC9n/ZMknLDxzYzNVfI7R95MH10emSSG1Wuw==", - "license": "BSD-2-Clause" + "license": "BSD-2-Clause", + "peer": true }, "node_modules/jsc-safe-url": { "version": "0.2.4", @@ -9887,6 +9778,7 @@ "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", "license": "Apache-2.0", + "peer": true, "dependencies": { "debug": "^2.6.9", "marky": "^1.2.2" @@ -9897,6 +9789,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", + "peer": true, "dependencies": { "ms": "2.0.0" } @@ -9905,7 +9798,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lines-and-columns": { "version": "1.2.4", @@ -10137,7 +10031,8 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz", "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/math-intrinsics": { "version": "1.1.0", @@ -10160,7 +10055,8 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/merge-stream": { "version": "2.0.0", @@ -10661,24 +10557,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -11379,6 +11257,7 @@ "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", "license": "MIT", + "peer": true, "dependencies": { "asap": "~2.0.6" } @@ -11434,24 +11313,6 @@ ], "license": "MIT" }, - "node_modules/query-string": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", - "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", - "license": "MIT", - "dependencies": { - "decode-uri-component": "^0.2.2", - "filter-obj": "^1.1.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/queue": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", @@ -11496,6 +11357,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -11508,6 +11370,7 @@ "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.28.5.tgz", "integrity": "sha512-cq/o30z9W2Wb4rzBefjv5fBalHU0rJGZCHAkf/RHSBWSSYwh8PlQTqqOJmgIIbBtpj27T6FIPXeomIjZtCNVqA==", "license": "MIT", + "peer": true, "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" @@ -11536,6 +11399,7 @@ "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.73.1.tgz", "integrity": "sha512-nLl9O2yKRh1nMXwsk4SUiD0ddd19RqlKgNU9AU8bTK/zD2xwnVOG56YK1/22SN67niWyoeG83vVg1eTk+S6ReA==", "license": "MIT", + "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.6.3", "@react-native-community/cli": "12.3.0", @@ -11585,22 +11449,6 @@ "react": "18.2.0" } }, - "node_modules/react-native-gesture-handler": { - "version": "2.25.0", - "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.25.0.tgz", - "integrity": "sha512-NPjJi6mislXxvjxQPU9IYwBjb1Uejp8GvAbE1Lhh+xMIMEvmgAvVIp5cz1P+xAbV6uYcRRArm278+tEInGOqWg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@egjs/hammerjs": "^2.0.17", - "hoist-non-react-statics": "^3.3.0", - "invariant": "^2.2.4" - }, - "peerDependencies": { - "react": "*", - "react-native": "*" - } - }, "node_modules/react-native-safe-area-context": { "version": "4.14.1", "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.14.1.tgz", @@ -11662,6 +11510,7 @@ "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-12.3.0.tgz", "integrity": "sha512-XeQohi2E+S2+MMSz97QcEZ/bWpi8sfKiQg35XuYeJkc32Til2g0b97jRpn0/+fV0BInHoG1CQYWwHA7opMsrHg==", "license": "MIT", + "peer": true, "dependencies": { "@react-native-community/cli-clean": "12.3.0", "@react-native-community/cli-config": "12.3.0", @@ -11694,6 +11543,7 @@ "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-12.3.0.tgz", "integrity": "sha512-iAgLCOWYRGh9ukr+eVQnhkV/OqN3V2EGd/in33Ggn/Mj4uO6+oUncXFwB+yjlyaUNz6FfjudhIz09yYGSF+9sg==", "license": "MIT", + "peer": true, "dependencies": { "@react-native-community/cli-tools": "12.3.0", "chalk": "^4.1.2", @@ -11705,6 +11555,7 @@ "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-12.3.0.tgz", "integrity": "sha512-BrTn5ndFD9uOxO8kxBQ32EpbtOvAsQExGPI7SokdI4Zlve70FziLtTq91LTlTUgMq1InVZn/jJb3VIDk6BTInQ==", "license": "MIT", + "peer": true, "dependencies": { "@react-native-community/cli-tools": "12.3.0", "chalk": "^4.1.2", @@ -11719,6 +11570,7 @@ "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-12.3.0.tgz", "integrity": "sha512-w3b0iwjQlk47GhZWHaeTG8kKH09NCMUJO729xSdMBXE8rlbm4kHpKbxQY9qKb6NlfWSJN4noGY+FkNZS2rRwnQ==", "license": "MIT", + "peer": true, "dependencies": { "serve-static": "^1.13.1" } @@ -11728,6 +11580,7 @@ "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-12.3.0.tgz", "integrity": "sha512-BPCwNNesoQMkKsxB08Ayy6URgGQ8Kndv6mMhIvJSNdST3J1+x3ehBHXzG9B9Vfi+DrTKRb8lmEl/b/7VkDlPkA==", "license": "MIT", + "peer": true, "dependencies": { "@react-native-community/cli-config": "12.3.0", "@react-native-community/cli-platform-android": "12.3.0", @@ -11753,6 +11606,7 @@ "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-12.3.0.tgz", "integrity": "sha512-G6FxpeZBO4AimKZwtWR3dpXRqTvsmEqlIkkxgwthdzn3LbVjDVIXKpVYU9PkR5cnT+KuAUxO0WwthrJ6Nmrrlg==", "license": "MIT", + "peer": true, "dependencies": { "@react-native-community/cli-platform-android": "12.3.0", "@react-native-community/cli-tools": "12.3.0", @@ -11766,6 +11620,7 @@ "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-12.3.0.tgz", "integrity": "sha512-VU1NZw63+GLU2TnyQ919bEMThpHQ/oMFju9MCfrd3pyPJz4Sn+vc3NfnTDUVA5Z5yfLijFOkHIHr4vo/C9bjnw==", "license": "MIT", + "peer": true, "dependencies": { "@react-native-community/cli-tools": "12.3.0", "chalk": "^4.1.2", @@ -11780,6 +11635,7 @@ "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-12.3.0.tgz", "integrity": "sha512-H95Sgt3wT7L8V75V0syFJDtv4YgqK5zbu69ko4yrXGv8dv2EBi6qZP0VMmkqXDamoPm9/U7tDTdbcf26ctnLfg==", "license": "MIT", + "peer": true, "dependencies": { "@react-native-community/cli-tools": "12.3.0", "chalk": "^4.1.2", @@ -11793,13 +11649,15 @@ "version": "12.3.0", "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-12.3.0.tgz", "integrity": "sha512-tYNHIYnNmxrBcsqbE2dAnLMzlKI3Cp1p1xUgTrNaOMsGPDN1epzNfa34n6Nps3iwKElSL7Js91CzYNqgTalucA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-native/node_modules/@react-native-community/cli-server-api": { "version": "12.3.0", "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-12.3.0.tgz", "integrity": "sha512-Rode8NrdyByC+lBKHHn+/W8Zu0c+DajJvLmOWbe2WY/ECvnwcd9MHHbu92hlT2EQaJ9LbLhGrSbQE3cQy9EOCw==", "license": "MIT", + "peer": true, "dependencies": { "@react-native-community/cli-debugger-ui": "12.3.0", "@react-native-community/cli-tools": "12.3.0", @@ -11817,6 +11675,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=8.3.0" }, @@ -11838,6 +11697,7 @@ "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-12.3.0.tgz", "integrity": "sha512-2GafnCr8D88VdClwnm9KZfkEb+lzVoFdr/7ybqhdeYM0Vnt/tr2N+fM1EQzwI1DpzXiBzTYemw8GjRq+Utcz2Q==", "license": "MIT", + "peer": true, "dependencies": { "appdirsjs": "^1.2.4", "chalk": "^4.1.2", @@ -11856,6 +11716,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "license": "MIT", + "peer": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -11872,6 +11733,7 @@ "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-12.3.0.tgz", "integrity": "sha512-MgOkmrXH4zsGxhte4YqKL7d+N8ZNEd3w1wo56MZlhu5WabwCJh87wYpU5T8vyfujFLYOFuFK5jjlcbs8F4/WDw==", "license": "MIT", + "peer": true, "dependencies": { "joi": "^17.2.1" } @@ -11881,6 +11743,7 @@ "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.73.3.tgz", "integrity": "sha512-sxslCAAb8kM06vGy9Jyh4TtvjhcP36k/rvj2QE2Jdhdm61KvfafCATSIsOfc0QvnduWFcpXUPvAVyYwuv7PYDg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/parser": "^7.20.0", "flow-parser": "^0.206.0", @@ -11902,6 +11765,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "license": "MIT", + "peer": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -11917,6 +11781,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "license": "MIT", + "peer": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -11931,13 +11796,15 @@ "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-native/node_modules/semver": { "version": "7.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "license": "ISC", + "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -11950,6 +11817,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", "license": "MIT", + "peer": true, "dependencies": { "async-limiter": "~1.0.0" } @@ -12026,7 +11894,8 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==", - "license": "BSD" + "license": "BSD", + "peer": true }, "node_modules/recast": { "version": "0.21.5", @@ -12394,6 +12263,7 @@ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.24.0-canary-efb381bbf-20230505.tgz", "integrity": "sha512-ABvovCDe/k9IluqSh4/ISoq8tIJnW8euVAWYt5j/bg6dRnqwQwiGO1F/V4AyK96NGF/FB04FhOUDuWj8IKfABA==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" } @@ -12700,21 +12570,6 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "license": "MIT" - }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -12826,15 +12681,6 @@ "node": ">=0.10.0" } }, - "node_modules/split-on-first": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", - "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -12873,6 +12719,7 @@ "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz", "integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==", "license": "MIT", + "peer": true, "dependencies": { "type-fest": "^0.7.1" }, @@ -12885,6 +12732,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", "license": "(MIT OR CC0-1.0)", + "peer": true, "engines": { "node": ">=8" } @@ -12898,15 +12746,6 @@ "node": ">= 0.6" } }, - "node_modules/strict-uri-encode": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", - "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -13255,6 +13094,7 @@ "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -13695,15 +13535,6 @@ "punycode": "^2.1.0" } }, - "node_modules/use-latest-callback": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.3.tgz", - "integrity": "sha512-7vI3fBuyRcP91pazVboc4qu+6ZqM8izPWX9k7cRnT8hbD5svslcknsh3S9BUhaK11OmgTV4oWZZVSeQAiV53SQ==", - "license": "MIT", - "peerDependencies": { - "react": ">=16.8" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -13783,7 +13614,8 @@ "version": "3.6.20", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/whatwg-url": { "version": "5.0.0",