diff --git a/android/app/build.gradle b/android/app/build.gradle
index d09ae4d9fc..16281d6b5a 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -28,6 +28,9 @@ repositories {
flatDir{
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
}
+ google()
+ mavenCentral()
+ maven { url 'https://jitpack.io' }
}
dependencies {
diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle
index bbfb44fad1..b65c2dbfda 100644
--- a/android/app/capacitor.build.gradle
+++ b/android/app/capacitor.build.gradle
@@ -9,7 +9,7 @@ android {
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
-
+ implementation project(':capacitor-plugin-usb-serial')
}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 4d7ca38041..2dcc2b1cc0 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -22,6 +22,14 @@
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/xml/device_filter.xml b/android/app/src/main/res/xml/device_filter.xml
new file mode 100644
index 0000000000..1dd1503c99
--- /dev/null
+++ b/android/app/src/main/res/xml/device_filter.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle
index 9a5fa872e7..a5560f042d 100644
--- a/android/capacitor.settings.gradle
+++ b/android/capacitor.settings.gradle
@@ -1,3 +1,6 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
+
+include ':capacitor-plugin-usb-serial'
+project(':capacitor-plugin-usb-serial').projectDir = new File('../node_modules/capacitor-plugin-usb-serial/android')
diff --git a/android/gradlew b/android/gradlew
old mode 100644
new mode 100755
diff --git a/android/settings.gradle b/android/settings.gradle
index 3b4431d772..52d2925a6a 100644
--- a/android/settings.gradle
+++ b/android/settings.gradle
@@ -2,4 +2,12 @@ include ':app'
include ':capacitor-cordova-android-plugins'
project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
+dependencyResolutionManagement {
+ repositories {
+ google()
+ mavenCentral()
+ maven { url 'https://jitpack.io' }
+ }
+}
+
apply from: 'capacitor.settings.gradle'
\ No newline at end of file
diff --git a/package.json b/package.json
index 995de18faa..1d9da16b3e 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,8 @@
"android:release": "vite build && node capacitor.config.generator.mjs && npx cap build android --release",
"format": "prettier --write {src,test}/**/*.{js,vue,css,less}",
"storybook": "start-storybook -p 6006",
- "prepare": "husky install"
+ "prepare": "husky install",
+ "postinstall": "patch-package"
},
"window": {
"icon": "images/bf_icon_128.png",
@@ -45,6 +46,7 @@
"@capacitor/core": "^7.0.1",
"@fortawesome/fontawesome-free": "^6.5.2",
"@vitejs/plugin-vue": "^6.0.1",
+ "capacitor-plugin-usb-serial": "^0.0.6",
"crypto-es": "^2.1.0",
"d3": "^7.9.0",
"djv": "^2.1.4",
@@ -115,7 +117,9 @@
"lint-staged": "^15.4.3",
"nw-builder": "^3.8.6",
"os": "^0.1.2",
+ "patch-package": "^8.0.1",
"postcss": "^8.5.3",
+ "postinstall-postinstall": "^2.1.0",
"prettier": "^3.5.2",
"rollup": "^4.34.9",
"rollup-plugin-copy": "^3.5.0",
diff --git a/patches/capacitor-plugin-usb-serial+0.0.6.patch b/patches/capacitor-plugin-usb-serial+0.0.6.patch
new file mode 100644
index 0000000000..49bf61e005
--- /dev/null
+++ b/patches/capacitor-plugin-usb-serial+0.0.6.patch
@@ -0,0 +1,44 @@
+diff --git a/node_modules/capacitor-plugin-usb-serial/android/src/main/java/com/viewtrak/plugins/usbserial/UsbSerial.java b/node_modules/capacitor-plugin-usb-serial/android/src/main/java/com/viewtrak/plugins/usbserial/UsbSerial.java
+index a366b4a..c80e172 100644
+--- a/node_modules/capacitor-plugin-usb-serial/android/src/main/java/com/viewtrak/plugins/usbserial/UsbSerial.java
++++ b/node_modules/capacitor-plugin-usb-serial/android/src/main/java/com/viewtrak/plugins/usbserial/UsbSerial.java
+@@ -233,7 +233,8 @@ public class UsbSerial implements SerialInputOutputManager.Listener {
+ throw new Error("can't send empty string to device", new Throwable("EMPTY_STRING"));
+ }
+ try {
+- byte[] data = (str + "\r\n").getBytes();
++ // Convert hex string to bytes (for binary protocols like MSP)
++ byte[] data = HexDump.hexStringToByteArray(str);
+ usbSerialPort.write(data, WRITE_WAIT_MILLIS);
+ } catch (Exception e) {
+ closeSerial();
+@@ -260,25 +261,10 @@ public class UsbSerial implements SerialInputOutputManager.Listener {
+
+ private void updateReceivedData(byte[] data) {
+ try {
+- messageNMEA += new String(data);
+-
+- while (messageNMEA.indexOf(0x0a) != -1) {
+-
+- int eol = messageNMEA.indexOf(0x0a);
+- if (-1 != eol) {
+- String sentence = messageNMEA.substring(0, eol + 1);
+- messageNMEA = messageNMEA.substring(eol + 1);
+-
+- // Boolean allowed = throttle.tryAcquire();
+- // if (!allowed) {
+- // return;
+- // }
+-
+- callback.receivedData(sentence);
+- } else if (messageNMEA.length() > 128) {
+- throw new Exception("invalid NMEA string");
+- }
+- }
++ // FIXED: Send all data immediately as hex string (for binary protocols like MSP)
++ // Original code waited for newline characters (NMEA protocol), which never comes with binary data
++ String hexData = HexDump.toHexString(data);
++ callback.receivedData(hexData);
+ } catch (Exception exception) {
+ updateReadDataError(exception);
+ }
diff --git a/src/js/gui.js b/src/js/gui.js
index b48f97089d..ae5124a3a5 100644
--- a/src/js/gui.js
+++ b/src/js/gui.js
@@ -3,7 +3,7 @@ import MSP from "./msp";
import Switchery from "switchery-latest";
import tippy from "tippy.js";
import $ from "jquery";
-import { getOS } from "./utils/checkBrowserCompatibility";
+import { getOS } from "./utils/checkCompatibility";
const TABS = {};
diff --git a/src/js/port_handler.js b/src/js/port_handler.js
index 3665bce13b..614448a9b9 100644
--- a/src/js/port_handler.js
+++ b/src/js/port_handler.js
@@ -4,11 +4,11 @@ import { serial } from "./serial.js";
import WEBUSBDFU from "./protocols/webusbdfu";
import { reactive } from "vue";
import {
- checkBrowserCompatibility,
- checkWebBluetoothSupport,
- checkWebSerialSupport,
- checkWebUSBSupport,
-} from "./utils/checkBrowserCompatibility.js";
+ checkCompatibility,
+ checkBluetoothSupport,
+ checkSerialSupport,
+ checkUsbSupport,
+} from "./utils/checkCompatibility.js";
const DEFAULT_PORT = "noselection";
const DEFAULT_BAUDS = 115200;
@@ -34,11 +34,11 @@ const PortHandler = new (function () {
this.dfuAvailable = false;
this.portAvailable = false;
- checkBrowserCompatibility();
+ checkCompatibility();
- this.showBluetoothOption = checkWebBluetoothSupport();
- this.showSerialOption = checkWebSerialSupport();
- this.showUsbOption = checkWebUSBSupport();
+ this.showBluetoothOption = checkBluetoothSupport();
+ this.showSerialOption = checkSerialSupport();
+ this.showUsbOption = checkUsbSupport();
console.log(`${this.logHead} Bluetooth available: ${this.showBluetoothOption}`);
console.log(`${this.logHead} Serial available: ${this.showSerialOption}`);
@@ -50,8 +50,8 @@ const PortHandler = new (function () {
})();
PortHandler.initialize = function () {
- EventBus.$on("ports-input:request-permission-bluetooth", () => this.requestDevicePermission("webbluetooth"));
- EventBus.$on("ports-input:request-permission-serial", () => this.requestDevicePermission("webserial"));
+ EventBus.$on("ports-input:request-permission-bluetooth", () => this.requestDevicePermission("bluetooth"));
+ EventBus.$on("ports-input:request-permission-serial", () => this.requestDevicePermission("serial"));
EventBus.$on("ports-input:request-permission-usb", () => this.requestDevicePermission("usb"));
EventBus.$on("ports-input:change", this.onChangeSelectedPort.bind(this));
@@ -60,9 +60,9 @@ PortHandler.initialize = function () {
const detail = event.detail;
if (detail?.path?.startsWith("bluetooth")) {
- this.handleDeviceAdded(detail, "webbluetooth");
+ this.handleDeviceAdded(detail, "bluetooth");
} else {
- this.handleDeviceAdded(detail, "webserial");
+ this.handleDeviceAdded(detail, "serial");
}
});
@@ -81,8 +81,8 @@ PortHandler.initialize = function () {
PortHandler.refreshAllDeviceLists = async function () {
// Update all device lists in parallel
return Promise.all([
- this.updateDeviceList("webserial"),
- this.updateDeviceList("webbluetooth"),
+ this.updateDeviceList("serial"),
+ this.updateDeviceList("bluetooth"),
this.updateDeviceList("usb"),
]).then(() => {
this.selectActivePort();
@@ -112,7 +112,7 @@ PortHandler.removedSerialDevice = function (device) {
if (!devicePath) {
console.warn(`${this.logHead} Device removal event missing path information`, device);
// Still update ports, but don't try to use the undefined path
- this.updateDeviceList("webserial").then(() => {
+ this.updateDeviceList("serial").then(() => {
this.selectActivePort();
});
return;
@@ -120,8 +120,8 @@ PortHandler.removedSerialDevice = function (device) {
// Update the appropriate ports list based on the device type
const updatePromise = devicePath.startsWith("bluetooth")
- ? this.updateDeviceList("webbluetooth")
- : this.updateDeviceList("webserial");
+ ? this.updateDeviceList("bluetooth")
+ : this.updateDeviceList("serial");
const wasSelectedPort = this.portPicker.selectedPort === devicePath;
@@ -274,7 +274,7 @@ PortHandler.handleDeviceAdded = function (device, deviceType) {
// Update the appropriate device list
const updatePromise =
- deviceType === "webbluetooth" ? this.updateDeviceList("webbluetooth") : this.updateDeviceList("webserial");
+ deviceType === "bluetooth" ? this.updateDeviceList("bluetooth") : this.updateDeviceList("serial");
updatePromise.then(() => {
const selectedPort = this.selectActivePort(device);
@@ -295,9 +295,9 @@ PortHandler.updateDeviceList = async function (deviceType) {
try {
switch (deviceType) {
- case "webbluetooth":
+ case "bluetooth":
if (this.showBluetoothOption) {
- ports = await serial.getDevices("webbluetooth");
+ ports = await serial.getDevices("bluetooth");
}
break;
case "usb":
@@ -305,9 +305,9 @@ PortHandler.updateDeviceList = async function (deviceType) {
ports = await WEBUSBDFU.getDevices();
}
break;
- case "webserial":
+ case "serial":
if (this.showSerialOption) {
- ports = await serial.getDevices("webserial");
+ ports = await serial.getDevices("serial");
}
break;
default:
@@ -320,7 +320,7 @@ PortHandler.updateDeviceList = async function (deviceType) {
// Update the appropriate properties based on device type
switch (deviceType) {
- case "webbluetooth":
+ case "bluetooth":
this.bluetoothAvailable = orderedPorts.length > 0;
this.currentBluetoothPorts = [...orderedPorts];
console.log(`${this.logHead} Found bluetooth port(s)`, orderedPorts);
@@ -330,7 +330,7 @@ PortHandler.updateDeviceList = async function (deviceType) {
this.currentUsbPorts = [...orderedPorts];
console.log(`${this.logHead} Found DFU port(s)`, orderedPorts);
break;
- case "webserial":
+ case "serial":
this.portAvailable = orderedPorts.length > 0;
this.currentSerialPorts = [...orderedPorts];
console.log(`${this.logHead} Found serial port(s)`, orderedPorts);
diff --git a/src/js/protocols/CapacitorSerial.js b/src/js/protocols/CapacitorSerial.js
new file mode 100644
index 0000000000..8773b606f7
--- /dev/null
+++ b/src/js/protocols/CapacitorSerial.js
@@ -0,0 +1,287 @@
+import { UsbSerial } from "capacitor-plugin-usb-serial";
+import { serialDevices, vendorIdNames } from "./devices";
+
+const logHead = "[Capacitor Serial]";
+
+class CapacitorSerialProtocol extends EventTarget {
+ constructor() {
+ super();
+
+ this.connected = false;
+ this.connectionInfo = null;
+
+ this.bytesSent = 0;
+ this.bytesReceived = 0;
+ this.isOpen = false;
+ this.port = null;
+ this.ports = [];
+
+ this.connectionId = null;
+ this.reading = false;
+ this.selectedDevice = null;
+
+ this.connect = this.connect.bind(this);
+ this.disconnect = this.disconnect.bind(this);
+ this.handleDataEvent = this.handleDataEvent.bind(this);
+ this.handleConnectedEvent = this.handleConnectedEvent.bind(this);
+ this.handleAttachedEvent = this.handleAttachedEvent.bind(this);
+ this.handleDetachedEvent = this.handleDetachedEvent.bind(this);
+ this.handleErrorEvent = this.handleErrorEvent.bind(this);
+
+ // Set up plugin event listeners
+ this.setupPluginListeners();
+ }
+
+ setupPluginListeners() {
+ // Listen for data from the USB serial plugin
+ UsbSerial.addListener("data", this.handleDataEvent);
+
+ // Listen for connection events
+ UsbSerial.addListener("connected", this.handleConnectedEvent);
+
+ // Listen for device attach/detach
+ UsbSerial.addListener("attached", this.handleAttachedEvent);
+ UsbSerial.addListener("detached", this.handleDetachedEvent);
+
+ // Listen for errors
+ UsbSerial.addListener("error", this.handleErrorEvent);
+ }
+
+ cleanup() {
+ // Don't remove listeners - they need to persist for reconnection and device attach/detach events
+ // Only cleanup is handled in disconnect() by resetting state
+ }
+
+ handleDataEvent(event) {
+ if (event?.data) {
+ // Convert hex string from plugin to Uint8Array
+ const uint8Array = this.hexStringToUint8Array(event.data);
+ this.bytesReceived += uint8Array.byteLength;
+ this.dispatchEvent(new CustomEvent("receive", { detail: uint8Array }));
+ }
+ }
+
+ handleConnectedEvent(event) {
+ this.connected = true;
+ }
+
+ async handleAttachedEvent(event) {
+ // Reload device list to ensure we have the latest device info with proper permissions
+ await this.loadDevices();
+
+ const added = this.handleNewDevice(event);
+ if (added) {
+ this.dispatchEvent(new CustomEvent("addedDevice", { detail: added }));
+ }
+ }
+
+ handleDetachedEvent(event) {
+ this.handleDeviceRemoval(event);
+ }
+
+ handleErrorEvent(event) {
+ console.error(`${logHead} Error:`, event.error);
+ this.dispatchEvent(new CustomEvent("error", { detail: event.error }));
+ }
+
+ hexStringToUint8Array(hexString) {
+ const length = hexString.length / 2;
+ const uint8Array = new Uint8Array(length);
+
+ for (let i = 0; i < length; i++) {
+ uint8Array[i] = Number.parseInt(hexString.slice(i * 2, i * 2 + 2), 16);
+ }
+
+ return uint8Array;
+ }
+
+ arrayBufferToHexString(buffer) {
+ const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
+ return Array.from(bytes)
+ .map((b) => b.toString(16).padStart(2, "0"))
+ .join("");
+ }
+
+ getConnectedPort() {
+ return this.connectionId;
+ }
+
+ handleNewDevice(device) {
+ const added = this.createPort(device);
+ if (added) {
+ this.ports.push(added);
+ }
+ return added;
+ }
+
+ createPort(device) {
+ // Avoid duplicates
+ if (this.ports.some((p) => p.vendorId === device.vendorId && p.productId === device.productId)) {
+ return null;
+ }
+ const displayName = vendorIdNames[device.vendorId]
+ ? vendorIdNames[device.vendorId]
+ : `VID:${device.vendorId} PID:${device.productId}`;
+
+ return {
+ path: "serial",
+ displayName: `Betaflight ${displayName}`,
+ vendorId: device.vendorId,
+ productId: device.productId,
+ deviceId: device.deviceId,
+ };
+ }
+
+ handleDeviceRemoval(device) {
+ const index = this.ports.findIndex((p) => p.vendorId === device.vendorId && p.productId === device.productId);
+ if (index !== -1) {
+ const removed = this.ports.splice(index, 1)[0];
+ this.dispatchEvent(new CustomEvent("removedDevice", { detail: removed }));
+ }
+ }
+
+ async loadDevices() {
+ try {
+ const result = await UsbSerial.connectedDevices();
+ this.ports = [];
+
+ if (result?.devices && Array.isArray(result.devices)) {
+ for (const device of result.devices) {
+ const port = this.createPort(device.device);
+ if (port) {
+ this.ports.push(port);
+ }
+ }
+ }
+ } catch (error) {
+ console.error(`${logHead} Error loading devices:`, error);
+ }
+ }
+
+ async getDevices() {
+ await this.loadDevices();
+ return this.ports;
+ }
+
+ async connect(path, options = { baudRate: 115200 }) {
+ try {
+ const device = this.ports.find((device) => device.path === path);
+
+ if (!device) {
+ console.error(`${logHead} Device not found`);
+ this.dispatchEvent(new CustomEvent("connect", { detail: false }));
+ return false;
+ }
+
+ await UsbSerial.openSerial({
+ deviceId: device.deviceId,
+ portNum: 0,
+ baudRate: options.baudRate || 115200,
+ dataBits: 8,
+ stopBits: 1,
+ parity: 0,
+ dtr: false,
+ rts: false,
+ });
+
+ this.isOpen = true;
+ this.connectionId = path;
+ this.connected = true;
+ this.port = device;
+
+ this.dispatchEvent(new CustomEvent("connect", { detail: true }));
+
+ return true;
+ } catch (error) {
+ console.error(`${logHead} Error opening serial connection:`, error);
+ this.dispatchEvent(new CustomEvent("connect", { detail: false }));
+ return false;
+ }
+ }
+
+ async requestPermissionDevice() {
+ try {
+ await this.loadDevices();
+
+ // Try to find a matching device from our known serial devices list
+ for (const { vendorId, productId } of serialDevices) {
+ const device = this.ports.find((p) => p.vendorId === vendorId && p.productId === productId);
+ if (device) {
+ this.selectedDevice = device;
+ return device;
+ }
+ }
+
+ // If no known device found, select the first available device
+ if (this.ports.length > 0) {
+ this.selectedDevice = this.ports[0];
+ return this.selectedDevice;
+ }
+
+ return null;
+ } catch (error) {
+ console.error(`${logHead} Error requesting device permission:`, error);
+ return null;
+ }
+ }
+
+ async send(data, callback) {
+ if (!this.isOpen) {
+ console.error(`${logHead} Cannot send - not connected`);
+ callback?.({ bytesSent: 0 });
+ return false;
+ }
+
+ try {
+ // Handle both Uint8Array and ArrayBuffer
+ const uint8Array = data instanceof Uint8Array ? data : new Uint8Array(data);
+
+ // Convert to hex string for the plugin
+ const hexString = this.arrayBufferToHexString(uint8Array);
+
+ await UsbSerial.writeSerial({ data: hexString });
+
+ const bytesSent = uint8Array.byteLength;
+ this.bytesSent += bytesSent;
+
+ callback?.({ bytesSent });
+ return true;
+ } catch (error) {
+ console.error(`${logHead} Error sending data:`, error);
+ callback?.({ bytesSent: 0 });
+ return false;
+ }
+ }
+
+ async disconnect(callback) {
+ if (!this.isOpen) {
+ callback?.(true);
+ return;
+ }
+
+ this.reading = false;
+ let closeError = null;
+
+ try {
+ await UsbSerial.closeSerial();
+ } catch (error) {
+ console.error(`${logHead} Error closing serial connection:`, error);
+ closeError = error;
+ } finally {
+ // Reset state but keep listeners active for reconnection
+ this.isOpen = false;
+ this.connected = false;
+ this.port = null;
+ this.connectionId = null;
+ this.dispatchEvent(new CustomEvent("disconnect", { detail: true }));
+ }
+
+ callback?.(!closeError);
+
+ if (closeError) {
+ throw new Error("Failed to close serial port", { cause: closeError });
+ }
+ }
+}
+
+export default CapacitorSerialProtocol;
diff --git a/src/js/serial.js b/src/js/serial.js
index d9a1a57047..edaf18d136 100644
--- a/src/js/serial.js
+++ b/src/js/serial.js
@@ -2,6 +2,8 @@ import WebSerial from "./protocols/WebSerial.js";
import WebBluetooth from "./protocols/WebBluetooth.js";
import Websocket from "./protocols/WebSocket.js";
import VirtualSerial from "./protocols/VirtualSerial.js";
+import { isAndroid } from "./utils/checkCompatibility.js";
+import CapacitorSerial from "./protocols/CapacitorSerial.js";
/**
* Base Serial class that manages all protocol implementations
@@ -16,12 +18,19 @@ class Serial extends EventTarget {
this.logHead = "[SERIAL]";
// Initialize protocols with metadata for easier lookup
- this._protocols = [
- { name: "webserial", instance: new WebSerial() },
- { name: "webbluetooth", instance: new WebBluetooth() },
- { name: "websocket", instance: new Websocket() },
- { name: "virtual", instance: new VirtualSerial() },
- ];
+
+ if (isAndroid()) {
+ this._protocols = [{ name: "serial", instance: new CapacitorSerial() }];
+ } else {
+ this._protocols = [
+ { name: "serial", instance: new WebSerial() },
+ { name: "bluetooth", instance: new WebBluetooth() },
+ { name: "websocket", instance: new Websocket() },
+ ];
+ }
+
+ // Always add virtual protocol
+ this._protocols.push({ name: "virtual", instance: new VirtualSerial() });
// Forward events from all protocols to the Serial class
this._setupEventForwarding();
@@ -74,7 +83,7 @@ class Serial extends EventTarget {
// Determine which protocol to use based on port path
const isFn = typeof portPath === "function";
const s = typeof portPath === "string" ? portPath : "";
- // Default to webserial for typical serial device identifiers.
+ // Default to serial for typical serial device identifiers.
if (isFn || s === "virtual") {
return this._protocols.find((p) => p.name === "virtual")?.instance;
}
@@ -82,9 +91,9 @@ class Serial extends EventTarget {
return this._protocols.find((p) => p.name === "websocket")?.instance;
}
if (s.startsWith("bluetooth")) {
- return this._protocols.find((p) => p.name === "webbluetooth")?.instance;
+ return this._protocols.find((p) => p.name === "bluetooth")?.instance;
}
- return this._protocols.find((p) => p.name === "webserial")?.instance;
+ return this._protocols.find((p) => p.name === "serial")?.instance;
}
/**
@@ -141,7 +150,7 @@ class Serial extends EventTarget {
/**
* Get devices from a specific protocol type or current protocol
- * @param {string} protocolType - Optional protocol type ('webserial', 'webbluetooth', 'websocket', 'virtual')
+ * @param {string} protocolType - Optional protocol type ('serial', 'bluetooth', 'websocket', 'virtual')
* @returns {Promise} - List of devices
*/
async getDevices(protocolType = null) {
diff --git a/src/js/utils/checkBrowserCompatibility.js b/src/js/utils/checkCompatibility.js
similarity index 61%
rename from src/js/utils/checkBrowserCompatibility.js
rename to src/js/utils/checkCompatibility.js
index da3e0fdc20..d0a0a01b3b 100644
--- a/src/js/utils/checkBrowserCompatibility.js
+++ b/src/js/utils/checkCompatibility.js
@@ -4,8 +4,8 @@ import { Capacitor } from "@capacitor/core";
// Returns standardized OS name string or "unknown"
export function getOS() {
let os = "unknown";
- const userAgent = window.navigator.userAgent;
- const platform = window.navigator?.userAgentData?.platform || window.navigator.platform;
+ const userAgent = globalThis.navigator.userAgent;
+ const platform = globalThis.navigator?.userAgentData?.platform;
const macosPlatforms = ["Macintosh", "MacIntel", "MacPPC", "Mac68K", "macOS"];
const windowsPlatforms = ["Win32", "Win64", "Windows", "WinCE"];
const iosPlatforms = ["iPhone", "iPad", "iPod"];
@@ -53,17 +53,10 @@ export function isIOS() {
return false;
}
-export function isCapacitorWeb() {
- if (Capacitor.isNativePlatform()) {
- return Capacitor.getPlatform() === "web";
- }
- return false;
-}
-
-export function checkBrowserCompatibility() {
- const isWebSerial = checkWebSerialSupport();
- const isWebBluetooth = checkWebBluetoothSupport();
- const isWebUSB = checkWebUSBSupport();
+export function checkCompatibility() {
+ const hasSerialSupport = checkSerialSupport();
+ const hasBluetoothSupport = checkBluetoothSupport();
+ const hasUsbSupport = checkUsbSupport();
const isChromium = isChromiumBrowser();
const isNative = Capacitor.isNativePlatform();
@@ -72,17 +65,18 @@ export function checkBrowserCompatibility() {
const isTestEnvironment =
typeof process !== "undefined" && (process.env.NODE_ENV === "test" || process.env.JEST_WORKER_ID !== undefined);
- const compatible = isTestEnvironment || isNative || (isChromium && (isWebSerial || isWebBluetooth || isWebUSB));
+ const compatible =
+ isTestEnvironment || isNative || (isChromium && (hasSerialSupport || hasBluetoothSupport || hasUsbSupport));
console.log("User Agent: ", navigator.userAgentData);
console.log("Native: ", isNative);
console.log("Chromium: ", isChromium);
- console.log("Web Serial: ", isWebSerial);
+ console.log("Serial: ", hasSerialSupport);
+ console.log("Bluetooth: ", hasBluetoothSupport);
+ console.log("USB: ", hasUsbSupport);
console.log("OS: ", getOS());
-
console.log("Android: ", isAndroid());
console.log("iOS: ", isIOS());
- console.log("Capacitor web: ", isCapacitorWeb());
if (compatible) {
return true;
@@ -93,16 +87,16 @@ export function checkBrowserCompatibility() {
errorMessage = "Betaflight app requires a Chromium based browser (Chrome, Chromium, Edge).
";
}
- if (!isWebBluetooth) {
- errorMessage += "
- Web Bluetooth API support is disabled.";
+ if (!hasBluetoothSupport) {
+ errorMessage += "
- Bluetooth API support is disabled.";
}
- if (!isWebSerial) {
- errorMessage += "
- Web Serial API support is disabled.";
+ if (!hasSerialSupport) {
+ errorMessage += "
- Serial API support is disabled.";
}
- if (!isWebUSB) {
- errorMessage += "
- Web USB API support is disabled.";
+ if (!hasUsbSupport) {
+ errorMessage += "
- USB API support is disabled.";
}
const newDiv = document.createElement("div");
@@ -131,44 +125,39 @@ export function checkBrowserCompatibility() {
throw new Error("No compatible browser found.");
}
-export function checkWebSerialSupport() {
- if (!navigator.serial) {
- console.error("Web Serial API is not supported in this browser.");
- return false;
- }
-
- if (isIOS()) {
- console.error("Web Serial API is not supported on iOS.");
- return false;
+export function checkSerialSupport() {
+ let result = false;
+ if (isAndroid()) {
+ result = true;
+ } else if (navigator.serial) {
+ result = true;
+ } else if (isIOS()) {
+ // Not implemented yet
}
- return true;
+ return result;
}
-export function checkWebBluetoothSupport() {
- if (!navigator.bluetooth) {
- console.error("Web Bluetooth API is not supported in this browser.");
- return false;
- }
-
- if (isIOS()) {
- console.error("Web Bluetooth API is not supported on iOS.");
- return false;
+export function checkBluetoothSupport() {
+ let result = false;
+ if (isAndroid()) {
+ // Not implemented yet
+ } else if (navigator.bluetooth) {
+ result = true;
+ } else if (isIOS()) {
+ // Not implemented yet
}
-
- return true;
+ return result;
}
-export function checkWebUSBSupport() {
- if (!navigator.usb) {
- console.error("Web USB API is not supported in this browser.");
- return false;
+export function checkUsbSupport() {
+ let result = false;
+ if (isAndroid()) {
+ // Not implemented yet
+ } else if (navigator.usb) {
+ result = true;
+ } else if (isIOS()) {
+ // Not implemented yet
}
-
- if (isIOS()) {
- console.error("Web USB API is not supported on iOS.");
- return false;
- }
-
- return true;
+ return result;
}
diff --git a/vite.config.js b/vite.config.js
index 1d973f1ca8..5fa4e6898e 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -120,6 +120,7 @@ export default defineConfig({
server: {
port: 8000,
strictPort: true,
+ host: "0.0.0.0", // Listen on all network interfaces for Android device access
},
preview: {
port: 8080,
diff --git a/yarn.lock b/yarn.lock
index ed71ba4025..91d2a6db92 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2448,6 +2448,11 @@
resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz#a1337ca426aa61cef9fe15b5b28e340a72f6fa99"
integrity sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==
+"@yarnpkg/lockfile@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
+ integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
+
abbrev@1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
@@ -3203,6 +3208,11 @@ caniuse-lite@^1.0.30001737:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz#67fb92953edc536442f3c9da74320774aa523143"
integrity sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==
+capacitor-plugin-usb-serial@^0.0.6:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/capacitor-plugin-usb-serial/-/capacitor-plugin-usb-serial-0.0.6.tgz#21bc7dd4a08be81351914f90f116d93b587d101b"
+ integrity sha512-AHclGGPWsk/+kF2s6CdOCxAkZv8W+zutBJcSN869EHxGN/gYzyLc/AUn0wCNFwOY2M78yXmsQV72cHzZKnvMWQ==
+
caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
@@ -3250,7 +3260,7 @@ chalk@^2.0.0:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
-chalk@^4.0.0, chalk@^4.1.0:
+chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@@ -3302,6 +3312,11 @@ chownr@^2.0.0:
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
+ci-info@^3.7.0:
+ version "3.9.0"
+ resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4"
+ integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
+
class-utils@^0.3.5:
version "0.3.6"
resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
@@ -5082,6 +5097,13 @@ find-up@^5.0.0:
locate-path "^6.0.0"
path-exists "^4.0.0"
+find-yarn-workspace-root@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd"
+ integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==
+ dependencies:
+ micromatch "^4.0.2"
+
find@^0.2.8:
version "0.2.9"
resolved "https://registry.yarnpkg.com/find/-/find-0.2.9.tgz#4b73f1ff9e56ad91b76e716407fe5ffe6554bb8c"
@@ -5249,6 +5271,15 @@ fs-extra@^1.0.0:
jsonfile "^2.1.0"
klaw "^1.0.0"
+fs-extra@^10.0.0:
+ version "10.1.0"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
+ integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
+ dependencies:
+ graceful-fs "^4.2.0"
+ jsonfile "^6.0.1"
+ universalify "^2.0.0"
+
fs-extra@^11.2.0, fs-extra@^11.3.0:
version "11.3.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.0.tgz#0daced136bbaf65a555a326719af931adc7a314d"
@@ -6619,7 +6650,7 @@ is-windows@^1.0.1, is-windows@^1.0.2:
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
-is-wsl@^2.2.0:
+is-wsl@^2.1.1, is-wsl@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
@@ -6823,6 +6854,17 @@ json-stable-stringify-without-jsonify@^1.0.1:
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
+json-stable-stringify@^1.0.2:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz#8903cfac42ea1a0f97f35d63a4ce0518f0cc6a70"
+ integrity sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==
+ dependencies:
+ call-bind "^1.0.8"
+ call-bound "^1.0.4"
+ isarray "^2.0.5"
+ jsonify "^0.0.1"
+ object-keys "^1.1.1"
+
json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
@@ -6856,6 +6898,11 @@ jsonfile@^6.0.1:
optionalDependencies:
graceful-fs "^4.1.6"
+jsonify@^0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978"
+ integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==
+
jsonpointer@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559"
@@ -6907,6 +6954,13 @@ kind-of@^6.0.2:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
+klaw-sync@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c"
+ integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==
+ dependencies:
+ graceful-fs "^4.1.11"
+
klaw@^1.0.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439"
@@ -7413,7 +7467,7 @@ micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4:
snapdragon "^0.8.1"
to-regex "^3.0.2"
-micromatch@^4.0.8:
+micromatch@^4.0.2, micromatch@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
@@ -7917,6 +7971,14 @@ onetime@^7.0.0:
dependencies:
mimic-function "^5.0.0"
+open@^7.4.2:
+ version "7.4.2"
+ resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321"
+ integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==
+ dependencies:
+ is-docker "^2.0.0"
+ is-wsl "^2.1.1"
+
open@^8.4.0:
version "8.4.2"
resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9"
@@ -8050,6 +8112,26 @@ pascalcase@^0.1.1:
resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
integrity sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==
+patch-package@^8.0.1:
+ version "8.0.1"
+ resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-8.0.1.tgz#79d02f953f711e06d1f8949c8a13e5d3d7ba1a60"
+ integrity sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==
+ dependencies:
+ "@yarnpkg/lockfile" "^1.1.0"
+ chalk "^4.1.2"
+ ci-info "^3.7.0"
+ cross-spawn "^7.0.3"
+ find-yarn-workspace-root "^2.0.0"
+ fs-extra "^10.0.0"
+ json-stable-stringify "^1.0.2"
+ klaw-sync "^6.0.0"
+ minimist "^1.2.6"
+ open "^7.4.2"
+ semver "^7.5.3"
+ slash "^2.0.0"
+ tmp "^0.2.4"
+ yaml "^2.2.2"
+
path-dirname@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0"
@@ -8283,6 +8365,11 @@ postcss@^8.4.48, postcss@^8.5.3, postcss@^8.5.6:
picocolors "^1.1.1"
source-map-js "^1.2.1"
+postinstall-postinstall@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3"
+ integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ==
+
prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@@ -9219,6 +9306,11 @@ sisteransi@^1.0.5:
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
+slash@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
+ integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==
+
slash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
@@ -9488,7 +9580,7 @@ string-argv@^0.3.2:
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6"
integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==
-"string-width-cjs@npm:string-width@^4.2.0":
+"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -9506,15 +9598,6 @@ string-width@^1.0.1, string-width@^1.0.2:
is-fullwidth-code-point "^1.0.0"
strip-ansi "^3.0.0"
-"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
- version "4.2.3"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
- integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
- dependencies:
- emoji-regex "^8.0.0"
- is-fullwidth-code-point "^3.0.0"
- strip-ansi "^6.0.1"
-
string-width@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
@@ -9620,7 +9703,7 @@ stringify-object@^3.3.0:
is-obj "^1.0.1"
is-regexp "^1.0.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -9648,13 +9731,6 @@ strip-ansi@^4.0.0:
dependencies:
ansi-regex "^3.0.0"
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
- integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
- dependencies:
- ansi-regex "^5.0.1"
-
strip-ansi@^7.0.1, strip-ansi@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -9977,6 +10053,11 @@ tmp@^0.0.33:
dependencies:
os-tmpdir "~1.0.2"
+tmp@^0.2.4:
+ version "0.2.5"
+ resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.5.tgz#b06bcd23f0f3c8357b426891726d16015abfd8f8"
+ integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==
+
to-absolute-glob@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1865f43d9e74b0822db9f145b78cff7d0f7c849b"
@@ -10927,7 +11008,7 @@ workbox-window@7.3.0, workbox-window@^7.3.0:
"@types/trusted-types" "^2.0.2"
workbox-core "7.3.0"
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -10953,15 +11034,6 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
-wrap-ansi@^7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
- integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
- dependencies:
- ansi-styles "^4.0.0"
- string-width "^4.1.0"
- strip-ansi "^6.0.0"
-
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
@@ -11053,6 +11125,11 @@ yallist@^4.0.0:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
+yaml@^2.2.2:
+ version "2.8.1"
+ resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.1.tgz#1870aa02b631f7e8328b93f8bc574fac5d6c4d79"
+ integrity sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==
+
yaml@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.7.0.tgz#aef9bb617a64c937a9a748803786ad8d3ffe1e98"