@kelnishi/satmouse-client 0.15.0 → 0.15.2

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.
@@ -262,9 +262,6 @@ var WebRTCAdapter = class {
262
262
  onError = null;
263
263
  pc = null;
264
264
  signalingUrl;
265
- /**
266
- * @param signalingUrl — HTTP endpoint for SDP exchange (e.g., "http://127.0.0.1:18945/rtc/offer")
267
- */
268
265
  constructor(signalingUrl) {
269
266
  this.signalingUrl = signalingUrl;
270
267
  }
@@ -272,18 +269,14 @@ var WebRTCAdapter = class {
272
269
  if (typeof globalThis.RTCPeerConnection === "undefined") {
273
270
  throw new Error("RTCPeerConnection not available");
274
271
  }
275
- this.pc = new RTCPeerConnection({
276
- iceServers: []
277
- // Local network, no STUN/TURN
278
- });
272
+ this.pc = new RTCPeerConnection({ iceServers: [] });
279
273
  this.pc.ondatachannel = (event) => {
280
274
  const channel = event.channel;
281
275
  if (channel.label === "spatial") {
282
276
  channel.binaryType = "arraybuffer";
283
277
  channel.onmessage = (e) => {
284
278
  if (e.data instanceof ArrayBuffer && e.data.byteLength >= 20) {
285
- const decoded = decodeBinaryFrame(e.data);
286
- this.onSpatialData?.(decoded);
279
+ this.onSpatialData?.(decodeBinaryFrame(e.data));
287
280
  }
288
281
  };
289
282
  } else if (channel.label === "reliable") {
@@ -307,23 +300,16 @@ var WebRTCAdapter = class {
307
300
  const offer = await this.pc.createOffer();
308
301
  await this.pc.setLocalDescription(offer);
309
302
  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();
303
+ const answerSdp = await this.exchangeSDP(this.pc.localDescription.sdp);
319
304
  await this.pc.setRemoteDescription({ type: "answer", sdp: answerSdp });
320
305
  await new Promise((resolve, reject) => {
321
306
  const timeout = setTimeout(() => reject(new Error("WebRTC connection timeout")), 1e4);
322
307
  const check = () => {
323
- if (this.pc?.connectionState === "connected") {
308
+ const state = this.pc?.connectionState;
309
+ if (state === "connected") {
324
310
  clearTimeout(timeout);
325
311
  resolve();
326
- } else if (this.pc?.connectionState === "failed") {
312
+ } else if (state === "failed") {
327
313
  clearTimeout(timeout);
328
314
  reject(new Error("WebRTC connection failed"));
329
315
  }
@@ -331,9 +317,7 @@ var WebRTCAdapter = class {
331
317
  this.pc.onconnectionstatechange = () => {
332
318
  check();
333
319
  const state = this.pc?.connectionState;
334
- if (state === "disconnected" || state === "failed" || state === "closed") {
335
- this.onClose?.();
336
- }
320
+ if (state === "disconnected" || state === "failed" || state === "closed") this.onClose?.();
337
321
  };
338
322
  check();
339
323
  });
@@ -345,6 +329,44 @@ var WebRTCAdapter = class {
345
329
  }
346
330
  this.pc = null;
347
331
  }
332
+ /** Exchange SDP: try fetch POST, fall back to popup window */
333
+ async exchangeSDP(offerSdp) {
334
+ try {
335
+ const res = await fetch(this.signalingUrl, {
336
+ method: "POST",
337
+ body: offerSdp,
338
+ headers: { "Content-Type": "application/sdp" }
339
+ });
340
+ if (res.ok) return await res.text();
341
+ } catch {
342
+ }
343
+ return this.exchangeSDPViaPopup(offerSdp);
344
+ }
345
+ exchangeSDPViaPopup(offerSdp) {
346
+ return new Promise((resolve, reject) => {
347
+ const offerB64 = btoa(offerSdp);
348
+ const baseUrl = this.signalingUrl.replace("/rtc/offer", "/rtc/connect-popup");
349
+ const url = `${baseUrl}?offer=${encodeURIComponent(offerB64)}`;
350
+ const popup = globalThis.open(url, "satmouse-rtc", "width=1,height=1,left=-100,top=-100");
351
+ if (!popup) {
352
+ reject(new Error("Popup blocked \u2014 user interaction required"));
353
+ return;
354
+ }
355
+ const timeout = setTimeout(() => {
356
+ popup.close();
357
+ reject(new Error("WebRTC signaling timeout"));
358
+ }, 1e4);
359
+ const onMessage = (event) => {
360
+ if (event.data?.type === "satmouse-rtc-answer") {
361
+ clearTimeout(timeout);
362
+ globalThis.removeEventListener("message", onMessage);
363
+ popup.close();
364
+ resolve(event.data.answer);
365
+ }
366
+ };
367
+ globalThis.addEventListener("message", onMessage);
368
+ });
369
+ }
348
370
  waitForICE() {
349
371
  return new Promise((resolve) => {
350
372
  if (!this.pc) return resolve();
@@ -502,12 +524,18 @@ var SatMouseConnection = class extends TypedEmitter {
502
524
  if (!this.intentionalClose) this.scheduleReconnect();
503
525
  };
504
526
  try {
505
- await adapter.connect();
527
+ await Promise.race([
528
+ adapter.connect(),
529
+ new Promise(
530
+ (_, reject) => setTimeout(() => reject(new Error(`${adapter.protocol} connection timeout`)), 5e3)
531
+ )
532
+ ]);
506
533
  this.transport = adapter;
507
534
  this.retryCount = 0;
508
535
  this.setState("connected", adapter.protocol);
509
536
  return true;
510
537
  } catch (err) {
538
+ adapter.close();
511
539
  this.emit("error", err instanceof Error ? err : new Error(String(err)));
512
540
  return false;
513
541
  }