@kelnishi/satmouse-client 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -276,7 +276,10 @@ var SatMouseConnection = class extends chunkI5MEZZOT_cjs.TypedEmitter {
276
276
  }
277
277
  if (!wtUrl && !wsUrl) {
278
278
  const tdUrl = this.options.tdUrl;
279
- const tdUrls = tdUrl ? [tdUrl] : ["https://localhost:18947/td.json", "http://localhost:18945/td.json"];
279
+ const tdUrls = tdUrl ? [tdUrl] : [
280
+ "https://127.0.0.1:18947/td.json",
281
+ "http://127.0.0.1:18945/td.json"
282
+ ];
280
283
  let resolved = false;
281
284
  for (const url of tdUrls) {
282
285
  try {
@@ -293,7 +296,7 @@ var SatMouseConnection = class extends chunkI5MEZZOT_cjs.TypedEmitter {
293
296
  }
294
297
  if (!resolved) {
295
298
  this.emit("error", new Error("Failed to fetch Thing Description"));
296
- wsUrl = "ws://localhost:18945/spatial";
299
+ wsUrl = "ws://127.0.0.1:18945/spatial";
297
300
  }
298
301
  }
299
302
  for (const proto of this.options.transports) {
@@ -395,6 +398,24 @@ var SatMouseConnection = class extends chunkI5MEZZOT_cjs.TypedEmitter {
395
398
  // src/core/launch.ts
396
399
  var SCHEME_URL = "satmouse://launch";
397
400
  var PROJECT_URL = "https://github.com/kelnishi/SatMouse/releases/latest";
401
+ function negotiateViaSatMouse(origin, callbackPath = "/satmouse-handshake", challenge) {
402
+ const params = new URLSearchParams({ origin, callback: callbackPath });
403
+ if (challenge) params.set("challenge", challenge);
404
+ globalThis.location.href = `satmouse://negotiate?${params}`;
405
+ }
406
+ function parseNegotiateCallback(searchParams) {
407
+ const ip = searchParams.get("ip");
408
+ const wsPort = searchParams.get("wsPort");
409
+ if (!ip || !wsPort) return null;
410
+ return {
411
+ ip,
412
+ wsPort: parseInt(wsPort, 10),
413
+ wtPort: parseInt(searchParams.get("wtPort") ?? "18946", 10),
414
+ httpsPort: parseInt(searchParams.get("httpsPort") ?? "18947", 10),
415
+ certHash: searchParams.get("certHash") ?? void 0,
416
+ challenge: searchParams.get("challenge") ?? void 0
417
+ };
418
+ }
398
419
  function launchSatMouse(options) {
399
420
  const schemeUrl = options?.schemeUrl ?? SCHEME_URL;
400
421
  const timeout = options?.timeout ?? 2500;
@@ -442,6 +463,8 @@ exports.decodeButtonStream = decodeButtonStream;
442
463
  exports.decodeWsBinaryFrame = decodeWsBinaryFrame;
443
464
  exports.fetchThingDescription = fetchThingDescription;
444
465
  exports.launchSatMouse = launchSatMouse;
466
+ exports.negotiateViaSatMouse = negotiateViaSatMouse;
467
+ exports.parseNegotiateCallback = parseNegotiateCallback;
445
468
  exports.parseSatMouseUri = parseSatMouseUri;
446
469
  exports.resolveEndpoints = resolveEndpoints;
447
470
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/discovery.ts","../../src/core/decode.ts","../../src/core/transports/webtransport.ts","../../src/core/transports/websocket.ts","../../src/core/connection.ts","../../src/core/launch.ts"],"names":["TypedEmitter"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { ThingDescription } from \"./types.js\";\n\nexport interface ResolvedEndpoints {\n webtransport?: { url: string; certHash?: string };\n websocket?: { url: string };\n deviceInfoUrl?: string;\n}\n\n/** Fetch the WoT Thing Description from the bridge */\nexport async function fetchThingDescription(tdUrl: string): Promise<ThingDescription> {\n const res = await globalThis.fetch(tdUrl);\n if (!res.ok) throw new Error(`Failed to fetch TD: HTTP ${res.status}`);\n return res.json() as Promise<ThingDescription>;\n}\n\n/** Extract WebTransport, WebSocket, and device info endpoints from a Thing Description */\nexport function resolveEndpoints(td: ThingDescription): ResolvedEndpoints {\n const result: ResolvedEndpoints = {};\n\n const spatialForms = td.events?.spatialData?.forms ?? [];\n\n const wtForm = spatialForms.find((f) => f.subprotocol === \"webtransport\");\n if (wtForm) {\n result.webtransport = {\n url: wtForm.href,\n certHash: td[\"satmouse:certHash\"],\n };\n }\n\n const wsForm = spatialForms.find((f) => f.subprotocol === \"websocket\");\n if (wsForm) {\n result.websocket = { url: wsForm.href };\n }\n\n const deviceForm = td.properties?.deviceInfo?.forms?.[0];\n if (deviceForm) {\n result.deviceInfoUrl = deviceForm.href;\n }\n\n return result;\n}\n","import type { SpatialData, ButtonEvent } from \"./types.js\";\n\n/** Decode 24-byte binary spatial data datagram (WebTransport or raw binary) */\nexport function decodeBinaryFrame(buffer: ArrayBuffer | Uint8Array): SpatialData {\n const len = buffer instanceof ArrayBuffer ? buffer.byteLength : buffer.byteLength;\n if (len < 20) {\n throw new RangeError(`Spatial frame too short: expected ≥20 bytes, got ${len}`);\n }\n const ab = buffer instanceof ArrayBuffer ? buffer : buffer.buffer;\n const offset = buffer instanceof Uint8Array ? buffer.byteOffset : 0;\n const view = new DataView(ab, offset);\n return {\n translation: {\n x: view.getInt16(8, true),\n y: view.getInt16(10, true),\n z: view.getInt16(12, true),\n },\n rotation: {\n x: view.getInt16(14, true),\n y: view.getInt16(16, true),\n z: view.getInt16(18, true),\n },\n timestamp: view.getFloat64(0, true),\n };\n}\n\n/** Decode WebSocket binary frame (1-byte type prefix + 24-byte payload) */\nexport function decodeWsBinaryFrame(\n buffer: ArrayBuffer | Uint8Array,\n): { type: \"spatialData\"; data: SpatialData } | { type: \"buttonEvent\"; data: ButtonEvent } | null {\n const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);\n if (bytes.length < 1) return null;\n\n const typePrefix = bytes[0];\n if (typePrefix === 0x01 && bytes.length >= 25) {\n return { type: \"spatialData\", data: decodeBinaryFrame(bytes.subarray(1, 25)) };\n }\n if (typePrefix === 0x02) {\n const json = new TextDecoder().decode(bytes.subarray(1));\n return { type: \"buttonEvent\", data: JSON.parse(json) as ButtonEvent };\n }\n return null;\n}\n\n/** Decode length-prefixed JSON button events from a WebTransport stream chunk */\nexport function decodeButtonStream(\n buffer: Uint8Array<ArrayBufferLike>,\n): { events: ButtonEvent[]; remainder: Uint8Array<ArrayBufferLike> } {\n const events: ButtonEvent[] = [];\n let pos = 0;\n\n while (pos + 4 <= buffer.length) {\n const view = new DataView(buffer.buffer, buffer.byteOffset + pos);\n const len = view.getUint32(0, true);\n // Guard against absurd lengths\n if (len > 65536 || pos + 4 + len > buffer.length) break;\n const json = new TextDecoder().decode(buffer.subarray(pos + 4, pos + 4 + len));\n try {\n const event = JSON.parse(json);\n if (typeof event.button === \"number\" && typeof event.pressed === \"boolean\") {\n events.push(event as ButtonEvent);\n }\n } catch {\n // Skip malformed JSON frames\n }\n pos += 4 + len;\n }\n\n return { events, remainder: buffer.subarray(pos) };\n}\n","import type { Transport } from \"./transport.js\";\nimport type { SpatialData, ButtonEvent } from \"../types.js\";\nimport { decodeBinaryFrame, decodeButtonStream } from \"../decode.js\";\n\nexport class WebTransportAdapter implements Transport {\n readonly protocol = \"webtransport\" as const;\n\n onSpatialData: ((data: SpatialData) => void) | null = null;\n onButtonEvent: ((data: ButtonEvent) => void) | null = null;\n onClose: (() => void) | null = null;\n onError: ((error: Error) => void) | null = null;\n\n private transport: any = null;\n private url: string;\n private certHash?: string;\n\n constructor(url: string, certHash?: string) {\n this.url = url;\n this.certHash = certHash;\n }\n\n async connect(): Promise<void> {\n if (typeof globalThis.WebTransport === \"undefined\") {\n throw new Error(\"WebTransport is not available in this environment\");\n }\n\n const options: any = {};\n if (this.certHash) {\n options.serverCertificateHashes = [\n {\n algorithm: \"sha-256\",\n value: Uint8Array.from(atob(this.certHash), (c) => c.charCodeAt(0)),\n },\n ];\n }\n\n this.transport = new (globalThis as any).WebTransport(this.url, options);\n await this.transport.ready;\n\n this.readDatagrams();\n this.readStreams();\n\n this.transport.closed\n .then(() => this.onClose?.())\n .catch(() => this.onClose?.());\n }\n\n close(): void {\n try {\n this.transport?.close();\n } catch {}\n this.transport = null;\n }\n\n private async readDatagrams(): Promise<void> {\n const reader = this.transport.datagrams.readable.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n this.onSpatialData?.(decodeBinaryFrame(value));\n }\n } catch {\n // Transport closed\n }\n }\n\n private async readStreams(): Promise<void> {\n const reader = this.transport.incomingUnidirectionalStreams.getReader();\n try {\n while (true) {\n const { value: stream, done } = await reader.read();\n if (done) break;\n this.readButtonStream(stream);\n }\n } catch {\n // Transport closed\n }\n }\n\n private async readButtonStream(stream: any): Promise<void> {\n const reader = stream.getReader();\n let buffer: Uint8Array<ArrayBufferLike> = new Uint8Array(0);\n\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n\n const newBuf = new Uint8Array(buffer.length + value.length);\n newBuf.set(buffer);\n newBuf.set(value, buffer.length);\n\n const { events, remainder } = decodeButtonStream(newBuf);\n for (const event of events) {\n this.onButtonEvent?.(event);\n }\n buffer = remainder;\n }\n } catch {\n // Stream closed\n }\n }\n}\n","import type { Transport } from \"./transport.js\";\nimport type { SpatialData, ButtonEvent, DeviceInfo } from \"../types.js\";\nimport { decodeWsBinaryFrame } from \"../decode.js\";\n\nexport class WebSocketAdapter implements Transport {\n readonly protocol = \"websocket\" as const;\n\n onSpatialData: ((data: SpatialData) => void) | null = null;\n onButtonEvent: ((data: ButtonEvent) => void) | null = null;\n onDeviceStatus: ((event: \"connected\" | \"disconnected\", device: DeviceInfo) => void) | null = null;\n onClose: (() => void) | null = null;\n onError: ((error: Error) => void) | null = null;\n\n private ws: WebSocket | null = null;\n private url: string;\n private subprotocol: string;\n\n constructor(url: string, subprotocol: string = \"satmouse-json\") {\n this.url = url;\n this.subprotocol = subprotocol;\n }\n\n async connect(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n this.ws = new globalThis.WebSocket(this.url, this.subprotocol);\n if (this.subprotocol === \"satmouse-binary\") {\n this.ws.binaryType = \"arraybuffer\";\n }\n\n this.ws.onopen = () => resolve();\n\n this.ws.onerror = () => {\n reject(new Error(`WebSocket connection failed: ${this.url}`));\n };\n\n this.ws.onmessage = (event: MessageEvent) => {\n if (this.subprotocol === \"satmouse-binary\" && event.data instanceof ArrayBuffer) {\n const decoded = decodeWsBinaryFrame(event.data);\n if (decoded?.type === \"spatialData\") this.onSpatialData?.(decoded.data);\n else if (decoded?.type === \"buttonEvent\") this.onButtonEvent?.(decoded.data);\n } else if (typeof event.data === \"string\") {\n try {\n const msg = JSON.parse(event.data);\n if (msg.type === \"spatialData\") this.onSpatialData?.(msg.data);\n else if (msg.type === \"buttonEvent\") this.onButtonEvent?.(msg.data);\n else if (msg.type === \"deviceStatus\") {\n this.onDeviceStatus?.(msg.data.event, msg.data.device);\n }\n } catch {\n // Ignore malformed messages\n }\n }\n };\n\n this.ws.onclose = () => this.onClose?.();\n });\n }\n\n close(): void {\n try {\n this.ws?.close();\n } catch {}\n this.ws = null;\n }\n}\n","import { TypedEmitter } from \"./emitter.js\";\nimport { fetchThingDescription, resolveEndpoints } from \"./discovery.js\";\nimport { WebTransportAdapter } from \"./transports/webtransport.js\";\nimport { WebSocketAdapter } from \"./transports/websocket.js\";\nimport type { Transport } from \"./transports/transport.js\";\nimport type {\n ConnectOptions,\n ConnectionState,\n DeviceInfo,\n SatMouseEvents,\n TransportProtocol,\n} from \"./types.js\";\n\n/**\n * Build a satmouse:// connect URI from connection parameters.\n */\nexport function buildSatMouseUri(host = \"localhost\", wsPort = 18945, wtPort = 18946): string {\n return `satmouse://connect?host=${encodeURIComponent(host)}&wsPort=${wsPort}&wtPort=${wtPort}`;\n}\n\n/**\n * Parse a satmouse:// URI into connection parameters.\n *\n * Format: satmouse://connect?host=<ip>&wsPort=<port>&wtPort=<port>\n * All query params are optional. Defaults: host=localhost, wsPort=4444, wtPort=4443.\n */\nexport function parseSatMouseUri(uri: string): { tdUrl: string; wsUrl: string; wtUrl: string } {\n const url = new URL(uri);\n const host = url.searchParams.get(\"host\") ?? \"localhost\";\n const wsPort = url.searchParams.get(\"wsPort\") ?? \"18945\";\n const wtPort = url.searchParams.get(\"wtPort\") ?? \"18946\";\n return {\n tdUrl: `http://${host}:${wsPort}/td.json`,\n wsUrl: `ws://${host}:${wsPort}/spatial`,\n wtUrl: `https://${host}:${wtPort}`,\n };\n}\n\nconst DEFAULT_OPTIONS: Required<\n Pick<ConnectOptions, \"transports\" | \"reconnectDelay\" | \"maxRetries\" | \"wsSubprotocol\">\n> = {\n transports: [\"webtransport\", \"websocket\"],\n reconnectDelay: 2000,\n maxRetries: 3,\n wsSubprotocol: \"satmouse-json\",\n};\n\n/**\n * Core connection to a SatMouse bridge.\n *\n * Handles discovery (via WoT Thing Description), transport negotiation\n * (WebTransport with WebSocket fallback), event dispatch, and auto-reconnect.\n */\nexport class SatMouseConnection extends TypedEmitter<SatMouseEvents> {\n private options: ConnectOptions & typeof DEFAULT_OPTIONS;\n private transport: Transport | null = null;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private intentionalClose = false;\n private deviceInfoUrl: string | null = null;\n private retryCount = 0;\n\n private _state: ConnectionState = \"disconnected\";\n private _protocol: TransportProtocol = \"none\";\n\n get state(): ConnectionState {\n return this._state;\n }\n get protocol(): TransportProtocol {\n return this._protocol;\n }\n\n constructor(options?: ConnectOptions) {\n super();\n this.options = { ...DEFAULT_OPTIONS, ...options };\n }\n\n async connect(): Promise<void> {\n this.intentionalClose = false;\n this.setState(\"connecting\", \"none\");\n\n // Resolve endpoints — satmouse:// URI takes priority\n let wtUrl = this.options.wtUrl;\n let wsUrl = this.options.wsUrl;\n let certHash = this.options.certHash;\n\n if (this.options.uri) {\n const parsed = parseSatMouseUri(this.options.uri);\n wtUrl = wtUrl ?? parsed.wtUrl;\n wsUrl = wsUrl ?? parsed.wsUrl;\n this.options.tdUrl = this.options.tdUrl ?? parsed.tdUrl;\n }\n\n if (!wtUrl && !wsUrl) {\n const tdUrl = this.options.tdUrl;\n // Try HTTPS first (works from HTTPS pages), fall back to HTTP\n const tdUrls = tdUrl\n ? [tdUrl]\n : [\"https://localhost:18947/td.json\", \"http://localhost:18945/td.json\"];\n\n let resolved = false;\n for (const url of tdUrls) {\n try {\n const td = await fetchThingDescription(url);\n const endpoints = resolveEndpoints(td);\n wtUrl = endpoints.webtransport?.url;\n wsUrl = endpoints.websocket?.url;\n certHash = certHash ?? endpoints.webtransport?.certHash;\n this.deviceInfoUrl = endpoints.deviceInfoUrl ?? null;\n resolved = true;\n break;\n } catch {\n // Try next URL\n }\n }\n if (!resolved) {\n this.emit(\"error\", new Error(\"Failed to fetch Thing Description\"));\n wsUrl = \"ws://localhost:18945/spatial\";\n }\n }\n\n // Try transports in preference order\n for (const proto of this.options.transports) {\n if (proto === \"webtransport\" && wtUrl) {\n try {\n if (typeof globalThis.WebTransport === \"undefined\") continue;\n const adapter = new WebTransportAdapter(wtUrl, certHash);\n if (await this.tryTransport(adapter)) return;\n } catch {\n continue;\n }\n }\n if (proto === \"websocket\" && wsUrl) {\n try {\n const adapter = new WebSocketAdapter(wsUrl, this.options.wsSubprotocol);\n if (await this.tryTransport(adapter)) return;\n } catch {\n continue;\n }\n }\n }\n\n this.setState(\"disconnected\", \"none\");\n this.scheduleReconnect();\n }\n\n /** Reset retry count and reconnect. Use after \"failed\" state. */\n retry(): void {\n this.retryCount = 0;\n this.intentionalClose = false;\n this.connect();\n }\n\n disconnect(): void {\n this.intentionalClose = true;\n this.clearReconnect();\n this.transport?.close();\n this.transport = null;\n this.setState(\"disconnected\", \"none\");\n }\n\n async fetchDeviceInfo(): Promise<DeviceInfo[]> {\n if (!this.deviceInfoUrl) return [];\n const res = await globalThis.fetch(this.deviceInfoUrl);\n if (!res.ok) return [];\n const data = await res.json();\n return data.devices ?? [];\n }\n\n private async tryTransport(adapter: Transport): Promise<boolean> {\n adapter.onSpatialData = (data) => this.emit(\"spatialData\", data);\n adapter.onButtonEvent = (data) => this.emit(\"buttonEvent\", data);\n adapter.onError = (err) => this.emit(\"error\", err);\n\n if (\"onDeviceStatus\" in adapter) {\n (adapter as any).onDeviceStatus = (event: \"connected\" | \"disconnected\", device: DeviceInfo) => {\n this.emit(\"deviceStatus\", event, device);\n };\n }\n\n adapter.onClose = () => {\n this.transport = null;\n this.setState(\"disconnected\", \"none\");\n if (!this.intentionalClose) this.scheduleReconnect();\n };\n\n try {\n await adapter.connect();\n this.transport = adapter;\n this.retryCount = 0;\n this.setState(\"connected\", adapter.protocol);\n return true;\n } catch (err) {\n this.emit(\"error\", err instanceof Error ? err : new Error(String(err)));\n return false;\n }\n }\n\n private setState(state: ConnectionState, protocol: TransportProtocol): void {\n if (this._state === state && this._protocol === protocol) return;\n this._state = state;\n this._protocol = protocol;\n this.emit(\"stateChange\", state, protocol);\n }\n\n private scheduleReconnect(): void {\n if (this.options.reconnectDelay <= 0 || this.intentionalClose) return;\n\n this.retryCount++;\n console.log(`[SatMouse] Reconnect attempt ${this.retryCount}/${this.options.maxRetries}`);\n if (this.retryCount > this.options.maxRetries) {\n console.log(\"[SatMouse] Max retries exceeded, giving up\");\n this.setState(\"failed\", \"none\");\n return;\n }\n\n this.clearReconnect();\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.connect();\n }, this.options.reconnectDelay);\n }\n\n private clearReconnect(): void {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n }\n}\n","const SCHEME_URL = \"satmouse://launch\";\nconst PROJECT_URL = \"https://github.com/kelnishi/SatMouse/releases/latest\";\n\nexport interface LaunchOptions {\n /** URL scheme to open. Default: \"satmouse://launch\" */\n schemeUrl?: string;\n /** Fallback URL if the app is not installed. Default: GitHub releases page */\n fallbackUrl?: string;\n /** Timeout in ms before assuming the app is not installed. Default: 2500 */\n timeout?: number;\n}\n\n/**\n * Attempt to launch SatMouse via the `satmouse://` URL scheme.\n *\n * If the app is installed and registered, the OS opens it. If not,\n * navigates to the fallback URL (project releases page) after a timeout.\n *\n * Returns true if the scheme likely opened, false if it fell back.\n */\nexport function launchSatMouse(options?: LaunchOptions): Promise<boolean> {\n const schemeUrl = options?.schemeUrl ?? SCHEME_URL;\n const timeout = options?.timeout ?? 2500;\n\n // Validate fallback URL — only allow http/https to prevent javascript: or data: injection\n let fallbackUrl = PROJECT_URL;\n if (options?.fallbackUrl) {\n try {\n const parsed = new URL(options.fallbackUrl);\n if (parsed.protocol === \"http:\" || parsed.protocol === \"https:\") {\n fallbackUrl = options.fallbackUrl;\n }\n } catch {}\n }\n\n return new Promise((resolve) => {\n // Track if we leave the page (scheme handler opened the app)\n let launched = false;\n\n const onBlur = () => {\n launched = true;\n };\n globalThis.addEventListener(\"blur\", onBlur);\n\n // Use a hidden iframe to trigger the scheme without navigating away\n const iframe = document.createElement(\"iframe\");\n iframe.style.display = \"none\";\n iframe.src = schemeUrl;\n document.body.appendChild(iframe);\n\n setTimeout(() => {\n globalThis.removeEventListener(\"blur\", onBlur);\n document.body.removeChild(iframe);\n\n if (launched || document.hidden) {\n resolve(true);\n } else {\n // App not installed — redirect to project page\n globalThis.location.href = fallbackUrl;\n resolve(false);\n }\n }, timeout);\n });\n}\n"]}
1
+ {"version":3,"sources":["../../src/core/discovery.ts","../../src/core/decode.ts","../../src/core/transports/webtransport.ts","../../src/core/transports/websocket.ts","../../src/core/connection.ts","../../src/core/launch.ts"],"names":["TypedEmitter"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { ThingDescription } from \"./types.js\";\n\nexport interface ResolvedEndpoints {\n webtransport?: { url: string; certHash?: string };\n websocket?: { url: string };\n deviceInfoUrl?: string;\n}\n\n/** Fetch the WoT Thing Description from the bridge */\nexport async function fetchThingDescription(tdUrl: string): Promise<ThingDescription> {\n const res = await globalThis.fetch(tdUrl);\n if (!res.ok) throw new Error(`Failed to fetch TD: HTTP ${res.status}`);\n return res.json() as Promise<ThingDescription>;\n}\n\n/** Extract WebTransport, WebSocket, and device info endpoints from a Thing Description */\nexport function resolveEndpoints(td: ThingDescription): ResolvedEndpoints {\n const result: ResolvedEndpoints = {};\n\n const spatialForms = td.events?.spatialData?.forms ?? [];\n\n const wtForm = spatialForms.find((f) => f.subprotocol === \"webtransport\");\n if (wtForm) {\n result.webtransport = {\n url: wtForm.href,\n certHash: td[\"satmouse:certHash\"],\n };\n }\n\n const wsForm = spatialForms.find((f) => f.subprotocol === \"websocket\");\n if (wsForm) {\n result.websocket = { url: wsForm.href };\n }\n\n const deviceForm = td.properties?.deviceInfo?.forms?.[0];\n if (deviceForm) {\n result.deviceInfoUrl = deviceForm.href;\n }\n\n return result;\n}\n","import type { SpatialData, ButtonEvent } from \"./types.js\";\n\n/** Decode 24-byte binary spatial data datagram (WebTransport or raw binary) */\nexport function decodeBinaryFrame(buffer: ArrayBuffer | Uint8Array): SpatialData {\n const len = buffer instanceof ArrayBuffer ? buffer.byteLength : buffer.byteLength;\n if (len < 20) {\n throw new RangeError(`Spatial frame too short: expected ≥20 bytes, got ${len}`);\n }\n const ab = buffer instanceof ArrayBuffer ? buffer : buffer.buffer;\n const offset = buffer instanceof Uint8Array ? buffer.byteOffset : 0;\n const view = new DataView(ab, offset);\n return {\n translation: {\n x: view.getInt16(8, true),\n y: view.getInt16(10, true),\n z: view.getInt16(12, true),\n },\n rotation: {\n x: view.getInt16(14, true),\n y: view.getInt16(16, true),\n z: view.getInt16(18, true),\n },\n timestamp: view.getFloat64(0, true),\n };\n}\n\n/** Decode WebSocket binary frame (1-byte type prefix + 24-byte payload) */\nexport function decodeWsBinaryFrame(\n buffer: ArrayBuffer | Uint8Array,\n): { type: \"spatialData\"; data: SpatialData } | { type: \"buttonEvent\"; data: ButtonEvent } | null {\n const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);\n if (bytes.length < 1) return null;\n\n const typePrefix = bytes[0];\n if (typePrefix === 0x01 && bytes.length >= 25) {\n return { type: \"spatialData\", data: decodeBinaryFrame(bytes.subarray(1, 25)) };\n }\n if (typePrefix === 0x02) {\n const json = new TextDecoder().decode(bytes.subarray(1));\n return { type: \"buttonEvent\", data: JSON.parse(json) as ButtonEvent };\n }\n return null;\n}\n\n/** Decode length-prefixed JSON button events from a WebTransport stream chunk */\nexport function decodeButtonStream(\n buffer: Uint8Array<ArrayBufferLike>,\n): { events: ButtonEvent[]; remainder: Uint8Array<ArrayBufferLike> } {\n const events: ButtonEvent[] = [];\n let pos = 0;\n\n while (pos + 4 <= buffer.length) {\n const view = new DataView(buffer.buffer, buffer.byteOffset + pos);\n const len = view.getUint32(0, true);\n // Guard against absurd lengths\n if (len > 65536 || pos + 4 + len > buffer.length) break;\n const json = new TextDecoder().decode(buffer.subarray(pos + 4, pos + 4 + len));\n try {\n const event = JSON.parse(json);\n if (typeof event.button === \"number\" && typeof event.pressed === \"boolean\") {\n events.push(event as ButtonEvent);\n }\n } catch {\n // Skip malformed JSON frames\n }\n pos += 4 + len;\n }\n\n return { events, remainder: buffer.subarray(pos) };\n}\n","import type { Transport } from \"./transport.js\";\nimport type { SpatialData, ButtonEvent } from \"../types.js\";\nimport { decodeBinaryFrame, decodeButtonStream } from \"../decode.js\";\n\nexport class WebTransportAdapter implements Transport {\n readonly protocol = \"webtransport\" as const;\n\n onSpatialData: ((data: SpatialData) => void) | null = null;\n onButtonEvent: ((data: ButtonEvent) => void) | null = null;\n onClose: (() => void) | null = null;\n onError: ((error: Error) => void) | null = null;\n\n private transport: any = null;\n private url: string;\n private certHash?: string;\n\n constructor(url: string, certHash?: string) {\n this.url = url;\n this.certHash = certHash;\n }\n\n async connect(): Promise<void> {\n if (typeof globalThis.WebTransport === \"undefined\") {\n throw new Error(\"WebTransport is not available in this environment\");\n }\n\n const options: any = {};\n if (this.certHash) {\n options.serverCertificateHashes = [\n {\n algorithm: \"sha-256\",\n value: Uint8Array.from(atob(this.certHash), (c) => c.charCodeAt(0)),\n },\n ];\n }\n\n this.transport = new (globalThis as any).WebTransport(this.url, options);\n await this.transport.ready;\n\n this.readDatagrams();\n this.readStreams();\n\n this.transport.closed\n .then(() => this.onClose?.())\n .catch(() => this.onClose?.());\n }\n\n close(): void {\n try {\n this.transport?.close();\n } catch {}\n this.transport = null;\n }\n\n private async readDatagrams(): Promise<void> {\n const reader = this.transport.datagrams.readable.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n this.onSpatialData?.(decodeBinaryFrame(value));\n }\n } catch {\n // Transport closed\n }\n }\n\n private async readStreams(): Promise<void> {\n const reader = this.transport.incomingUnidirectionalStreams.getReader();\n try {\n while (true) {\n const { value: stream, done } = await reader.read();\n if (done) break;\n this.readButtonStream(stream);\n }\n } catch {\n // Transport closed\n }\n }\n\n private async readButtonStream(stream: any): Promise<void> {\n const reader = stream.getReader();\n let buffer: Uint8Array<ArrayBufferLike> = new Uint8Array(0);\n\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n\n const newBuf = new Uint8Array(buffer.length + value.length);\n newBuf.set(buffer);\n newBuf.set(value, buffer.length);\n\n const { events, remainder } = decodeButtonStream(newBuf);\n for (const event of events) {\n this.onButtonEvent?.(event);\n }\n buffer = remainder;\n }\n } catch {\n // Stream closed\n }\n }\n}\n","import type { Transport } from \"./transport.js\";\nimport type { SpatialData, ButtonEvent, DeviceInfo } from \"../types.js\";\nimport { decodeWsBinaryFrame } from \"../decode.js\";\n\nexport class WebSocketAdapter implements Transport {\n readonly protocol = \"websocket\" as const;\n\n onSpatialData: ((data: SpatialData) => void) | null = null;\n onButtonEvent: ((data: ButtonEvent) => void) | null = null;\n onDeviceStatus: ((event: \"connected\" | \"disconnected\", device: DeviceInfo) => void) | null = null;\n onClose: (() => void) | null = null;\n onError: ((error: Error) => void) | null = null;\n\n private ws: WebSocket | null = null;\n private url: string;\n private subprotocol: string;\n\n constructor(url: string, subprotocol: string = \"satmouse-json\") {\n this.url = url;\n this.subprotocol = subprotocol;\n }\n\n async connect(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n this.ws = new globalThis.WebSocket(this.url, this.subprotocol);\n if (this.subprotocol === \"satmouse-binary\") {\n this.ws.binaryType = \"arraybuffer\";\n }\n\n this.ws.onopen = () => resolve();\n\n this.ws.onerror = () => {\n reject(new Error(`WebSocket connection failed: ${this.url}`));\n };\n\n this.ws.onmessage = (event: MessageEvent) => {\n if (this.subprotocol === \"satmouse-binary\" && event.data instanceof ArrayBuffer) {\n const decoded = decodeWsBinaryFrame(event.data);\n if (decoded?.type === \"spatialData\") this.onSpatialData?.(decoded.data);\n else if (decoded?.type === \"buttonEvent\") this.onButtonEvent?.(decoded.data);\n } else if (typeof event.data === \"string\") {\n try {\n const msg = JSON.parse(event.data);\n if (msg.type === \"spatialData\") this.onSpatialData?.(msg.data);\n else if (msg.type === \"buttonEvent\") this.onButtonEvent?.(msg.data);\n else if (msg.type === \"deviceStatus\") {\n this.onDeviceStatus?.(msg.data.event, msg.data.device);\n }\n } catch {\n // Ignore malformed messages\n }\n }\n };\n\n this.ws.onclose = () => this.onClose?.();\n });\n }\n\n close(): void {\n try {\n this.ws?.close();\n } catch {}\n this.ws = null;\n }\n}\n","import { TypedEmitter } from \"./emitter.js\";\nimport { fetchThingDescription, resolveEndpoints } from \"./discovery.js\";\nimport { WebTransportAdapter } from \"./transports/webtransport.js\";\nimport { WebSocketAdapter } from \"./transports/websocket.js\";\nimport type { Transport } from \"./transports/transport.js\";\nimport type {\n ConnectOptions,\n ConnectionState,\n DeviceInfo,\n SatMouseEvents,\n TransportProtocol,\n} from \"./types.js\";\n\n/**\n * Build a satmouse:// connect URI from connection parameters.\n */\nexport function buildSatMouseUri(host = \"localhost\", wsPort = 18945, wtPort = 18946): string {\n return `satmouse://connect?host=${encodeURIComponent(host)}&wsPort=${wsPort}&wtPort=${wtPort}`;\n}\n\n/**\n * Parse a satmouse:// URI into connection parameters.\n *\n * Format: satmouse://connect?host=<ip>&wsPort=<port>&wtPort=<port>\n * All query params are optional. Defaults: host=localhost, wsPort=4444, wtPort=4443.\n */\nexport function parseSatMouseUri(uri: string): { tdUrl: string; wsUrl: string; wtUrl: string } {\n const url = new URL(uri);\n const host = url.searchParams.get(\"host\") ?? \"localhost\";\n const wsPort = url.searchParams.get(\"wsPort\") ?? \"18945\";\n const wtPort = url.searchParams.get(\"wtPort\") ?? \"18946\";\n return {\n tdUrl: `http://${host}:${wsPort}/td.json`,\n wsUrl: `ws://${host}:${wsPort}/spatial`,\n wtUrl: `https://${host}:${wtPort}`,\n };\n}\n\nconst DEFAULT_OPTIONS: Required<\n Pick<ConnectOptions, \"transports\" | \"reconnectDelay\" | \"maxRetries\" | \"wsSubprotocol\">\n> = {\n transports: [\"webtransport\", \"websocket\"],\n reconnectDelay: 2000,\n maxRetries: 3,\n wsSubprotocol: \"satmouse-json\",\n};\n\n/**\n * Core connection to a SatMouse bridge.\n *\n * Handles discovery (via WoT Thing Description), transport negotiation\n * (WebTransport with WebSocket fallback), event dispatch, and auto-reconnect.\n */\nexport class SatMouseConnection extends TypedEmitter<SatMouseEvents> {\n private options: ConnectOptions & typeof DEFAULT_OPTIONS;\n private transport: Transport | null = null;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private intentionalClose = false;\n private deviceInfoUrl: string | null = null;\n private retryCount = 0;\n\n private _state: ConnectionState = \"disconnected\";\n private _protocol: TransportProtocol = \"none\";\n\n get state(): ConnectionState {\n return this._state;\n }\n get protocol(): TransportProtocol {\n return this._protocol;\n }\n\n constructor(options?: ConnectOptions) {\n super();\n this.options = { ...DEFAULT_OPTIONS, ...options };\n }\n\n async connect(): Promise<void> {\n this.intentionalClose = false;\n this.setState(\"connecting\", \"none\");\n\n // Resolve endpoints — satmouse:// URI takes priority\n let wtUrl = this.options.wtUrl;\n let wsUrl = this.options.wsUrl;\n let certHash = this.options.certHash;\n\n if (this.options.uri) {\n const parsed = parseSatMouseUri(this.options.uri);\n wtUrl = wtUrl ?? parsed.wtUrl;\n wsUrl = wsUrl ?? parsed.wsUrl;\n this.options.tdUrl = this.options.tdUrl ?? parsed.tdUrl;\n }\n\n if (!wtUrl && !wsUrl) {\n const tdUrl = this.options.tdUrl;\n // Use 127.0.0.1 (not localhost) — Safari treats the loopback IP as a\n // \"Potentially Trustworthy Origin\" more reliably than the hostname.\n // Try HTTPS first (works from HTTPS pages), fall back to HTTP.\n const tdUrls = tdUrl\n ? [tdUrl]\n : [\n \"https://127.0.0.1:18947/td.json\",\n \"http://127.0.0.1:18945/td.json\",\n ];\n\n let resolved = false;\n for (const url of tdUrls) {\n try {\n const td = await fetchThingDescription(url);\n const endpoints = resolveEndpoints(td);\n wtUrl = endpoints.webtransport?.url;\n wsUrl = endpoints.websocket?.url;\n certHash = certHash ?? endpoints.webtransport?.certHash;\n this.deviceInfoUrl = endpoints.deviceInfoUrl ?? null;\n resolved = true;\n break;\n } catch {\n // Try next URL\n }\n }\n if (!resolved) {\n this.emit(\"error\", new Error(\"Failed to fetch Thing Description\"));\n wsUrl = \"ws://127.0.0.1:18945/spatial\";\n }\n }\n\n // Try transports in preference order\n for (const proto of this.options.transports) {\n if (proto === \"webtransport\" && wtUrl) {\n try {\n if (typeof globalThis.WebTransport === \"undefined\") continue;\n const adapter = new WebTransportAdapter(wtUrl, certHash);\n if (await this.tryTransport(adapter)) return;\n } catch {\n continue;\n }\n }\n if (proto === \"websocket\" && wsUrl) {\n try {\n const adapter = new WebSocketAdapter(wsUrl, this.options.wsSubprotocol);\n if (await this.tryTransport(adapter)) return;\n } catch {\n continue;\n }\n }\n }\n\n this.setState(\"disconnected\", \"none\");\n this.scheduleReconnect();\n }\n\n /** Reset retry count and reconnect. Use after \"failed\" state. */\n retry(): void {\n this.retryCount = 0;\n this.intentionalClose = false;\n this.connect();\n }\n\n disconnect(): void {\n this.intentionalClose = true;\n this.clearReconnect();\n this.transport?.close();\n this.transport = null;\n this.setState(\"disconnected\", \"none\");\n }\n\n async fetchDeviceInfo(): Promise<DeviceInfo[]> {\n if (!this.deviceInfoUrl) return [];\n const res = await globalThis.fetch(this.deviceInfoUrl);\n if (!res.ok) return [];\n const data = await res.json();\n return data.devices ?? [];\n }\n\n private async tryTransport(adapter: Transport): Promise<boolean> {\n adapter.onSpatialData = (data) => this.emit(\"spatialData\", data);\n adapter.onButtonEvent = (data) => this.emit(\"buttonEvent\", data);\n adapter.onError = (err) => this.emit(\"error\", err);\n\n if (\"onDeviceStatus\" in adapter) {\n (adapter as any).onDeviceStatus = (event: \"connected\" | \"disconnected\", device: DeviceInfo) => {\n this.emit(\"deviceStatus\", event, device);\n };\n }\n\n adapter.onClose = () => {\n this.transport = null;\n this.setState(\"disconnected\", \"none\");\n if (!this.intentionalClose) this.scheduleReconnect();\n };\n\n try {\n await adapter.connect();\n this.transport = adapter;\n this.retryCount = 0;\n this.setState(\"connected\", adapter.protocol);\n return true;\n } catch (err) {\n this.emit(\"error\", err instanceof Error ? err : new Error(String(err)));\n return false;\n }\n }\n\n private setState(state: ConnectionState, protocol: TransportProtocol): void {\n if (this._state === state && this._protocol === protocol) return;\n this._state = state;\n this._protocol = protocol;\n this.emit(\"stateChange\", state, protocol);\n }\n\n private scheduleReconnect(): void {\n if (this.options.reconnectDelay <= 0 || this.intentionalClose) return;\n\n this.retryCount++;\n console.log(`[SatMouse] Reconnect attempt ${this.retryCount}/${this.options.maxRetries}`);\n if (this.retryCount > this.options.maxRetries) {\n console.log(\"[SatMouse] Max retries exceeded, giving up\");\n this.setState(\"failed\", \"none\");\n return;\n }\n\n this.clearReconnect();\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.connect();\n }, this.options.reconnectDelay);\n }\n\n private clearReconnect(): void {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n }\n}\n","const SCHEME_URL = \"satmouse://launch\";\nconst PROJECT_URL = \"https://github.com/kelnishi/SatMouse/releases/latest\";\n\n/** Result of a URI scheme negotiation */\nexport interface NegotiateResult {\n ip: string;\n wsPort: number;\n wtPort: number;\n httpsPort: number;\n certHash?: string;\n challenge?: string;\n}\n\n/**\n * Trigger the satmouse://negotiate URI scheme for discovery when direct\n * HTTP/HTTPS fetch is blocked (Safari LNA).\n *\n * The bridge intercepts the URI, then redirects back to your origin with\n * connection details as query parameters. Your app handles the callback\n * route and passes the result to SatMouseConnection.\n *\n * @param origin - Your app's origin (e.g., \"https://kelcite.app\")\n * @param callbackPath - Path the bridge redirects to (default: \"/satmouse-handshake\")\n * @param challenge - Optional challenge token for verification\n */\nexport function negotiateViaSatMouse(origin: string, callbackPath = \"/satmouse-handshake\", challenge?: string): void {\n const params = new URLSearchParams({ origin, callback: callbackPath });\n if (challenge) params.set(\"challenge\", challenge);\n globalThis.location.href = `satmouse://negotiate?${params}`;\n}\n\n/**\n * Parse the negotiate callback URL parameters (called on your callback route).\n * Returns connection details or null if the params are missing.\n */\nexport function parseNegotiateCallback(searchParams: URLSearchParams): NegotiateResult | null {\n const ip = searchParams.get(\"ip\");\n const wsPort = searchParams.get(\"wsPort\");\n if (!ip || !wsPort) return null;\n return {\n ip,\n wsPort: parseInt(wsPort, 10),\n wtPort: parseInt(searchParams.get(\"wtPort\") ?? \"18946\", 10),\n httpsPort: parseInt(searchParams.get(\"httpsPort\") ?? \"18947\", 10),\n certHash: searchParams.get(\"certHash\") ?? undefined,\n challenge: searchParams.get(\"challenge\") ?? undefined,\n };\n}\n\nexport interface LaunchOptions {\n /** URL scheme to open. Default: \"satmouse://launch\" */\n schemeUrl?: string;\n /** Fallback URL if the app is not installed. Default: GitHub releases page */\n fallbackUrl?: string;\n /** Timeout in ms before assuming the app is not installed. Default: 2500 */\n timeout?: number;\n}\n\n/**\n * Attempt to launch SatMouse via the `satmouse://` URL scheme.\n *\n * If the app is installed and registered, the OS opens it. If not,\n * navigates to the fallback URL (project releases page) after a timeout.\n *\n * Returns true if the scheme likely opened, false if it fell back.\n */\nexport function launchSatMouse(options?: LaunchOptions): Promise<boolean> {\n const schemeUrl = options?.schemeUrl ?? SCHEME_URL;\n const timeout = options?.timeout ?? 2500;\n\n // Validate fallback URL — only allow http/https to prevent javascript: or data: injection\n let fallbackUrl = PROJECT_URL;\n if (options?.fallbackUrl) {\n try {\n const parsed = new URL(options.fallbackUrl);\n if (parsed.protocol === \"http:\" || parsed.protocol === \"https:\") {\n fallbackUrl = options.fallbackUrl;\n }\n } catch {}\n }\n\n return new Promise((resolve) => {\n // Track if we leave the page (scheme handler opened the app)\n let launched = false;\n\n const onBlur = () => {\n launched = true;\n };\n globalThis.addEventListener(\"blur\", onBlur);\n\n // Use a hidden iframe to trigger the scheme without navigating away\n const iframe = document.createElement(\"iframe\");\n iframe.style.display = \"none\";\n iframe.src = schemeUrl;\n document.body.appendChild(iframe);\n\n setTimeout(() => {\n globalThis.removeEventListener(\"blur\", onBlur);\n document.body.removeChild(iframe);\n\n if (launched || document.hidden) {\n resolve(true);\n } else {\n // App not installed — redirect to project page\n globalThis.location.href = fallbackUrl;\n resolve(false);\n }\n }, timeout);\n });\n}\n"]}
@@ -32,6 +32,33 @@ declare function decodeButtonStream(buffer: Uint8Array<ArrayBufferLike>): {
32
32
  remainder: Uint8Array<ArrayBufferLike>;
33
33
  };
34
34
 
35
+ /** Result of a URI scheme negotiation */
36
+ interface NegotiateResult {
37
+ ip: string;
38
+ wsPort: number;
39
+ wtPort: number;
40
+ httpsPort: number;
41
+ certHash?: string;
42
+ challenge?: string;
43
+ }
44
+ /**
45
+ * Trigger the satmouse://negotiate URI scheme for discovery when direct
46
+ * HTTP/HTTPS fetch is blocked (Safari LNA).
47
+ *
48
+ * The bridge intercepts the URI, then redirects back to your origin with
49
+ * connection details as query parameters. Your app handles the callback
50
+ * route and passes the result to SatMouseConnection.
51
+ *
52
+ * @param origin - Your app's origin (e.g., "https://kelcite.app")
53
+ * @param callbackPath - Path the bridge redirects to (default: "/satmouse-handshake")
54
+ * @param challenge - Optional challenge token for verification
55
+ */
56
+ declare function negotiateViaSatMouse(origin: string, callbackPath?: string, challenge?: string): void;
57
+ /**
58
+ * Parse the negotiate callback URL parameters (called on your callback route).
59
+ * Returns connection details or null if the params are missing.
60
+ */
61
+ declare function parseNegotiateCallback(searchParams: URLSearchParams): NegotiateResult | null;
35
62
  interface LaunchOptions {
36
63
  /** URL scheme to open. Default: "satmouse://launch" */
37
64
  schemeUrl?: string;
@@ -50,4 +77,4 @@ interface LaunchOptions {
50
77
  */
51
78
  declare function launchSatMouse(options?: LaunchOptions): Promise<boolean>;
52
79
 
53
- export { ButtonEvent, type LaunchOptions, SpatialData, ThingDescription, decodeBinaryFrame, decodeButtonStream, decodeWsBinaryFrame, fetchThingDescription, launchSatMouse, resolveEndpoints };
80
+ export { ButtonEvent, type LaunchOptions, type NegotiateResult, SpatialData, ThingDescription, decodeBinaryFrame, decodeButtonStream, decodeWsBinaryFrame, fetchThingDescription, launchSatMouse, negotiateViaSatMouse, parseNegotiateCallback, resolveEndpoints };
@@ -32,6 +32,33 @@ declare function decodeButtonStream(buffer: Uint8Array<ArrayBufferLike>): {
32
32
  remainder: Uint8Array<ArrayBufferLike>;
33
33
  };
34
34
 
35
+ /** Result of a URI scheme negotiation */
36
+ interface NegotiateResult {
37
+ ip: string;
38
+ wsPort: number;
39
+ wtPort: number;
40
+ httpsPort: number;
41
+ certHash?: string;
42
+ challenge?: string;
43
+ }
44
+ /**
45
+ * Trigger the satmouse://negotiate URI scheme for discovery when direct
46
+ * HTTP/HTTPS fetch is blocked (Safari LNA).
47
+ *
48
+ * The bridge intercepts the URI, then redirects back to your origin with
49
+ * connection details as query parameters. Your app handles the callback
50
+ * route and passes the result to SatMouseConnection.
51
+ *
52
+ * @param origin - Your app's origin (e.g., "https://kelcite.app")
53
+ * @param callbackPath - Path the bridge redirects to (default: "/satmouse-handshake")
54
+ * @param challenge - Optional challenge token for verification
55
+ */
56
+ declare function negotiateViaSatMouse(origin: string, callbackPath?: string, challenge?: string): void;
57
+ /**
58
+ * Parse the negotiate callback URL parameters (called on your callback route).
59
+ * Returns connection details or null if the params are missing.
60
+ */
61
+ declare function parseNegotiateCallback(searchParams: URLSearchParams): NegotiateResult | null;
35
62
  interface LaunchOptions {
36
63
  /** URL scheme to open. Default: "satmouse://launch" */
37
64
  schemeUrl?: string;
@@ -50,4 +77,4 @@ interface LaunchOptions {
50
77
  */
51
78
  declare function launchSatMouse(options?: LaunchOptions): Promise<boolean>;
52
79
 
53
- export { ButtonEvent, type LaunchOptions, SpatialData, ThingDescription, decodeBinaryFrame, decodeButtonStream, decodeWsBinaryFrame, fetchThingDescription, launchSatMouse, resolveEndpoints };
80
+ export { ButtonEvent, type LaunchOptions, type NegotiateResult, SpatialData, ThingDescription, decodeBinaryFrame, decodeButtonStream, decodeWsBinaryFrame, fetchThingDescription, launchSatMouse, negotiateViaSatMouse, parseNegotiateCallback, resolveEndpoints };
@@ -275,7 +275,10 @@ var SatMouseConnection = class extends TypedEmitter {
275
275
  }
276
276
  if (!wtUrl && !wsUrl) {
277
277
  const tdUrl = this.options.tdUrl;
278
- const tdUrls = tdUrl ? [tdUrl] : ["https://localhost:18947/td.json", "http://localhost:18945/td.json"];
278
+ const tdUrls = tdUrl ? [tdUrl] : [
279
+ "https://127.0.0.1:18947/td.json",
280
+ "http://127.0.0.1:18945/td.json"
281
+ ];
279
282
  let resolved = false;
280
283
  for (const url of tdUrls) {
281
284
  try {
@@ -292,7 +295,7 @@ var SatMouseConnection = class extends TypedEmitter {
292
295
  }
293
296
  if (!resolved) {
294
297
  this.emit("error", new Error("Failed to fetch Thing Description"));
295
- wsUrl = "ws://localhost:18945/spatial";
298
+ wsUrl = "ws://127.0.0.1:18945/spatial";
296
299
  }
297
300
  }
298
301
  for (const proto of this.options.transports) {
@@ -394,6 +397,24 @@ var SatMouseConnection = class extends TypedEmitter {
394
397
  // src/core/launch.ts
395
398
  var SCHEME_URL = "satmouse://launch";
396
399
  var PROJECT_URL = "https://github.com/kelnishi/SatMouse/releases/latest";
400
+ function negotiateViaSatMouse(origin, callbackPath = "/satmouse-handshake", challenge) {
401
+ const params = new URLSearchParams({ origin, callback: callbackPath });
402
+ if (challenge) params.set("challenge", challenge);
403
+ globalThis.location.href = `satmouse://negotiate?${params}`;
404
+ }
405
+ function parseNegotiateCallback(searchParams) {
406
+ const ip = searchParams.get("ip");
407
+ const wsPort = searchParams.get("wsPort");
408
+ if (!ip || !wsPort) return null;
409
+ return {
410
+ ip,
411
+ wsPort: parseInt(wsPort, 10),
412
+ wtPort: parseInt(searchParams.get("wtPort") ?? "18946", 10),
413
+ httpsPort: parseInt(searchParams.get("httpsPort") ?? "18947", 10),
414
+ certHash: searchParams.get("certHash") ?? void 0,
415
+ challenge: searchParams.get("challenge") ?? void 0
416
+ };
417
+ }
397
418
  function launchSatMouse(options) {
398
419
  const schemeUrl = options?.schemeUrl ?? SCHEME_URL;
399
420
  const timeout = options?.timeout ?? 2500;
@@ -430,6 +451,6 @@ function launchSatMouse(options) {
430
451
  });
431
452
  }
432
453
 
433
- export { SatMouseConnection, buildSatMouseUri, decodeBinaryFrame, decodeButtonStream, decodeWsBinaryFrame, fetchThingDescription, launchSatMouse, parseSatMouseUri, resolveEndpoints };
454
+ export { SatMouseConnection, buildSatMouseUri, decodeBinaryFrame, decodeButtonStream, decodeWsBinaryFrame, fetchThingDescription, launchSatMouse, negotiateViaSatMouse, parseNegotiateCallback, parseSatMouseUri, resolveEndpoints };
434
455
  //# sourceMappingURL=index.js.map
435
456
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/discovery.ts","../../src/core/decode.ts","../../src/core/transports/webtransport.ts","../../src/core/transports/websocket.ts","../../src/core/connection.ts","../../src/core/launch.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { ThingDescription } from \"./types.js\";\n\nexport interface ResolvedEndpoints {\n webtransport?: { url: string; certHash?: string };\n websocket?: { url: string };\n deviceInfoUrl?: string;\n}\n\n/** Fetch the WoT Thing Description from the bridge */\nexport async function fetchThingDescription(tdUrl: string): Promise<ThingDescription> {\n const res = await globalThis.fetch(tdUrl);\n if (!res.ok) throw new Error(`Failed to fetch TD: HTTP ${res.status}`);\n return res.json() as Promise<ThingDescription>;\n}\n\n/** Extract WebTransport, WebSocket, and device info endpoints from a Thing Description */\nexport function resolveEndpoints(td: ThingDescription): ResolvedEndpoints {\n const result: ResolvedEndpoints = {};\n\n const spatialForms = td.events?.spatialData?.forms ?? [];\n\n const wtForm = spatialForms.find((f) => f.subprotocol === \"webtransport\");\n if (wtForm) {\n result.webtransport = {\n url: wtForm.href,\n certHash: td[\"satmouse:certHash\"],\n };\n }\n\n const wsForm = spatialForms.find((f) => f.subprotocol === \"websocket\");\n if (wsForm) {\n result.websocket = { url: wsForm.href };\n }\n\n const deviceForm = td.properties?.deviceInfo?.forms?.[0];\n if (deviceForm) {\n result.deviceInfoUrl = deviceForm.href;\n }\n\n return result;\n}\n","import type { SpatialData, ButtonEvent } from \"./types.js\";\n\n/** Decode 24-byte binary spatial data datagram (WebTransport or raw binary) */\nexport function decodeBinaryFrame(buffer: ArrayBuffer | Uint8Array): SpatialData {\n const len = buffer instanceof ArrayBuffer ? buffer.byteLength : buffer.byteLength;\n if (len < 20) {\n throw new RangeError(`Spatial frame too short: expected ≥20 bytes, got ${len}`);\n }\n const ab = buffer instanceof ArrayBuffer ? buffer : buffer.buffer;\n const offset = buffer instanceof Uint8Array ? buffer.byteOffset : 0;\n const view = new DataView(ab, offset);\n return {\n translation: {\n x: view.getInt16(8, true),\n y: view.getInt16(10, true),\n z: view.getInt16(12, true),\n },\n rotation: {\n x: view.getInt16(14, true),\n y: view.getInt16(16, true),\n z: view.getInt16(18, true),\n },\n timestamp: view.getFloat64(0, true),\n };\n}\n\n/** Decode WebSocket binary frame (1-byte type prefix + 24-byte payload) */\nexport function decodeWsBinaryFrame(\n buffer: ArrayBuffer | Uint8Array,\n): { type: \"spatialData\"; data: SpatialData } | { type: \"buttonEvent\"; data: ButtonEvent } | null {\n const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);\n if (bytes.length < 1) return null;\n\n const typePrefix = bytes[0];\n if (typePrefix === 0x01 && bytes.length >= 25) {\n return { type: \"spatialData\", data: decodeBinaryFrame(bytes.subarray(1, 25)) };\n }\n if (typePrefix === 0x02) {\n const json = new TextDecoder().decode(bytes.subarray(1));\n return { type: \"buttonEvent\", data: JSON.parse(json) as ButtonEvent };\n }\n return null;\n}\n\n/** Decode length-prefixed JSON button events from a WebTransport stream chunk */\nexport function decodeButtonStream(\n buffer: Uint8Array<ArrayBufferLike>,\n): { events: ButtonEvent[]; remainder: Uint8Array<ArrayBufferLike> } {\n const events: ButtonEvent[] = [];\n let pos = 0;\n\n while (pos + 4 <= buffer.length) {\n const view = new DataView(buffer.buffer, buffer.byteOffset + pos);\n const len = view.getUint32(0, true);\n // Guard against absurd lengths\n if (len > 65536 || pos + 4 + len > buffer.length) break;\n const json = new TextDecoder().decode(buffer.subarray(pos + 4, pos + 4 + len));\n try {\n const event = JSON.parse(json);\n if (typeof event.button === \"number\" && typeof event.pressed === \"boolean\") {\n events.push(event as ButtonEvent);\n }\n } catch {\n // Skip malformed JSON frames\n }\n pos += 4 + len;\n }\n\n return { events, remainder: buffer.subarray(pos) };\n}\n","import type { Transport } from \"./transport.js\";\nimport type { SpatialData, ButtonEvent } from \"../types.js\";\nimport { decodeBinaryFrame, decodeButtonStream } from \"../decode.js\";\n\nexport class WebTransportAdapter implements Transport {\n readonly protocol = \"webtransport\" as const;\n\n onSpatialData: ((data: SpatialData) => void) | null = null;\n onButtonEvent: ((data: ButtonEvent) => void) | null = null;\n onClose: (() => void) | null = null;\n onError: ((error: Error) => void) | null = null;\n\n private transport: any = null;\n private url: string;\n private certHash?: string;\n\n constructor(url: string, certHash?: string) {\n this.url = url;\n this.certHash = certHash;\n }\n\n async connect(): Promise<void> {\n if (typeof globalThis.WebTransport === \"undefined\") {\n throw new Error(\"WebTransport is not available in this environment\");\n }\n\n const options: any = {};\n if (this.certHash) {\n options.serverCertificateHashes = [\n {\n algorithm: \"sha-256\",\n value: Uint8Array.from(atob(this.certHash), (c) => c.charCodeAt(0)),\n },\n ];\n }\n\n this.transport = new (globalThis as any).WebTransport(this.url, options);\n await this.transport.ready;\n\n this.readDatagrams();\n this.readStreams();\n\n this.transport.closed\n .then(() => this.onClose?.())\n .catch(() => this.onClose?.());\n }\n\n close(): void {\n try {\n this.transport?.close();\n } catch {}\n this.transport = null;\n }\n\n private async readDatagrams(): Promise<void> {\n const reader = this.transport.datagrams.readable.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n this.onSpatialData?.(decodeBinaryFrame(value));\n }\n } catch {\n // Transport closed\n }\n }\n\n private async readStreams(): Promise<void> {\n const reader = this.transport.incomingUnidirectionalStreams.getReader();\n try {\n while (true) {\n const { value: stream, done } = await reader.read();\n if (done) break;\n this.readButtonStream(stream);\n }\n } catch {\n // Transport closed\n }\n }\n\n private async readButtonStream(stream: any): Promise<void> {\n const reader = stream.getReader();\n let buffer: Uint8Array<ArrayBufferLike> = new Uint8Array(0);\n\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n\n const newBuf = new Uint8Array(buffer.length + value.length);\n newBuf.set(buffer);\n newBuf.set(value, buffer.length);\n\n const { events, remainder } = decodeButtonStream(newBuf);\n for (const event of events) {\n this.onButtonEvent?.(event);\n }\n buffer = remainder;\n }\n } catch {\n // Stream closed\n }\n }\n}\n","import type { Transport } from \"./transport.js\";\nimport type { SpatialData, ButtonEvent, DeviceInfo } from \"../types.js\";\nimport { decodeWsBinaryFrame } from \"../decode.js\";\n\nexport class WebSocketAdapter implements Transport {\n readonly protocol = \"websocket\" as const;\n\n onSpatialData: ((data: SpatialData) => void) | null = null;\n onButtonEvent: ((data: ButtonEvent) => void) | null = null;\n onDeviceStatus: ((event: \"connected\" | \"disconnected\", device: DeviceInfo) => void) | null = null;\n onClose: (() => void) | null = null;\n onError: ((error: Error) => void) | null = null;\n\n private ws: WebSocket | null = null;\n private url: string;\n private subprotocol: string;\n\n constructor(url: string, subprotocol: string = \"satmouse-json\") {\n this.url = url;\n this.subprotocol = subprotocol;\n }\n\n async connect(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n this.ws = new globalThis.WebSocket(this.url, this.subprotocol);\n if (this.subprotocol === \"satmouse-binary\") {\n this.ws.binaryType = \"arraybuffer\";\n }\n\n this.ws.onopen = () => resolve();\n\n this.ws.onerror = () => {\n reject(new Error(`WebSocket connection failed: ${this.url}`));\n };\n\n this.ws.onmessage = (event: MessageEvent) => {\n if (this.subprotocol === \"satmouse-binary\" && event.data instanceof ArrayBuffer) {\n const decoded = decodeWsBinaryFrame(event.data);\n if (decoded?.type === \"spatialData\") this.onSpatialData?.(decoded.data);\n else if (decoded?.type === \"buttonEvent\") this.onButtonEvent?.(decoded.data);\n } else if (typeof event.data === \"string\") {\n try {\n const msg = JSON.parse(event.data);\n if (msg.type === \"spatialData\") this.onSpatialData?.(msg.data);\n else if (msg.type === \"buttonEvent\") this.onButtonEvent?.(msg.data);\n else if (msg.type === \"deviceStatus\") {\n this.onDeviceStatus?.(msg.data.event, msg.data.device);\n }\n } catch {\n // Ignore malformed messages\n }\n }\n };\n\n this.ws.onclose = () => this.onClose?.();\n });\n }\n\n close(): void {\n try {\n this.ws?.close();\n } catch {}\n this.ws = null;\n }\n}\n","import { TypedEmitter } from \"./emitter.js\";\nimport { fetchThingDescription, resolveEndpoints } from \"./discovery.js\";\nimport { WebTransportAdapter } from \"./transports/webtransport.js\";\nimport { WebSocketAdapter } from \"./transports/websocket.js\";\nimport type { Transport } from \"./transports/transport.js\";\nimport type {\n ConnectOptions,\n ConnectionState,\n DeviceInfo,\n SatMouseEvents,\n TransportProtocol,\n} from \"./types.js\";\n\n/**\n * Build a satmouse:// connect URI from connection parameters.\n */\nexport function buildSatMouseUri(host = \"localhost\", wsPort = 18945, wtPort = 18946): string {\n return `satmouse://connect?host=${encodeURIComponent(host)}&wsPort=${wsPort}&wtPort=${wtPort}`;\n}\n\n/**\n * Parse a satmouse:// URI into connection parameters.\n *\n * Format: satmouse://connect?host=<ip>&wsPort=<port>&wtPort=<port>\n * All query params are optional. Defaults: host=localhost, wsPort=4444, wtPort=4443.\n */\nexport function parseSatMouseUri(uri: string): { tdUrl: string; wsUrl: string; wtUrl: string } {\n const url = new URL(uri);\n const host = url.searchParams.get(\"host\") ?? \"localhost\";\n const wsPort = url.searchParams.get(\"wsPort\") ?? \"18945\";\n const wtPort = url.searchParams.get(\"wtPort\") ?? \"18946\";\n return {\n tdUrl: `http://${host}:${wsPort}/td.json`,\n wsUrl: `ws://${host}:${wsPort}/spatial`,\n wtUrl: `https://${host}:${wtPort}`,\n };\n}\n\nconst DEFAULT_OPTIONS: Required<\n Pick<ConnectOptions, \"transports\" | \"reconnectDelay\" | \"maxRetries\" | \"wsSubprotocol\">\n> = {\n transports: [\"webtransport\", \"websocket\"],\n reconnectDelay: 2000,\n maxRetries: 3,\n wsSubprotocol: \"satmouse-json\",\n};\n\n/**\n * Core connection to a SatMouse bridge.\n *\n * Handles discovery (via WoT Thing Description), transport negotiation\n * (WebTransport with WebSocket fallback), event dispatch, and auto-reconnect.\n */\nexport class SatMouseConnection extends TypedEmitter<SatMouseEvents> {\n private options: ConnectOptions & typeof DEFAULT_OPTIONS;\n private transport: Transport | null = null;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private intentionalClose = false;\n private deviceInfoUrl: string | null = null;\n private retryCount = 0;\n\n private _state: ConnectionState = \"disconnected\";\n private _protocol: TransportProtocol = \"none\";\n\n get state(): ConnectionState {\n return this._state;\n }\n get protocol(): TransportProtocol {\n return this._protocol;\n }\n\n constructor(options?: ConnectOptions) {\n super();\n this.options = { ...DEFAULT_OPTIONS, ...options };\n }\n\n async connect(): Promise<void> {\n this.intentionalClose = false;\n this.setState(\"connecting\", \"none\");\n\n // Resolve endpoints — satmouse:// URI takes priority\n let wtUrl = this.options.wtUrl;\n let wsUrl = this.options.wsUrl;\n let certHash = this.options.certHash;\n\n if (this.options.uri) {\n const parsed = parseSatMouseUri(this.options.uri);\n wtUrl = wtUrl ?? parsed.wtUrl;\n wsUrl = wsUrl ?? parsed.wsUrl;\n this.options.tdUrl = this.options.tdUrl ?? parsed.tdUrl;\n }\n\n if (!wtUrl && !wsUrl) {\n const tdUrl = this.options.tdUrl;\n // Try HTTPS first (works from HTTPS pages), fall back to HTTP\n const tdUrls = tdUrl\n ? [tdUrl]\n : [\"https://localhost:18947/td.json\", \"http://localhost:18945/td.json\"];\n\n let resolved = false;\n for (const url of tdUrls) {\n try {\n const td = await fetchThingDescription(url);\n const endpoints = resolveEndpoints(td);\n wtUrl = endpoints.webtransport?.url;\n wsUrl = endpoints.websocket?.url;\n certHash = certHash ?? endpoints.webtransport?.certHash;\n this.deviceInfoUrl = endpoints.deviceInfoUrl ?? null;\n resolved = true;\n break;\n } catch {\n // Try next URL\n }\n }\n if (!resolved) {\n this.emit(\"error\", new Error(\"Failed to fetch Thing Description\"));\n wsUrl = \"ws://localhost:18945/spatial\";\n }\n }\n\n // Try transports in preference order\n for (const proto of this.options.transports) {\n if (proto === \"webtransport\" && wtUrl) {\n try {\n if (typeof globalThis.WebTransport === \"undefined\") continue;\n const adapter = new WebTransportAdapter(wtUrl, certHash);\n if (await this.tryTransport(adapter)) return;\n } catch {\n continue;\n }\n }\n if (proto === \"websocket\" && wsUrl) {\n try {\n const adapter = new WebSocketAdapter(wsUrl, this.options.wsSubprotocol);\n if (await this.tryTransport(adapter)) return;\n } catch {\n continue;\n }\n }\n }\n\n this.setState(\"disconnected\", \"none\");\n this.scheduleReconnect();\n }\n\n /** Reset retry count and reconnect. Use after \"failed\" state. */\n retry(): void {\n this.retryCount = 0;\n this.intentionalClose = false;\n this.connect();\n }\n\n disconnect(): void {\n this.intentionalClose = true;\n this.clearReconnect();\n this.transport?.close();\n this.transport = null;\n this.setState(\"disconnected\", \"none\");\n }\n\n async fetchDeviceInfo(): Promise<DeviceInfo[]> {\n if (!this.deviceInfoUrl) return [];\n const res = await globalThis.fetch(this.deviceInfoUrl);\n if (!res.ok) return [];\n const data = await res.json();\n return data.devices ?? [];\n }\n\n private async tryTransport(adapter: Transport): Promise<boolean> {\n adapter.onSpatialData = (data) => this.emit(\"spatialData\", data);\n adapter.onButtonEvent = (data) => this.emit(\"buttonEvent\", data);\n adapter.onError = (err) => this.emit(\"error\", err);\n\n if (\"onDeviceStatus\" in adapter) {\n (adapter as any).onDeviceStatus = (event: \"connected\" | \"disconnected\", device: DeviceInfo) => {\n this.emit(\"deviceStatus\", event, device);\n };\n }\n\n adapter.onClose = () => {\n this.transport = null;\n this.setState(\"disconnected\", \"none\");\n if (!this.intentionalClose) this.scheduleReconnect();\n };\n\n try {\n await adapter.connect();\n this.transport = adapter;\n this.retryCount = 0;\n this.setState(\"connected\", adapter.protocol);\n return true;\n } catch (err) {\n this.emit(\"error\", err instanceof Error ? err : new Error(String(err)));\n return false;\n }\n }\n\n private setState(state: ConnectionState, protocol: TransportProtocol): void {\n if (this._state === state && this._protocol === protocol) return;\n this._state = state;\n this._protocol = protocol;\n this.emit(\"stateChange\", state, protocol);\n }\n\n private scheduleReconnect(): void {\n if (this.options.reconnectDelay <= 0 || this.intentionalClose) return;\n\n this.retryCount++;\n console.log(`[SatMouse] Reconnect attempt ${this.retryCount}/${this.options.maxRetries}`);\n if (this.retryCount > this.options.maxRetries) {\n console.log(\"[SatMouse] Max retries exceeded, giving up\");\n this.setState(\"failed\", \"none\");\n return;\n }\n\n this.clearReconnect();\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.connect();\n }, this.options.reconnectDelay);\n }\n\n private clearReconnect(): void {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n }\n}\n","const SCHEME_URL = \"satmouse://launch\";\nconst PROJECT_URL = \"https://github.com/kelnishi/SatMouse/releases/latest\";\n\nexport interface LaunchOptions {\n /** URL scheme to open. Default: \"satmouse://launch\" */\n schemeUrl?: string;\n /** Fallback URL if the app is not installed. Default: GitHub releases page */\n fallbackUrl?: string;\n /** Timeout in ms before assuming the app is not installed. Default: 2500 */\n timeout?: number;\n}\n\n/**\n * Attempt to launch SatMouse via the `satmouse://` URL scheme.\n *\n * If the app is installed and registered, the OS opens it. If not,\n * navigates to the fallback URL (project releases page) after a timeout.\n *\n * Returns true if the scheme likely opened, false if it fell back.\n */\nexport function launchSatMouse(options?: LaunchOptions): Promise<boolean> {\n const schemeUrl = options?.schemeUrl ?? SCHEME_URL;\n const timeout = options?.timeout ?? 2500;\n\n // Validate fallback URL — only allow http/https to prevent javascript: or data: injection\n let fallbackUrl = PROJECT_URL;\n if (options?.fallbackUrl) {\n try {\n const parsed = new URL(options.fallbackUrl);\n if (parsed.protocol === \"http:\" || parsed.protocol === \"https:\") {\n fallbackUrl = options.fallbackUrl;\n }\n } catch {}\n }\n\n return new Promise((resolve) => {\n // Track if we leave the page (scheme handler opened the app)\n let launched = false;\n\n const onBlur = () => {\n launched = true;\n };\n globalThis.addEventListener(\"blur\", onBlur);\n\n // Use a hidden iframe to trigger the scheme without navigating away\n const iframe = document.createElement(\"iframe\");\n iframe.style.display = \"none\";\n iframe.src = schemeUrl;\n document.body.appendChild(iframe);\n\n setTimeout(() => {\n globalThis.removeEventListener(\"blur\", onBlur);\n document.body.removeChild(iframe);\n\n if (launched || document.hidden) {\n resolve(true);\n } else {\n // App not installed — redirect to project page\n globalThis.location.href = fallbackUrl;\n resolve(false);\n }\n }, timeout);\n });\n}\n"]}
1
+ {"version":3,"sources":["../../src/core/discovery.ts","../../src/core/decode.ts","../../src/core/transports/webtransport.ts","../../src/core/transports/websocket.ts","../../src/core/connection.ts","../../src/core/launch.ts"],"names":[],"mappings":";;;;AASA,eAAsB,sBAAsB,KAAA,EAA0C;AACpF,EAAA,MAAM,GAAA,GAAM,MAAM,UAAA,CAAW,KAAA,CAAM,KAAK,CAAA;AACxC,EAAA,IAAI,CAAC,IAAI,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,yBAAA,EAA4B,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AACrE,EAAA,OAAO,IAAI,IAAA,EAAK;AAClB;AAGO,SAAS,iBAAiB,EAAA,EAAyC;AACxE,EAAA,MAAM,SAA4B,EAAC;AAEnC,EAAA,MAAM,YAAA,GAAe,EAAA,CAAG,MAAA,EAAQ,WAAA,EAAa,SAAS,EAAC;AAEvD,EAAA,MAAM,SAAS,YAAA,CAAa,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,gBAAgB,cAAc,CAAA;AACxE,EAAA,IAAI,MAAA,EAAQ;AACV,IAAA,MAAA,CAAO,YAAA,GAAe;AAAA,MACpB,KAAK,MAAA,CAAO,IAAA;AAAA,MACZ,QAAA,EAAU,GAAG,mBAAmB;AAAA,KAClC;AAAA,EACF;AAEA,EAAA,MAAM,SAAS,YAAA,CAAa,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,gBAAgB,WAAW,CAAA;AACrE,EAAA,IAAI,MAAA,EAAQ;AACV,IAAA,MAAA,CAAO,SAAA,GAAY,EAAE,GAAA,EAAK,MAAA,CAAO,IAAA,EAAK;AAAA,EACxC;AAEA,EAAA,MAAM,UAAA,GAAa,EAAA,CAAG,UAAA,EAAY,UAAA,EAAY,QAAQ,CAAC,CAAA;AACvD,EAAA,IAAI,UAAA,EAAY;AACd,IAAA,MAAA,CAAO,gBAAgB,UAAA,CAAW,IAAA;AAAA,EACpC;AAEA,EAAA,OAAO,MAAA;AACT;;;ACrCO,SAAS,kBAAkB,MAAA,EAA+C;AAC/E,EAAA,MAAM,GAAA,GAAM,MAAA,YAAkB,WAAA,GAAc,MAAA,CAAO,aAAa,MAAA,CAAO,UAAA;AACvE,EAAA,IAAI,MAAM,EAAA,EAAI;AACZ,IAAA,MAAM,IAAI,UAAA,CAAW,CAAA,sDAAA,EAAoD,GAAG,CAAA,CAAE,CAAA;AAAA,EAChF;AACA,EAAA,MAAM,EAAA,GAAK,MAAA,YAAkB,WAAA,GAAc,MAAA,GAAS,MAAA,CAAO,MAAA;AAC3D,EAAA,MAAM,MAAA,GAAS,MAAA,YAAkB,UAAA,GAAa,MAAA,CAAO,UAAA,GAAa,CAAA;AAClE,EAAA,MAAM,IAAA,GAAO,IAAI,QAAA,CAAS,EAAA,EAAI,MAAM,CAAA;AACpC,EAAA,OAAO;AAAA,IACL,WAAA,EAAa;AAAA,MACX,CAAA,EAAG,IAAA,CAAK,QAAA,CAAS,CAAA,EAAG,IAAI,CAAA;AAAA,MACxB,CAAA,EAAG,IAAA,CAAK,QAAA,CAAS,EAAA,EAAI,IAAI,CAAA;AAAA,MACzB,CAAA,EAAG,IAAA,CAAK,QAAA,CAAS,EAAA,EAAI,IAAI;AAAA,KAC3B;AAAA,IACA,QAAA,EAAU;AAAA,MACR,CAAA,EAAG,IAAA,CAAK,QAAA,CAAS,EAAA,EAAI,IAAI,CAAA;AAAA,MACzB,CAAA,EAAG,IAAA,CAAK,QAAA,CAAS,EAAA,EAAI,IAAI,CAAA;AAAA,MACzB,CAAA,EAAG,IAAA,CAAK,QAAA,CAAS,EAAA,EAAI,IAAI;AAAA,KAC3B;AAAA,IACA,SAAA,EAAW,IAAA,CAAK,UAAA,CAAW,CAAA,EAAG,IAAI;AAAA,GACpC;AACF;AAGO,SAAS,oBACd,MAAA,EACgG;AAChG,EAAA,MAAM,QAAQ,MAAA,YAAkB,UAAA,GAAa,MAAA,GAAS,IAAI,WAAW,MAAM,CAAA;AAC3E,EAAA,IAAI,KAAA,CAAM,MAAA,GAAS,CAAA,EAAG,OAAO,IAAA;AAE7B,EAAA,MAAM,UAAA,GAAa,MAAM,CAAC,CAAA;AAC1B,EAAA,IAAI,UAAA,KAAe,CAAA,IAAQ,KAAA,CAAM,MAAA,IAAU,EAAA,EAAI;AAC7C,IAAA,OAAO,EAAE,IAAA,EAAM,aAAA,EAAe,IAAA,EAAM,iBAAA,CAAkB,MAAM,QAAA,CAAS,CAAA,EAAG,EAAE,CAAC,CAAA,EAAE;AAAA,EAC/E;AACA,EAAA,IAAI,eAAe,CAAA,EAAM;AACvB,IAAA,MAAM,IAAA,GAAO,IAAI,WAAA,EAAY,CAAE,OAAO,KAAA,CAAM,QAAA,CAAS,CAAC,CAAC,CAAA;AACvD,IAAA,OAAO,EAAE,IAAA,EAAM,aAAA,EAAe,MAAM,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA,EAAiB;AAAA,EACtE;AACA,EAAA,OAAO,IAAA;AACT;AAGO,SAAS,mBACd,MAAA,EACmE;AACnE,EAAA,MAAM,SAAwB,EAAC;AAC/B,EAAA,IAAI,GAAA,GAAM,CAAA;AAEV,EAAA,OAAO,GAAA,GAAM,CAAA,IAAK,MAAA,CAAO,MAAA,EAAQ;AAC/B,IAAA,MAAM,OAAO,IAAI,QAAA,CAAS,OAAO,MAAA,EAAQ,MAAA,CAAO,aAAa,GAAG,CAAA;AAChE,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,SAAA,CAAU,CAAA,EAAG,IAAI,CAAA;AAElC,IAAA,IAAI,MAAM,KAAA,IAAS,GAAA,GAAM,CAAA,GAAI,GAAA,GAAM,OAAO,MAAA,EAAQ;AAClD,IAAA,MAAM,IAAA,GAAO,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,MAAA,CAAO,QAAA,CAAS,GAAA,GAAM,CAAA,EAAG,GAAA,GAAM,CAAA,GAAI,GAAG,CAAC,CAAA;AAC7E,IAAA,IAAI;AACF,MAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAC7B,MAAA,IAAI,OAAO,KAAA,CAAM,MAAA,KAAW,YAAY,OAAO,KAAA,CAAM,YAAY,SAAA,EAAW;AAC1E,QAAA,MAAA,CAAO,KAAK,KAAoB,CAAA;AAAA,MAClC;AAAA,IACF,CAAA,CAAA,MAAQ;AAAA,IAER;AACA,IAAA,GAAA,IAAO,CAAA,GAAI,GAAA;AAAA,EACb;AAEA,EAAA,OAAO,EAAE,MAAA,EAAQ,SAAA,EAAW,MAAA,CAAO,QAAA,CAAS,GAAG,CAAA,EAAE;AACnD;;;ACjEO,IAAM,sBAAN,MAA+C;AAAA,EAC3C,QAAA,GAAW,cAAA;AAAA,EAEpB,aAAA,GAAsD,IAAA;AAAA,EACtD,aAAA,GAAsD,IAAA;AAAA,EACtD,OAAA,GAA+B,IAAA;AAAA,EAC/B,OAAA,GAA2C,IAAA;AAAA,EAEnC,SAAA,GAAiB,IAAA;AAAA,EACjB,GAAA;AAAA,EACA,QAAA;AAAA,EAER,WAAA,CAAY,KAAa,QAAA,EAAmB;AAC1C,IAAA,IAAA,CAAK,GAAA,GAAM,GAAA;AACX,IAAA,IAAA,CAAK,QAAA,GAAW,QAAA;AAAA,EAClB;AAAA,EAEA,MAAM,OAAA,GAAyB;AAC7B,IAAA,IAAI,OAAO,UAAA,CAAW,YAAA,KAAiB,WAAA,EAAa;AAClD,MAAA,MAAM,IAAI,MAAM,mDAAmD,CAAA;AAAA,IACrE;AAEA,IAAA,MAAM,UAAe,EAAC;AACtB,IAAA,IAAI,KAAK,QAAA,EAAU;AACjB,MAAA,OAAA,CAAQ,uBAAA,GAA0B;AAAA,QAChC;AAAA,UACE,SAAA,EAAW,SAAA;AAAA,UACX,KAAA,EAAO,UAAA,CAAW,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,QAAQ,CAAA,EAAG,CAAC,CAAA,KAAM,CAAA,CAAE,UAAA,CAAW,CAAC,CAAC;AAAA;AACpE,OACF;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,YAAY,IAAK,UAAA,CAAmB,YAAA,CAAa,IAAA,CAAK,KAAK,OAAO,CAAA;AACvE,IAAA,MAAM,KAAK,SAAA,CAAU,KAAA;AAErB,IAAA,IAAA,CAAK,aAAA,EAAc;AACnB,IAAA,IAAA,CAAK,WAAA,EAAY;AAEjB,IAAA,IAAA,CAAK,SAAA,CAAU,MAAA,CACZ,IAAA,CAAK,MAAM,IAAA,CAAK,OAAA,IAAW,CAAA,CAC3B,KAAA,CAAM,MAAM,IAAA,CAAK,OAAA,IAAW,CAAA;AAAA,EACjC;AAAA,EAEA,KAAA,GAAc;AACZ,IAAA,IAAI;AACF,MAAA,IAAA,CAAK,WAAW,KAAA,EAAM;AAAA,IACxB,CAAA,CAAA,MAAQ;AAAA,IAAC;AACT,IAAA,IAAA,CAAK,SAAA,GAAY,IAAA;AAAA,EACnB;AAAA,EAEA,MAAc,aAAA,GAA+B;AAC3C,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,SAAA,CAAU,SAAA,CAAU,SAAS,SAAA,EAAU;AAC3D,IAAA,IAAI;AACF,MAAA,OAAO,IAAA,EAAM;AACX,QAAA,MAAM,EAAE,KAAA,EAAO,IAAA,EAAK,GAAI,MAAM,OAAO,IAAA,EAAK;AAC1C,QAAA,IAAI,IAAA,EAAM;AACV,QAAA,IAAA,CAAK,aAAA,GAAgB,iBAAA,CAAkB,KAAK,CAAC,CAAA;AAAA,MAC/C;AAAA,IACF,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEA,MAAc,WAAA,GAA6B;AACzC,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,SAAA,CAAU,6BAAA,CAA8B,SAAA,EAAU;AACtE,IAAA,IAAI;AACF,MAAA,OAAO,IAAA,EAAM;AACX,QAAA,MAAM,EAAE,KAAA,EAAO,MAAA,EAAQ,MAAK,GAAI,MAAM,OAAO,IAAA,EAAK;AAClD,QAAA,IAAI,IAAA,EAAM;AACV,QAAA,IAAA,CAAK,iBAAiB,MAAM,CAAA;AAAA,MAC9B;AAAA,IACF,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEA,MAAc,iBAAiB,MAAA,EAA4B;AACzD,IAAA,MAAM,MAAA,GAAS,OAAO,SAAA,EAAU;AAChC,IAAA,IAAI,MAAA,GAAsC,IAAI,UAAA,CAAW,CAAC,CAAA;AAE1D,IAAA,IAAI;AACF,MAAA,OAAO,IAAA,EAAM;AACX,QAAA,MAAM,EAAE,KAAA,EAAO,IAAA,EAAK,GAAI,MAAM,OAAO,IAAA,EAAK;AAC1C,QAAA,IAAI,IAAA,EAAM;AAEV,QAAA,MAAM,SAAS,IAAI,UAAA,CAAW,MAAA,CAAO,MAAA,GAAS,MAAM,MAAM,CAAA;AAC1D,QAAA,MAAA,CAAO,IAAI,MAAM,CAAA;AACjB,QAAA,MAAA,CAAO,GAAA,CAAI,KAAA,EAAO,MAAA,CAAO,MAAM,CAAA;AAE/B,QAAA,MAAM,EAAE,MAAA,EAAQ,SAAA,EAAU,GAAI,mBAAmB,MAAM,CAAA;AACvD,QAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,UAAA,IAAA,CAAK,gBAAgB,KAAK,CAAA;AAAA,QAC5B;AACA,QAAA,MAAA,GAAS,SAAA;AAAA,MACX;AAAA,IACF,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AACF,CAAA;;;ACnGO,IAAM,mBAAN,MAA4C;AAAA,EACxC,QAAA,GAAW,WAAA;AAAA,EAEpB,aAAA,GAAsD,IAAA;AAAA,EACtD,aAAA,GAAsD,IAAA;AAAA,EACtD,cAAA,GAA6F,IAAA;AAAA,EAC7F,OAAA,GAA+B,IAAA;AAAA,EAC/B,OAAA,GAA2C,IAAA;AAAA,EAEnC,EAAA,GAAuB,IAAA;AAAA,EACvB,GAAA;AAAA,EACA,WAAA;AAAA,EAER,WAAA,CAAY,GAAA,EAAa,WAAA,GAAsB,eAAA,EAAiB;AAC9D,IAAA,IAAA,CAAK,GAAA,GAAM,GAAA;AACX,IAAA,IAAA,CAAK,WAAA,GAAc,WAAA;AAAA,EACrB;AAAA,EAEA,MAAM,OAAA,GAAyB;AAC7B,IAAA,OAAO,IAAI,OAAA,CAAc,CAAC,OAAA,EAAS,MAAA,KAAW;AAC5C,MAAA,IAAA,CAAK,KAAK,IAAI,UAAA,CAAW,UAAU,IAAA,CAAK,GAAA,EAAK,KAAK,WAAW,CAAA;AAC7D,MAAA,IAAI,IAAA,CAAK,gBAAgB,iBAAA,EAAmB;AAC1C,QAAA,IAAA,CAAK,GAAG,UAAA,GAAa,aAAA;AAAA,MACvB;AAEA,MAAA,IAAA,CAAK,EAAA,CAAG,MAAA,GAAS,MAAM,OAAA,EAAQ;AAE/B,MAAA,IAAA,CAAK,EAAA,CAAG,UAAU,MAAM;AACtB,QAAA,MAAA,CAAO,IAAI,KAAA,CAAM,CAAA,6BAAA,EAAgC,IAAA,CAAK,GAAG,EAAE,CAAC,CAAA;AAAA,MAC9D,CAAA;AAEA,MAAA,IAAA,CAAK,EAAA,CAAG,SAAA,GAAY,CAAC,KAAA,KAAwB;AAC3C,QAAA,IAAI,IAAA,CAAK,WAAA,KAAgB,iBAAA,IAAqB,KAAA,CAAM,gBAAgB,WAAA,EAAa;AAC/E,UAAA,MAAM,OAAA,GAAU,mBAAA,CAAoB,KAAA,CAAM,IAAI,CAAA;AAC9C,UAAA,IAAI,SAAS,IAAA,KAAS,aAAA,EAAe,IAAA,CAAK,aAAA,GAAgB,QAAQ,IAAI,CAAA;AAAA,eAAA,IAC7D,SAAS,IAAA,KAAS,aAAA,EAAe,IAAA,CAAK,aAAA,GAAgB,QAAQ,IAAI,CAAA;AAAA,QAC7E,CAAA,MAAA,IAAW,OAAO,KAAA,CAAM,IAAA,KAAS,QAAA,EAAU;AACzC,UAAA,IAAI;AACF,YAAA,MAAM,GAAA,GAAM,IAAA,CAAK,KAAA,CAAM,KAAA,CAAM,IAAI,CAAA;AACjC,YAAA,IAAI,IAAI,IAAA,KAAS,aAAA,EAAe,IAAA,CAAK,aAAA,GAAgB,IAAI,IAAI,CAAA;AAAA,iBAAA,IACpD,IAAI,IAAA,KAAS,aAAA,EAAe,IAAA,CAAK,aAAA,GAAgB,IAAI,IAAI,CAAA;AAAA,iBAAA,IACzD,GAAA,CAAI,SAAS,cAAA,EAAgB;AACpC,cAAA,IAAA,CAAK,iBAAiB,GAAA,CAAI,IAAA,CAAK,KAAA,EAAO,GAAA,CAAI,KAAK,MAAM,CAAA;AAAA,YACvD;AAAA,UACF,CAAA,CAAA,MAAQ;AAAA,UAER;AAAA,QACF;AAAA,MACF,CAAA;AAEA,MAAA,IAAA,CAAK,EAAA,CAAG,OAAA,GAAU,MAAM,IAAA,CAAK,OAAA,IAAU;AAAA,IACzC,CAAC,CAAA;AAAA,EACH;AAAA,EAEA,KAAA,GAAc;AACZ,IAAA,IAAI;AACF,MAAA,IAAA,CAAK,IAAI,KAAA,EAAM;AAAA,IACjB,CAAA,CAAA,MAAQ;AAAA,IAAC;AACT,IAAA,IAAA,CAAK,EAAA,GAAK,IAAA;AAAA,EACZ;AACF,CAAA;;;AChDO,SAAS,iBAAiB,IAAA,GAAO,WAAA,EAAa,MAAA,GAAS,KAAA,EAAO,SAAS,KAAA,EAAe;AAC3F,EAAA,OAAO,2BAA2B,kBAAA,CAAmB,IAAI,CAAC,CAAA,QAAA,EAAW,MAAM,WAAW,MAAM,CAAA,CAAA;AAC9F;AAQO,SAAS,iBAAiB,GAAA,EAA8D;AAC7F,EAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,GAAG,CAAA;AACvB,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,MAAM,CAAA,IAAK,WAAA;AAC7C,EAAA,MAAM,MAAA,GAAS,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,QAAQ,CAAA,IAAK,OAAA;AACjD,EAAA,MAAM,MAAA,GAAS,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,QAAQ,CAAA,IAAK,OAAA;AACjD,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,CAAA,OAAA,EAAU,IAAI,CAAA,CAAA,EAAI,MAAM,CAAA,QAAA,CAAA;AAAA,IAC/B,KAAA,EAAO,CAAA,KAAA,EAAQ,IAAI,CAAA,CAAA,EAAI,MAAM,CAAA,QAAA,CAAA;AAAA,IAC7B,KAAA,EAAO,CAAA,QAAA,EAAW,IAAI,CAAA,CAAA,EAAI,MAAM,CAAA;AAAA,GAClC;AACF;AAEA,IAAM,eAAA,GAEF;AAAA,EACF,UAAA,EAAY,CAAC,cAAA,EAAgB,WAAW,CAAA;AAAA,EACxC,cAAA,EAAgB,GAAA;AAAA,EAChB,UAAA,EAAY,CAAA;AAAA,EACZ,aAAA,EAAe;AACjB,CAAA;AAQO,IAAM,kBAAA,GAAN,cAAiC,YAAA,CAA6B;AAAA,EAC3D,OAAA;AAAA,EACA,SAAA,GAA8B,IAAA;AAAA,EAC9B,cAAA,GAAuD,IAAA;AAAA,EACvD,gBAAA,GAAmB,KAAA;AAAA,EACnB,aAAA,GAA+B,IAAA;AAAA,EAC/B,UAAA,GAAa,CAAA;AAAA,EAEb,MAAA,GAA0B,cAAA;AAAA,EAC1B,SAAA,GAA+B,MAAA;AAAA,EAEvC,IAAI,KAAA,GAAyB;AAC3B,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EACd;AAAA,EACA,IAAI,QAAA,GAA8B;AAChC,IAAA,OAAO,IAAA,CAAK,SAAA;AAAA,EACd;AAAA,EAEA,YAAY,OAAA,EAA0B;AACpC,IAAA,KAAA,EAAM;AACN,IAAA,IAAA,CAAK,OAAA,GAAU,EAAE,GAAG,eAAA,EAAiB,GAAG,OAAA,EAAQ;AAAA,EAClD;AAAA,EAEA,MAAM,OAAA,GAAyB;AAC7B,IAAA,IAAA,CAAK,gBAAA,GAAmB,KAAA;AACxB,IAAA,IAAA,CAAK,QAAA,CAAS,cAAc,MAAM,CAAA;AAGlC,IAAA,IAAI,KAAA,GAAQ,KAAK,OAAA,CAAQ,KAAA;AACzB,IAAA,IAAI,KAAA,GAAQ,KAAK,OAAA,CAAQ,KAAA;AACzB,IAAA,IAAI,QAAA,GAAW,KAAK,OAAA,CAAQ,QAAA;AAE5B,IAAA,IAAI,IAAA,CAAK,QAAQ,GAAA,EAAK;AACpB,MAAA,MAAM,MAAA,GAAS,gBAAA,CAAiB,IAAA,CAAK,OAAA,CAAQ,GAAG,CAAA;AAChD,MAAA,KAAA,GAAQ,SAAS,MAAA,CAAO,KAAA;AACxB,MAAA,KAAA,GAAQ,SAAS,MAAA,CAAO,KAAA;AACxB,MAAA,IAAA,CAAK,OAAA,CAAQ,KAAA,GAAQ,IAAA,CAAK,OAAA,CAAQ,SAAS,MAAA,CAAO,KAAA;AAAA,IACpD;AAEA,IAAA,IAAI,CAAC,KAAA,IAAS,CAAC,KAAA,EAAO;AACpB,MAAA,MAAM,KAAA,GAAQ,KAAK,OAAA,CAAQ,KAAA;AAI3B,MAAA,MAAM,MAAA,GAAS,KAAA,GACX,CAAC,KAAK,CAAA,GACN;AAAA,QACE,iCAAA;AAAA,QACA;AAAA,OACF;AAEJ,MAAA,IAAI,QAAA,GAAW,KAAA;AACf,MAAA,KAAA,MAAW,OAAO,MAAA,EAAQ;AACxB,QAAA,IAAI;AACF,UAAA,MAAM,EAAA,GAAK,MAAM,qBAAA,CAAsB,GAAG,CAAA;AAC1C,UAAA,MAAM,SAAA,GAAY,iBAAiB,EAAE,CAAA;AACrC,UAAA,KAAA,GAAQ,UAAU,YAAA,EAAc,GAAA;AAChC,UAAA,KAAA,GAAQ,UAAU,SAAA,EAAW,GAAA;AAC7B,UAAA,QAAA,GAAW,QAAA,IAAY,UAAU,YAAA,EAAc,QAAA;AAC/C,UAAA,IAAA,CAAK,aAAA,GAAgB,UAAU,aAAA,IAAiB,IAAA;AAChD,UAAA,QAAA,GAAW,IAAA;AACX,UAAA;AAAA,QACF,CAAA,CAAA,MAAQ;AAAA,QAER;AAAA,MACF;AACA,MAAA,IAAI,CAAC,QAAA,EAAU;AACb,QAAA,IAAA,CAAK,IAAA,CAAK,OAAA,EAAS,IAAI,KAAA,CAAM,mCAAmC,CAAC,CAAA;AACjE,QAAA,KAAA,GAAQ,8BAAA;AAAA,MACV;AAAA,IACF;AAGA,IAAA,KAAA,MAAW,KAAA,IAAS,IAAA,CAAK,OAAA,CAAQ,UAAA,EAAY;AAC3C,MAAA,IAAI,KAAA,KAAU,kBAAkB,KAAA,EAAO;AACrC,QAAA,IAAI;AACF,UAAA,IAAI,OAAO,UAAA,CAAW,YAAA,KAAiB,WAAA,EAAa;AACpD,UAAA,MAAM,OAAA,GAAU,IAAI,mBAAA,CAAoB,KAAA,EAAO,QAAQ,CAAA;AACvD,UAAA,IAAI,MAAM,IAAA,CAAK,YAAA,CAAa,OAAO,CAAA,EAAG;AAAA,QACxC,CAAA,CAAA,MAAQ;AACN,UAAA;AAAA,QACF;AAAA,MACF;AACA,MAAA,IAAI,KAAA,KAAU,eAAe,KAAA,EAAO;AAClC,QAAA,IAAI;AACF,UAAA,MAAM,UAAU,IAAI,gBAAA,CAAiB,KAAA,EAAO,IAAA,CAAK,QAAQ,aAAa,CAAA;AACtE,UAAA,IAAI,MAAM,IAAA,CAAK,YAAA,CAAa,OAAO,CAAA,EAAG;AAAA,QACxC,CAAA,CAAA,MAAQ;AACN,UAAA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,QAAA,CAAS,gBAAgB,MAAM,CAAA;AACpC,IAAA,IAAA,CAAK,iBAAA,EAAkB;AAAA,EACzB;AAAA;AAAA,EAGA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,UAAA,GAAa,CAAA;AAClB,IAAA,IAAA,CAAK,gBAAA,GAAmB,KAAA;AACxB,IAAA,IAAA,CAAK,OAAA,EAAQ;AAAA,EACf;AAAA,EAEA,UAAA,GAAmB;AACjB,IAAA,IAAA,CAAK,gBAAA,GAAmB,IAAA;AACxB,IAAA,IAAA,CAAK,cAAA,EAAe;AACpB,IAAA,IAAA,CAAK,WAAW,KAAA,EAAM;AACtB,IAAA,IAAA,CAAK,SAAA,GAAY,IAAA;AACjB,IAAA,IAAA,CAAK,QAAA,CAAS,gBAAgB,MAAM,CAAA;AAAA,EACtC;AAAA,EAEA,MAAM,eAAA,GAAyC;AAC7C,IAAA,IAAI,CAAC,IAAA,CAAK,aAAA,EAAe,OAAO,EAAC;AACjC,IAAA,MAAM,GAAA,GAAM,MAAM,UAAA,CAAW,KAAA,CAAM,KAAK,aAAa,CAAA;AACrD,IAAA,IAAI,CAAC,GAAA,CAAI,EAAA,EAAI,OAAO,EAAC;AACrB,IAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAC5B,IAAA,OAAO,IAAA,CAAK,WAAW,EAAC;AAAA,EAC1B;AAAA,EAEA,MAAc,aAAa,OAAA,EAAsC;AAC/D,IAAA,OAAA,CAAQ,gBAAgB,CAAC,IAAA,KAAS,IAAA,CAAK,IAAA,CAAK,eAAe,IAAI,CAAA;AAC/D,IAAA,OAAA,CAAQ,gBAAgB,CAAC,IAAA,KAAS,IAAA,CAAK,IAAA,CAAK,eAAe,IAAI,CAAA;AAC/D,IAAA,OAAA,CAAQ,UAAU,CAAC,GAAA,KAAQ,IAAA,CAAK,IAAA,CAAK,SAAS,GAAG,CAAA;AAEjD,IAAA,IAAI,oBAAoB,OAAA,EAAS;AAC/B,MAAC,OAAA,CAAgB,cAAA,GAAiB,CAAC,KAAA,EAAqC,MAAA,KAAuB;AAC7F,QAAA,IAAA,CAAK,IAAA,CAAK,cAAA,EAAgB,KAAA,EAAO,MAAM,CAAA;AAAA,MACzC,CAAA;AAAA,IACF;AAEA,IAAA,OAAA,CAAQ,UAAU,MAAM;AACtB,MAAA,IAAA,CAAK,SAAA,GAAY,IAAA;AACjB,MAAA,IAAA,CAAK,QAAA,CAAS,gBAAgB,MAAM,CAAA;AACpC,MAAA,IAAI,CAAC,IAAA,CAAK,gBAAA,EAAkB,IAAA,CAAK,iBAAA,EAAkB;AAAA,IACrD,CAAA;AAEA,IAAA,IAAI;AACF,MAAA,MAAM,QAAQ,OAAA,EAAQ;AACtB,MAAA,IAAA,CAAK,SAAA,GAAY,OAAA;AACjB,MAAA,IAAA,CAAK,UAAA,GAAa,CAAA;AAClB,MAAA,IAAA,CAAK,QAAA,CAAS,WAAA,EAAa,OAAA,CAAQ,QAAQ,CAAA;AAC3C,MAAA,OAAO,IAAA;AAAA,IACT,SAAS,GAAA,EAAK;AACZ,MAAA,IAAA,CAAK,IAAA,CAAK,OAAA,EAAS,GAAA,YAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,KAAA,CAAM,MAAA,CAAO,GAAG,CAAC,CAAC,CAAA;AACtE,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF;AAAA,EAEQ,QAAA,CAAS,OAAwB,QAAA,EAAmC;AAC1E,IAAA,IAAI,IAAA,CAAK,MAAA,KAAW,KAAA,IAAS,IAAA,CAAK,cAAc,QAAA,EAAU;AAC1D,IAAA,IAAA,CAAK,MAAA,GAAS,KAAA;AACd,IAAA,IAAA,CAAK,SAAA,GAAY,QAAA;AACjB,IAAA,IAAA,CAAK,IAAA,CAAK,aAAA,EAAe,KAAA,EAAO,QAAQ,CAAA;AAAA,EAC1C;AAAA,EAEQ,iBAAA,GAA0B;AAChC,IAAA,IAAI,IAAA,CAAK,OAAA,CAAQ,cAAA,IAAkB,CAAA,IAAK,KAAK,gBAAA,EAAkB;AAE/D,IAAA,IAAA,CAAK,UAAA,EAAA;AACL,IAAA,OAAA,CAAQ,GAAA,CAAI,gCAAgC,IAAA,CAAK,UAAU,IAAI,IAAA,CAAK,OAAA,CAAQ,UAAU,CAAA,CAAE,CAAA;AACxF,IAAA,IAAI,IAAA,CAAK,UAAA,GAAa,IAAA,CAAK,OAAA,CAAQ,UAAA,EAAY;AAC7C,MAAA,OAAA,CAAQ,IAAI,4CAA4C,CAAA;AACxD,MAAA,IAAA,CAAK,QAAA,CAAS,UAAU,MAAM,CAAA;AAC9B,MAAA;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,cAAA,EAAe;AACpB,IAAA,IAAA,CAAK,cAAA,GAAiB,WAAW,MAAM;AACrC,MAAA,IAAA,CAAK,cAAA,GAAiB,IAAA;AACtB,MAAA,IAAA,CAAK,OAAA,EAAQ;AAAA,IACf,CAAA,EAAG,IAAA,CAAK,OAAA,CAAQ,cAAc,CAAA;AAAA,EAChC;AAAA,EAEQ,cAAA,GAAuB;AAC7B,IAAA,IAAI,KAAK,cAAA,EAAgB;AACvB,MAAA,YAAA,CAAa,KAAK,cAAc,CAAA;AAChC,MAAA,IAAA,CAAK,cAAA,GAAiB,IAAA;AAAA,IACxB;AAAA,EACF;AACF;;;ACzOA,IAAM,UAAA,GAAa,mBAAA;AACnB,IAAM,WAAA,GAAc,sDAAA;AAwBb,SAAS,oBAAA,CAAqB,MAAA,EAAgB,YAAA,GAAe,qBAAA,EAAuB,SAAA,EAA0B;AACnH,EAAA,MAAM,SAAS,IAAI,eAAA,CAAgB,EAAE,MAAA,EAAQ,QAAA,EAAU,cAAc,CAAA;AACrE,EAAA,IAAI,SAAA,EAAW,MAAA,CAAO,GAAA,CAAI,WAAA,EAAa,SAAS,CAAA;AAChD,EAAA,UAAA,CAAW,QAAA,CAAS,IAAA,GAAO,CAAA,qBAAA,EAAwB,MAAM,CAAA,CAAA;AAC3D;AAMO,SAAS,uBAAuB,YAAA,EAAuD;AAC5F,EAAA,MAAM,EAAA,GAAK,YAAA,CAAa,GAAA,CAAI,IAAI,CAAA;AAChC,EAAA,MAAM,MAAA,GAAS,YAAA,CAAa,GAAA,CAAI,QAAQ,CAAA;AACxC,EAAA,IAAI,CAAC,EAAA,IAAM,CAAC,MAAA,EAAQ,OAAO,IAAA;AAC3B,EAAA,OAAO;AAAA,IACL,EAAA;AAAA,IACA,MAAA,EAAQ,QAAA,CAAS,MAAA,EAAQ,EAAE,CAAA;AAAA,IAC3B,QAAQ,QAAA,CAAS,YAAA,CAAa,IAAI,QAAQ,CAAA,IAAK,SAAS,EAAE,CAAA;AAAA,IAC1D,WAAW,QAAA,CAAS,YAAA,CAAa,IAAI,WAAW,CAAA,IAAK,SAAS,EAAE,CAAA;AAAA,IAChE,QAAA,EAAU,YAAA,CAAa,GAAA,CAAI,UAAU,CAAA,IAAK,MAAA;AAAA,IAC1C,SAAA,EAAW,YAAA,CAAa,GAAA,CAAI,WAAW,CAAA,IAAK;AAAA,GAC9C;AACF;AAmBO,SAAS,eAAe,OAAA,EAA2C;AACxE,EAAA,MAAM,SAAA,GAAY,SAAS,SAAA,IAAa,UAAA;AACxC,EAAA,MAAM,OAAA,GAAU,SAAS,OAAA,IAAW,IAAA;AAGpC,EAAA,IAAI,WAAA,GAAc,WAAA;AAClB,EAAA,IAAI,SAAS,WAAA,EAAa;AACxB,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,IAAI,GAAA,CAAI,OAAA,CAAQ,WAAW,CAAA;AAC1C,MAAA,IAAI,MAAA,CAAO,QAAA,KAAa,OAAA,IAAW,MAAA,CAAO,aAAa,QAAA,EAAU;AAC/D,QAAA,WAAA,GAAc,OAAA,CAAQ,WAAA;AAAA,MACxB;AAAA,IACF,CAAA,CAAA,MAAQ;AAAA,IAAC;AAAA,EACX;AAEA,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,KAAY;AAE9B,IAAA,IAAI,QAAA,GAAW,KAAA;AAEf,IAAA,MAAM,SAAS,MAAM;AACnB,MAAA,QAAA,GAAW,IAAA;AAAA,IACb,CAAA;AACA,IAAA,UAAA,CAAW,gBAAA,CAAiB,QAAQ,MAAM,CAAA;AAG1C,IAAA,MAAM,MAAA,GAAS,QAAA,CAAS,aAAA,CAAc,QAAQ,CAAA;AAC9C,IAAA,MAAA,CAAO,MAAM,OAAA,GAAU,MAAA;AACvB,IAAA,MAAA,CAAO,GAAA,GAAM,SAAA;AACb,IAAA,QAAA,CAAS,IAAA,CAAK,YAAY,MAAM,CAAA;AAEhC,IAAA,UAAA,CAAW,MAAM;AACf,MAAA,UAAA,CAAW,mBAAA,CAAoB,QAAQ,MAAM,CAAA;AAC7C,MAAA,QAAA,CAAS,IAAA,CAAK,YAAY,MAAM,CAAA;AAEhC,MAAA,IAAI,QAAA,IAAY,SAAS,MAAA,EAAQ;AAC/B,QAAA,OAAA,CAAQ,IAAI,CAAA;AAAA,MACd,CAAA,MAAO;AAEL,QAAA,UAAA,CAAW,SAAS,IAAA,GAAO,WAAA;AAC3B,QAAA,OAAA,CAAQ,KAAK,CAAA;AAAA,MACf;AAAA,IACF,GAAG,OAAO,CAAA;AAAA,EACZ,CAAC,CAAA;AACH","file":"index.js","sourcesContent":["import type { ThingDescription } from \"./types.js\";\n\nexport interface ResolvedEndpoints {\n webtransport?: { url: string; certHash?: string };\n websocket?: { url: string };\n deviceInfoUrl?: string;\n}\n\n/** Fetch the WoT Thing Description from the bridge */\nexport async function fetchThingDescription(tdUrl: string): Promise<ThingDescription> {\n const res = await globalThis.fetch(tdUrl);\n if (!res.ok) throw new Error(`Failed to fetch TD: HTTP ${res.status}`);\n return res.json() as Promise<ThingDescription>;\n}\n\n/** Extract WebTransport, WebSocket, and device info endpoints from a Thing Description */\nexport function resolveEndpoints(td: ThingDescription): ResolvedEndpoints {\n const result: ResolvedEndpoints = {};\n\n const spatialForms = td.events?.spatialData?.forms ?? [];\n\n const wtForm = spatialForms.find((f) => f.subprotocol === \"webtransport\");\n if (wtForm) {\n result.webtransport = {\n url: wtForm.href,\n certHash: td[\"satmouse:certHash\"],\n };\n }\n\n const wsForm = spatialForms.find((f) => f.subprotocol === \"websocket\");\n if (wsForm) {\n result.websocket = { url: wsForm.href };\n }\n\n const deviceForm = td.properties?.deviceInfo?.forms?.[0];\n if (deviceForm) {\n result.deviceInfoUrl = deviceForm.href;\n }\n\n return result;\n}\n","import type { SpatialData, ButtonEvent } from \"./types.js\";\n\n/** Decode 24-byte binary spatial data datagram (WebTransport or raw binary) */\nexport function decodeBinaryFrame(buffer: ArrayBuffer | Uint8Array): SpatialData {\n const len = buffer instanceof ArrayBuffer ? buffer.byteLength : buffer.byteLength;\n if (len < 20) {\n throw new RangeError(`Spatial frame too short: expected ≥20 bytes, got ${len}`);\n }\n const ab = buffer instanceof ArrayBuffer ? buffer : buffer.buffer;\n const offset = buffer instanceof Uint8Array ? buffer.byteOffset : 0;\n const view = new DataView(ab, offset);\n return {\n translation: {\n x: view.getInt16(8, true),\n y: view.getInt16(10, true),\n z: view.getInt16(12, true),\n },\n rotation: {\n x: view.getInt16(14, true),\n y: view.getInt16(16, true),\n z: view.getInt16(18, true),\n },\n timestamp: view.getFloat64(0, true),\n };\n}\n\n/** Decode WebSocket binary frame (1-byte type prefix + 24-byte payload) */\nexport function decodeWsBinaryFrame(\n buffer: ArrayBuffer | Uint8Array,\n): { type: \"spatialData\"; data: SpatialData } | { type: \"buttonEvent\"; data: ButtonEvent } | null {\n const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);\n if (bytes.length < 1) return null;\n\n const typePrefix = bytes[0];\n if (typePrefix === 0x01 && bytes.length >= 25) {\n return { type: \"spatialData\", data: decodeBinaryFrame(bytes.subarray(1, 25)) };\n }\n if (typePrefix === 0x02) {\n const json = new TextDecoder().decode(bytes.subarray(1));\n return { type: \"buttonEvent\", data: JSON.parse(json) as ButtonEvent };\n }\n return null;\n}\n\n/** Decode length-prefixed JSON button events from a WebTransport stream chunk */\nexport function decodeButtonStream(\n buffer: Uint8Array<ArrayBufferLike>,\n): { events: ButtonEvent[]; remainder: Uint8Array<ArrayBufferLike> } {\n const events: ButtonEvent[] = [];\n let pos = 0;\n\n while (pos + 4 <= buffer.length) {\n const view = new DataView(buffer.buffer, buffer.byteOffset + pos);\n const len = view.getUint32(0, true);\n // Guard against absurd lengths\n if (len > 65536 || pos + 4 + len > buffer.length) break;\n const json = new TextDecoder().decode(buffer.subarray(pos + 4, pos + 4 + len));\n try {\n const event = JSON.parse(json);\n if (typeof event.button === \"number\" && typeof event.pressed === \"boolean\") {\n events.push(event as ButtonEvent);\n }\n } catch {\n // Skip malformed JSON frames\n }\n pos += 4 + len;\n }\n\n return { events, remainder: buffer.subarray(pos) };\n}\n","import type { Transport } from \"./transport.js\";\nimport type { SpatialData, ButtonEvent } from \"../types.js\";\nimport { decodeBinaryFrame, decodeButtonStream } from \"../decode.js\";\n\nexport class WebTransportAdapter implements Transport {\n readonly protocol = \"webtransport\" as const;\n\n onSpatialData: ((data: SpatialData) => void) | null = null;\n onButtonEvent: ((data: ButtonEvent) => void) | null = null;\n onClose: (() => void) | null = null;\n onError: ((error: Error) => void) | null = null;\n\n private transport: any = null;\n private url: string;\n private certHash?: string;\n\n constructor(url: string, certHash?: string) {\n this.url = url;\n this.certHash = certHash;\n }\n\n async connect(): Promise<void> {\n if (typeof globalThis.WebTransport === \"undefined\") {\n throw new Error(\"WebTransport is not available in this environment\");\n }\n\n const options: any = {};\n if (this.certHash) {\n options.serverCertificateHashes = [\n {\n algorithm: \"sha-256\",\n value: Uint8Array.from(atob(this.certHash), (c) => c.charCodeAt(0)),\n },\n ];\n }\n\n this.transport = new (globalThis as any).WebTransport(this.url, options);\n await this.transport.ready;\n\n this.readDatagrams();\n this.readStreams();\n\n this.transport.closed\n .then(() => this.onClose?.())\n .catch(() => this.onClose?.());\n }\n\n close(): void {\n try {\n this.transport?.close();\n } catch {}\n this.transport = null;\n }\n\n private async readDatagrams(): Promise<void> {\n const reader = this.transport.datagrams.readable.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n this.onSpatialData?.(decodeBinaryFrame(value));\n }\n } catch {\n // Transport closed\n }\n }\n\n private async readStreams(): Promise<void> {\n const reader = this.transport.incomingUnidirectionalStreams.getReader();\n try {\n while (true) {\n const { value: stream, done } = await reader.read();\n if (done) break;\n this.readButtonStream(stream);\n }\n } catch {\n // Transport closed\n }\n }\n\n private async readButtonStream(stream: any): Promise<void> {\n const reader = stream.getReader();\n let buffer: Uint8Array<ArrayBufferLike> = new Uint8Array(0);\n\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n\n const newBuf = new Uint8Array(buffer.length + value.length);\n newBuf.set(buffer);\n newBuf.set(value, buffer.length);\n\n const { events, remainder } = decodeButtonStream(newBuf);\n for (const event of events) {\n this.onButtonEvent?.(event);\n }\n buffer = remainder;\n }\n } catch {\n // Stream closed\n }\n }\n}\n","import type { Transport } from \"./transport.js\";\nimport type { SpatialData, ButtonEvent, DeviceInfo } from \"../types.js\";\nimport { decodeWsBinaryFrame } from \"../decode.js\";\n\nexport class WebSocketAdapter implements Transport {\n readonly protocol = \"websocket\" as const;\n\n onSpatialData: ((data: SpatialData) => void) | null = null;\n onButtonEvent: ((data: ButtonEvent) => void) | null = null;\n onDeviceStatus: ((event: \"connected\" | \"disconnected\", device: DeviceInfo) => void) | null = null;\n onClose: (() => void) | null = null;\n onError: ((error: Error) => void) | null = null;\n\n private ws: WebSocket | null = null;\n private url: string;\n private subprotocol: string;\n\n constructor(url: string, subprotocol: string = \"satmouse-json\") {\n this.url = url;\n this.subprotocol = subprotocol;\n }\n\n async connect(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n this.ws = new globalThis.WebSocket(this.url, this.subprotocol);\n if (this.subprotocol === \"satmouse-binary\") {\n this.ws.binaryType = \"arraybuffer\";\n }\n\n this.ws.onopen = () => resolve();\n\n this.ws.onerror = () => {\n reject(new Error(`WebSocket connection failed: ${this.url}`));\n };\n\n this.ws.onmessage = (event: MessageEvent) => {\n if (this.subprotocol === \"satmouse-binary\" && event.data instanceof ArrayBuffer) {\n const decoded = decodeWsBinaryFrame(event.data);\n if (decoded?.type === \"spatialData\") this.onSpatialData?.(decoded.data);\n else if (decoded?.type === \"buttonEvent\") this.onButtonEvent?.(decoded.data);\n } else if (typeof event.data === \"string\") {\n try {\n const msg = JSON.parse(event.data);\n if (msg.type === \"spatialData\") this.onSpatialData?.(msg.data);\n else if (msg.type === \"buttonEvent\") this.onButtonEvent?.(msg.data);\n else if (msg.type === \"deviceStatus\") {\n this.onDeviceStatus?.(msg.data.event, msg.data.device);\n }\n } catch {\n // Ignore malformed messages\n }\n }\n };\n\n this.ws.onclose = () => this.onClose?.();\n });\n }\n\n close(): void {\n try {\n this.ws?.close();\n } catch {}\n this.ws = null;\n }\n}\n","import { TypedEmitter } from \"./emitter.js\";\nimport { fetchThingDescription, resolveEndpoints } from \"./discovery.js\";\nimport { WebTransportAdapter } from \"./transports/webtransport.js\";\nimport { WebSocketAdapter } from \"./transports/websocket.js\";\nimport type { Transport } from \"./transports/transport.js\";\nimport type {\n ConnectOptions,\n ConnectionState,\n DeviceInfo,\n SatMouseEvents,\n TransportProtocol,\n} from \"./types.js\";\n\n/**\n * Build a satmouse:// connect URI from connection parameters.\n */\nexport function buildSatMouseUri(host = \"localhost\", wsPort = 18945, wtPort = 18946): string {\n return `satmouse://connect?host=${encodeURIComponent(host)}&wsPort=${wsPort}&wtPort=${wtPort}`;\n}\n\n/**\n * Parse a satmouse:// URI into connection parameters.\n *\n * Format: satmouse://connect?host=<ip>&wsPort=<port>&wtPort=<port>\n * All query params are optional. Defaults: host=localhost, wsPort=4444, wtPort=4443.\n */\nexport function parseSatMouseUri(uri: string): { tdUrl: string; wsUrl: string; wtUrl: string } {\n const url = new URL(uri);\n const host = url.searchParams.get(\"host\") ?? \"localhost\";\n const wsPort = url.searchParams.get(\"wsPort\") ?? \"18945\";\n const wtPort = url.searchParams.get(\"wtPort\") ?? \"18946\";\n return {\n tdUrl: `http://${host}:${wsPort}/td.json`,\n wsUrl: `ws://${host}:${wsPort}/spatial`,\n wtUrl: `https://${host}:${wtPort}`,\n };\n}\n\nconst DEFAULT_OPTIONS: Required<\n Pick<ConnectOptions, \"transports\" | \"reconnectDelay\" | \"maxRetries\" | \"wsSubprotocol\">\n> = {\n transports: [\"webtransport\", \"websocket\"],\n reconnectDelay: 2000,\n maxRetries: 3,\n wsSubprotocol: \"satmouse-json\",\n};\n\n/**\n * Core connection to a SatMouse bridge.\n *\n * Handles discovery (via WoT Thing Description), transport negotiation\n * (WebTransport with WebSocket fallback), event dispatch, and auto-reconnect.\n */\nexport class SatMouseConnection extends TypedEmitter<SatMouseEvents> {\n private options: ConnectOptions & typeof DEFAULT_OPTIONS;\n private transport: Transport | null = null;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private intentionalClose = false;\n private deviceInfoUrl: string | null = null;\n private retryCount = 0;\n\n private _state: ConnectionState = \"disconnected\";\n private _protocol: TransportProtocol = \"none\";\n\n get state(): ConnectionState {\n return this._state;\n }\n get protocol(): TransportProtocol {\n return this._protocol;\n }\n\n constructor(options?: ConnectOptions) {\n super();\n this.options = { ...DEFAULT_OPTIONS, ...options };\n }\n\n async connect(): Promise<void> {\n this.intentionalClose = false;\n this.setState(\"connecting\", \"none\");\n\n // Resolve endpoints — satmouse:// URI takes priority\n let wtUrl = this.options.wtUrl;\n let wsUrl = this.options.wsUrl;\n let certHash = this.options.certHash;\n\n if (this.options.uri) {\n const parsed = parseSatMouseUri(this.options.uri);\n wtUrl = wtUrl ?? parsed.wtUrl;\n wsUrl = wsUrl ?? parsed.wsUrl;\n this.options.tdUrl = this.options.tdUrl ?? parsed.tdUrl;\n }\n\n if (!wtUrl && !wsUrl) {\n const tdUrl = this.options.tdUrl;\n // Use 127.0.0.1 (not localhost) — Safari treats the loopback IP as a\n // \"Potentially Trustworthy Origin\" more reliably than the hostname.\n // Try HTTPS first (works from HTTPS pages), fall back to HTTP.\n const tdUrls = tdUrl\n ? [tdUrl]\n : [\n \"https://127.0.0.1:18947/td.json\",\n \"http://127.0.0.1:18945/td.json\",\n ];\n\n let resolved = false;\n for (const url of tdUrls) {\n try {\n const td = await fetchThingDescription(url);\n const endpoints = resolveEndpoints(td);\n wtUrl = endpoints.webtransport?.url;\n wsUrl = endpoints.websocket?.url;\n certHash = certHash ?? endpoints.webtransport?.certHash;\n this.deviceInfoUrl = endpoints.deviceInfoUrl ?? null;\n resolved = true;\n break;\n } catch {\n // Try next URL\n }\n }\n if (!resolved) {\n this.emit(\"error\", new Error(\"Failed to fetch Thing Description\"));\n wsUrl = \"ws://127.0.0.1:18945/spatial\";\n }\n }\n\n // Try transports in preference order\n for (const proto of this.options.transports) {\n if (proto === \"webtransport\" && wtUrl) {\n try {\n if (typeof globalThis.WebTransport === \"undefined\") continue;\n const adapter = new WebTransportAdapter(wtUrl, certHash);\n if (await this.tryTransport(adapter)) return;\n } catch {\n continue;\n }\n }\n if (proto === \"websocket\" && wsUrl) {\n try {\n const adapter = new WebSocketAdapter(wsUrl, this.options.wsSubprotocol);\n if (await this.tryTransport(adapter)) return;\n } catch {\n continue;\n }\n }\n }\n\n this.setState(\"disconnected\", \"none\");\n this.scheduleReconnect();\n }\n\n /** Reset retry count and reconnect. Use after \"failed\" state. */\n retry(): void {\n this.retryCount = 0;\n this.intentionalClose = false;\n this.connect();\n }\n\n disconnect(): void {\n this.intentionalClose = true;\n this.clearReconnect();\n this.transport?.close();\n this.transport = null;\n this.setState(\"disconnected\", \"none\");\n }\n\n async fetchDeviceInfo(): Promise<DeviceInfo[]> {\n if (!this.deviceInfoUrl) return [];\n const res = await globalThis.fetch(this.deviceInfoUrl);\n if (!res.ok) return [];\n const data = await res.json();\n return data.devices ?? [];\n }\n\n private async tryTransport(adapter: Transport): Promise<boolean> {\n adapter.onSpatialData = (data) => this.emit(\"spatialData\", data);\n adapter.onButtonEvent = (data) => this.emit(\"buttonEvent\", data);\n adapter.onError = (err) => this.emit(\"error\", err);\n\n if (\"onDeviceStatus\" in adapter) {\n (adapter as any).onDeviceStatus = (event: \"connected\" | \"disconnected\", device: DeviceInfo) => {\n this.emit(\"deviceStatus\", event, device);\n };\n }\n\n adapter.onClose = () => {\n this.transport = null;\n this.setState(\"disconnected\", \"none\");\n if (!this.intentionalClose) this.scheduleReconnect();\n };\n\n try {\n await adapter.connect();\n this.transport = adapter;\n this.retryCount = 0;\n this.setState(\"connected\", adapter.protocol);\n return true;\n } catch (err) {\n this.emit(\"error\", err instanceof Error ? err : new Error(String(err)));\n return false;\n }\n }\n\n private setState(state: ConnectionState, protocol: TransportProtocol): void {\n if (this._state === state && this._protocol === protocol) return;\n this._state = state;\n this._protocol = protocol;\n this.emit(\"stateChange\", state, protocol);\n }\n\n private scheduleReconnect(): void {\n if (this.options.reconnectDelay <= 0 || this.intentionalClose) return;\n\n this.retryCount++;\n console.log(`[SatMouse] Reconnect attempt ${this.retryCount}/${this.options.maxRetries}`);\n if (this.retryCount > this.options.maxRetries) {\n console.log(\"[SatMouse] Max retries exceeded, giving up\");\n this.setState(\"failed\", \"none\");\n return;\n }\n\n this.clearReconnect();\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null;\n this.connect();\n }, this.options.reconnectDelay);\n }\n\n private clearReconnect(): void {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n }\n}\n","const SCHEME_URL = \"satmouse://launch\";\nconst PROJECT_URL = \"https://github.com/kelnishi/SatMouse/releases/latest\";\n\n/** Result of a URI scheme negotiation */\nexport interface NegotiateResult {\n ip: string;\n wsPort: number;\n wtPort: number;\n httpsPort: number;\n certHash?: string;\n challenge?: string;\n}\n\n/**\n * Trigger the satmouse://negotiate URI scheme for discovery when direct\n * HTTP/HTTPS fetch is blocked (Safari LNA).\n *\n * The bridge intercepts the URI, then redirects back to your origin with\n * connection details as query parameters. Your app handles the callback\n * route and passes the result to SatMouseConnection.\n *\n * @param origin - Your app's origin (e.g., \"https://kelcite.app\")\n * @param callbackPath - Path the bridge redirects to (default: \"/satmouse-handshake\")\n * @param challenge - Optional challenge token for verification\n */\nexport function negotiateViaSatMouse(origin: string, callbackPath = \"/satmouse-handshake\", challenge?: string): void {\n const params = new URLSearchParams({ origin, callback: callbackPath });\n if (challenge) params.set(\"challenge\", challenge);\n globalThis.location.href = `satmouse://negotiate?${params}`;\n}\n\n/**\n * Parse the negotiate callback URL parameters (called on your callback route).\n * Returns connection details or null if the params are missing.\n */\nexport function parseNegotiateCallback(searchParams: URLSearchParams): NegotiateResult | null {\n const ip = searchParams.get(\"ip\");\n const wsPort = searchParams.get(\"wsPort\");\n if (!ip || !wsPort) return null;\n return {\n ip,\n wsPort: parseInt(wsPort, 10),\n wtPort: parseInt(searchParams.get(\"wtPort\") ?? \"18946\", 10),\n httpsPort: parseInt(searchParams.get(\"httpsPort\") ?? \"18947\", 10),\n certHash: searchParams.get(\"certHash\") ?? undefined,\n challenge: searchParams.get(\"challenge\") ?? undefined,\n };\n}\n\nexport interface LaunchOptions {\n /** URL scheme to open. Default: \"satmouse://launch\" */\n schemeUrl?: string;\n /** Fallback URL if the app is not installed. Default: GitHub releases page */\n fallbackUrl?: string;\n /** Timeout in ms before assuming the app is not installed. Default: 2500 */\n timeout?: number;\n}\n\n/**\n * Attempt to launch SatMouse via the `satmouse://` URL scheme.\n *\n * If the app is installed and registered, the OS opens it. If not,\n * navigates to the fallback URL (project releases page) after a timeout.\n *\n * Returns true if the scheme likely opened, false if it fell back.\n */\nexport function launchSatMouse(options?: LaunchOptions): Promise<boolean> {\n const schemeUrl = options?.schemeUrl ?? SCHEME_URL;\n const timeout = options?.timeout ?? 2500;\n\n // Validate fallback URL — only allow http/https to prevent javascript: or data: injection\n let fallbackUrl = PROJECT_URL;\n if (options?.fallbackUrl) {\n try {\n const parsed = new URL(options.fallbackUrl);\n if (parsed.protocol === \"http:\" || parsed.protocol === \"https:\") {\n fallbackUrl = options.fallbackUrl;\n }\n } catch {}\n }\n\n return new Promise((resolve) => {\n // Track if we leave the page (scheme handler opened the app)\n let launched = false;\n\n const onBlur = () => {\n launched = true;\n };\n globalThis.addEventListener(\"blur\", onBlur);\n\n // Use a hidden iframe to trigger the scheme without navigating away\n const iframe = document.createElement(\"iframe\");\n iframe.style.display = \"none\";\n iframe.src = schemeUrl;\n document.body.appendChild(iframe);\n\n setTimeout(() => {\n globalThis.removeEventListener(\"blur\", onBlur);\n document.body.removeChild(iframe);\n\n if (launched || document.hidden) {\n resolve(true);\n } else {\n // App not installed — redirect to project page\n globalThis.location.href = fallbackUrl;\n resolve(false);\n }\n }, timeout);\n });\n}\n"]}
@@ -305,7 +305,10 @@ var SatMouseConnection = class extends TypedEmitter {
305
305
  }
306
306
  if (!wtUrl && !wsUrl) {
307
307
  const tdUrl = this.options.tdUrl;
308
- const tdUrls = tdUrl ? [tdUrl] : ["https://localhost:18947/td.json", "http://localhost:18945/td.json"];
308
+ const tdUrls = tdUrl ? [tdUrl] : [
309
+ "https://127.0.0.1:18947/td.json",
310
+ "http://127.0.0.1:18945/td.json"
311
+ ];
309
312
  let resolved = false;
310
313
  for (const url of tdUrls) {
311
314
  try {
@@ -322,7 +325,7 @@ var SatMouseConnection = class extends TypedEmitter {
322
325
  }
323
326
  if (!resolved) {
324
327
  this.emit("error", new Error("Failed to fetch Thing Description"));
325
- wsUrl = "ws://localhost:18945/spatial";
328
+ wsUrl = "ws://127.0.0.1:18945/spatial";
326
329
  }
327
330
  }
328
331
  for (const proto of this.options.transports) {