feat: force web usb

This commit is contained in:
2026-02-11 18:32:06 +01:00
parent 5e4283a462
commit dee754c015
5 changed files with 119 additions and 86 deletions

View File

@@ -1,4 +1,4 @@
import { get, writable } from "svelte/store";
import { derived, get, writable } from "svelte/store";
import { CharaDevice, type SerialPortLike } from "$lib/serial/device";
import type { Chord } from "$lib/serial/chord";
import type { Writable } from "svelte/store";
@@ -7,12 +7,27 @@ import { persistentWritable } from "$lib/storage";
import { userPreferences } from "$lib/preferences";
import { getMeta } from "$lib/meta/meta-storage";
import type { VersionMeta } from "$lib/meta/types/meta";
import { serial as serialPolyfill } from "web-serial-polyfill";
export const serialPort = writable<CharaDevice | undefined>();
navigator.serial?.addEventListener("disconnect", async (event) => {
export const forceWebUSB = persistentWritable("force-webusb", false);
async function onSerialDisconnect() {
serialPort.set(undefined);
});
}
export const serialObject = derived<typeof forceWebUSB, Serial>(
forceWebUSB,
(forceWebUSB) =>
forceWebUSB || !("serial" in navigator)
? (serialPolyfill as any as Serial)
: navigator.serial,
);
if ("serial" in navigator) {
navigator.serial.addEventListener("disconnect", onSerialDisconnect);
}
export interface SerialLogEntry {
type: "input" | "output" | "system";

View File

@@ -7,7 +7,6 @@ import {
stringifyChordActions,
stringifyPhrase,
} from "$lib/serial/chord";
import { browser } from "$app/environment";
import { showConnectionFailedDialog } from "$lib/dialogs/connection-failed-dialog";
import semverGte from "semver/functions/gte";
@@ -71,23 +70,8 @@ const KEY_COUNTS = {
ZERO: 256,
} as const;
if (
browser &&
navigator.serial === undefined &&
import.meta.env.TAURI_FAMILY !== undefined
) {
await import("./tauri-serial");
}
if (browser && navigator.serial === undefined && navigator.usb !== undefined) {
// @ts-expect-error polyfill
navigator.serial = await import("web-serial-polyfill").then(
({ serial }) => serial,
);
}
export async function getViablePorts(): Promise<SerialPort[]> {
return navigator.serial.getPorts().then((ports) =>
export async function getViablePorts(serial: Serial): Promise<SerialPort[]> {
return serial.getPorts().then((ports) =>
ports.filter((it) => {
const { usbProductId, usbVendorId } = it.getInfo();
for (const filter of PORT_FILTERS.values()) {
@@ -109,8 +93,8 @@ type LengthArray<T, N extends number, R extends T[] = []> = number extends N
? R
: LengthArray<T, N, [T, ...R]>;
export async function canAutoConnect() {
return getViablePorts().then((it) => it.length === 1);
export async function canAutoConnect(serial: Serial) {
return getViablePorts(serial).then((it) => it.length === 1);
}
async function timeout<T>(promise: Promise<T>, ms: number): Promise<T> {