@kelnishi/satmouse-client 0.14.3 → 0.15.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.
@@ -252,6 +252,114 @@ var WebSocketAdapter = class {
252
252
  }
253
253
  };
254
254
 
255
+ // src/core/transports/webrtc.ts
256
+ var WebRTCAdapter = class {
257
+ protocol = "webrtc";
258
+ onSpatialData = null;
259
+ onButtonEvent = null;
260
+ onDeviceStatus = null;
261
+ onClose = null;
262
+ onError = null;
263
+ pc = null;
264
+ signalingUrl;
265
+ /**
266
+ * @param signalingUrl — HTTP endpoint for SDP exchange (e.g., "http://127.0.0.1:18945/rtc/offer")
267
+ */
268
+ constructor(signalingUrl) {
269
+ this.signalingUrl = signalingUrl;
270
+ }
271
+ async connect() {
272
+ if (typeof globalThis.RTCPeerConnection === "undefined") {
273
+ throw new Error("RTCPeerConnection not available");
274
+ }
275
+ this.pc = new RTCPeerConnection({
276
+ iceServers: []
277
+ // Local network, no STUN/TURN
278
+ });
279
+ this.pc.ondatachannel = (event) => {
280
+ const channel = event.channel;
281
+ if (channel.label === "spatial") {
282
+ channel.binaryType = "arraybuffer";
283
+ channel.onmessage = (e) => {
284
+ if (e.data instanceof ArrayBuffer && e.data.byteLength >= 20) {
285
+ const decoded = decodeBinaryFrame(e.data);
286
+ this.onSpatialData?.(decoded);
287
+ }
288
+ };
289
+ } else if (channel.label === "reliable") {
290
+ channel.onmessage = (e) => {
291
+ try {
292
+ const text = typeof e.data === "string" ? e.data : new TextDecoder().decode(e.data);
293
+ const msg = JSON.parse(text);
294
+ if (msg.type === "buttonEvent") this.onButtonEvent?.(msg.data);
295
+ else if (msg.type === "deviceStatus") this.onDeviceStatus?.(msg.data.event, msg.data.device);
296
+ } catch {
297
+ }
298
+ };
299
+ }
300
+ };
301
+ this.pc.onconnectionstatechange = () => {
302
+ const state = this.pc?.connectionState;
303
+ if (state === "disconnected" || state === "failed" || state === "closed") {
304
+ this.onClose?.();
305
+ }
306
+ };
307
+ const offer = await this.pc.createOffer();
308
+ await this.pc.setLocalDescription(offer);
309
+ await this.waitForICE();
310
+ const response = await fetch(this.signalingUrl, {
311
+ method: "POST",
312
+ body: this.pc.localDescription.sdp,
313
+ headers: { "Content-Type": "application/sdp" }
314
+ });
315
+ if (!response.ok) {
316
+ throw new Error(`Signaling failed: ${response.status}`);
317
+ }
318
+ const answerSdp = await response.text();
319
+ await this.pc.setRemoteDescription({ type: "answer", sdp: answerSdp });
320
+ await new Promise((resolve, reject) => {
321
+ const timeout = setTimeout(() => reject(new Error("WebRTC connection timeout")), 1e4);
322
+ const check = () => {
323
+ if (this.pc?.connectionState === "connected") {
324
+ clearTimeout(timeout);
325
+ resolve();
326
+ } else if (this.pc?.connectionState === "failed") {
327
+ clearTimeout(timeout);
328
+ reject(new Error("WebRTC connection failed"));
329
+ }
330
+ };
331
+ this.pc.onconnectionstatechange = () => {
332
+ check();
333
+ const state = this.pc?.connectionState;
334
+ if (state === "disconnected" || state === "failed" || state === "closed") {
335
+ this.onClose?.();
336
+ }
337
+ };
338
+ check();
339
+ });
340
+ }
341
+ close() {
342
+ try {
343
+ this.pc?.close();
344
+ } catch {
345
+ }
346
+ this.pc = null;
347
+ }
348
+ waitForICE() {
349
+ return new Promise((resolve) => {
350
+ if (!this.pc) return resolve();
351
+ if (this.pc.iceGatheringState === "complete") return resolve();
352
+ const timeout = setTimeout(resolve, 2e3);
353
+ this.pc.onicegatheringstatechange = () => {
354
+ if (this.pc?.iceGatheringState === "complete") {
355
+ clearTimeout(timeout);
356
+ resolve();
357
+ }
358
+ };
359
+ });
360
+ }
361
+ };
362
+
255
363
  // src/core/connection.ts
256
364
  function parseSatMouseUri(uri) {
257
365
  const url = new URL(uri);
@@ -265,7 +373,7 @@ function parseSatMouseUri(uri) {
265
373
  };
266
374
  }
267
375
  var DEFAULT_OPTIONS = {
268
- transports: ["webtransport", "websocket"],
376
+ transports: ["webtransport", "webrtc", "websocket"],
269
377
  reconnectDelay: 2e3,
270
378
  maxRetries: 3,
271
379
  wsSubprotocol: "satmouse-json"
@@ -337,6 +445,16 @@ var SatMouseConnection = class extends TypedEmitter {
337
445
  continue;
338
446
  }
339
447
  }
448
+ if (proto === "webrtc") {
449
+ try {
450
+ if (typeof globalThis.RTCPeerConnection === "undefined") continue;
451
+ const rtcUrl = this.options.rtcUrl ?? `http://127.0.0.1:18945/rtc/offer`;
452
+ const adapter = new WebRTCAdapter(rtcUrl);
453
+ if (await this.tryTransport(adapter)) return;
454
+ } catch {
455
+ continue;
456
+ }
457
+ }
340
458
  if (proto === "websocket" && wsUrl) {
341
459
  try {
342
460
  const adapter = new WebSocketAdapter(wsUrl, this.options.wsSubprotocol);