@nextclaw/remote 0.1.19 → 0.1.20

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.
Files changed (2) hide show
  1. package/dist/index.js +130 -119
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -405,17 +405,9 @@ var RemoteRelayBridge = class {
405
405
 
406
406
  // src/remote-app.adapter.ts
407
407
  import WebSocket2 from "ws";
408
- function toWebSocketUrl(origin, path) {
409
- const normalizedOrigin = origin.replace(/\/$/, "");
410
- if (normalizedOrigin.startsWith("https://")) {
411
- return `${normalizedOrigin.replace(/^https:/, "wss:")}${path}`;
412
- }
413
- if (normalizedOrigin.startsWith("http://")) {
414
- return `${normalizedOrigin.replace(/^http:/, "ws:")}${path}`;
415
- }
416
- return `${normalizedOrigin}${path}`;
417
- }
418
- function parseSseFrame(frame) {
408
+
409
+ // src/remote-app-stream.ts
410
+ function parseRemoteSseFrame(frame) {
419
411
  const lines = frame.split("\n");
420
412
  let event = "";
421
413
  const dataLines = [];
@@ -440,16 +432,100 @@ function parseSseFrame(frame) {
440
432
  return { event };
441
433
  }
442
434
  try {
443
- return {
444
- event,
445
- payload: JSON.parse(data)
446
- };
435
+ return { event, payload: JSON.parse(data) };
447
436
  } catch {
448
- return {
449
- event,
450
- payload: data
451
- };
437
+ return { event, payload: data };
438
+ }
439
+ }
440
+ function readRemoteStreamError(payload, fallback) {
441
+ if (typeof payload === "object" && payload && "error" in payload) {
442
+ const typed = payload;
443
+ if (typed.error?.message) {
444
+ return typed.error.message;
445
+ }
446
+ }
447
+ if (typeof payload === "string" && payload.trim()) {
448
+ return payload.trim();
449
+ }
450
+ return fallback;
451
+ }
452
+ function processRemoteStreamFrame(params) {
453
+ const frame = parseRemoteSseFrame(params.rawFrame);
454
+ if (!frame) {
455
+ return;
456
+ }
457
+ if (frame.event === "final") {
458
+ params.setFinalResult(frame.payload);
459
+ return;
460
+ }
461
+ if (frame.event === "error") {
462
+ throw new Error(readRemoteStreamError(frame.payload, "stream failed"));
463
+ }
464
+ params.onEvent(frame);
465
+ }
466
+ function flushRemoteStreamFrames(params) {
467
+ let boundary = params.bufferState.value.indexOf("\n\n");
468
+ while (boundary !== -1) {
469
+ processRemoteStreamFrame({
470
+ rawFrame: params.bufferState.value.slice(0, boundary),
471
+ onEvent: params.onEvent,
472
+ setFinalResult: params.setFinalResult
473
+ });
474
+ params.bufferState.value = params.bufferState.value.slice(boundary + 2);
475
+ boundary = params.bufferState.value.indexOf("\n\n");
476
+ }
477
+ }
478
+ async function readRemoteAppStreamResult(params) {
479
+ const reader = params.response.body?.getReader();
480
+ if (!reader) {
481
+ throw new Error("SSE response body unavailable.");
482
+ }
483
+ const decoder = new TextDecoder();
484
+ const bufferState = { value: "" };
485
+ let finalResult = void 0;
486
+ try {
487
+ while (true) {
488
+ const { value, done } = await reader.read();
489
+ if (done) {
490
+ break;
491
+ }
492
+ bufferState.value += decoder.decode(value, { stream: true });
493
+ flushRemoteStreamFrames({
494
+ bufferState,
495
+ onEvent: params.onEvent,
496
+ setFinalResult: (nextValue) => {
497
+ finalResult = nextValue;
498
+ }
499
+ });
500
+ }
501
+ if (bufferState.value.trim()) {
502
+ processRemoteStreamFrame({
503
+ rawFrame: bufferState.value,
504
+ onEvent: params.onEvent,
505
+ setFinalResult: (nextValue) => {
506
+ finalResult = nextValue;
507
+ }
508
+ });
509
+ }
510
+ } finally {
511
+ reader.releaseLock();
512
+ }
513
+ if (finalResult === void 0) {
514
+ throw new Error("stream ended without final event");
452
515
  }
516
+ return finalResult;
517
+ }
518
+
519
+ // src/remote-app.adapter.ts
520
+ function toWebSocketUrl(origin, path) {
521
+ const normalizedOrigin = origin.replace(/\/$/, "");
522
+ if (normalizedOrigin.startsWith("https://")) {
523
+ return `${normalizedOrigin.replace(/^https:/, "wss:")}${path}`;
524
+ }
525
+ if (normalizedOrigin.startsWith("http://")) {
526
+ return `${normalizedOrigin.replace(/^http:/, "ws:")}${path}`;
527
+ }
528
+ return `${normalizedOrigin}${path}`;
453
529
  }
454
530
  function readErrorMessage(body, fallback) {
455
531
  if (typeof body === "object" && body && "error" in body) {
@@ -524,113 +600,28 @@ var RemoteAppAdapter = class {
524
600
  const controller = new AbortController();
525
601
  this.activeStreams.set(frame.streamId, controller);
526
602
  try {
527
- const bridgeCookie = await this.relayBridge.requestBridgeCookie();
528
- const response = await fetch(new URL(frame.target.path, this.localOrigin), {
529
- method: frame.target.method,
530
- headers: this.createStreamHeaders(bridgeCookie),
531
- body: this.buildRequestBody(frame.target),
532
- signal: controller.signal
533
- });
534
- if (!response.ok) {
535
- const errorBody = await this.readResponseBody(response);
536
- this.send({
537
- type: "client.stream.error",
538
- clientId: frame.clientId,
539
- streamId: frame.streamId,
540
- message: readErrorMessage(errorBody, `HTTP ${response.status}`)
541
- });
603
+ const response = await this.openStreamResponse(frame, controller);
604
+ if (!response) {
542
605
  return;
543
606
  }
544
- const reader = response.body?.getReader();
545
- if (!reader) {
546
- this.send({
547
- type: "client.stream.error",
548
- clientId: frame.clientId,
549
- streamId: frame.streamId,
550
- message: "SSE response body unavailable."
551
- });
552
- return;
553
- }
554
- const decoder = new TextDecoder();
555
- let buffer = "";
556
- let finalResult;
557
- try {
558
- while (true) {
559
- const { value, done } = await reader.read();
560
- if (done) {
561
- break;
562
- }
563
- buffer += decoder.decode(value, { stream: true });
564
- let boundary = buffer.indexOf("\n\n");
565
- while (boundary !== -1) {
566
- const frameEvent = parseSseFrame(buffer.slice(0, boundary));
567
- buffer = buffer.slice(boundary + 2);
568
- if (frameEvent) {
569
- if (frameEvent.event === "final") {
570
- finalResult = frameEvent.payload;
571
- } else if (frameEvent.event === "error") {
572
- this.send({
573
- type: "client.stream.error",
574
- clientId: frame.clientId,
575
- streamId: frame.streamId,
576
- message: readErrorMessage(frameEvent.payload, "stream failed")
577
- });
578
- return;
579
- } else {
580
- this.send({
581
- type: "client.stream.event",
582
- clientId: frame.clientId,
583
- streamId: frame.streamId,
584
- event: frameEvent.event,
585
- payload: frameEvent.payload
586
- });
587
- }
588
- }
589
- boundary = buffer.indexOf("\n\n");
590
- }
591
- }
592
- if (buffer.trim()) {
593
- const frameEvent = parseSseFrame(buffer);
594
- if (frameEvent) {
595
- if (frameEvent.event === "final") {
596
- finalResult = frameEvent.payload;
597
- } else if (frameEvent.event === "error") {
598
- this.send({
599
- type: "client.stream.error",
600
- clientId: frame.clientId,
601
- streamId: frame.streamId,
602
- message: readErrorMessage(frameEvent.payload, "stream failed")
603
- });
604
- return;
605
- } else {
606
- this.send({
607
- type: "client.stream.event",
608
- clientId: frame.clientId,
609
- streamId: frame.streamId,
610
- event: frameEvent.event,
611
- payload: frameEvent.payload
612
- });
613
- }
614
- }
615
- }
616
- if (finalResult === void 0) {
607
+ const finalResult = await readRemoteAppStreamResult({
608
+ response,
609
+ onEvent: (event) => {
617
610
  this.send({
618
- type: "client.stream.error",
611
+ type: "client.stream.event",
619
612
  clientId: frame.clientId,
620
613
  streamId: frame.streamId,
621
- message: "stream ended without final event"
614
+ event: event.event,
615
+ payload: event.payload
622
616
  });
623
- return;
624
617
  }
625
- this.send({
626
- type: "client.stream.end",
627
- clientId: frame.clientId,
628
- streamId: frame.streamId,
629
- result: finalResult
630
- });
631
- } finally {
632
- reader.releaseLock();
633
- }
618
+ });
619
+ this.send({
620
+ type: "client.stream.end",
621
+ clientId: frame.clientId,
622
+ streamId: frame.streamId,
623
+ result: finalResult
624
+ });
634
625
  } catch (error) {
635
626
  if (controller.signal.aborted) {
636
627
  return;
@@ -645,6 +636,26 @@ var RemoteAppAdapter = class {
645
636
  this.activeStreams.delete(frame.streamId);
646
637
  }
647
638
  }
639
+ async openStreamResponse(frame, controller) {
640
+ const bridgeCookie = await this.relayBridge.requestBridgeCookie();
641
+ const response = await fetch(new URL(frame.target.path, this.localOrigin), {
642
+ method: frame.target.method,
643
+ headers: this.createStreamHeaders(bridgeCookie),
644
+ body: this.buildRequestBody(frame.target),
645
+ signal: controller.signal
646
+ });
647
+ if (response.ok) {
648
+ return response;
649
+ }
650
+ const errorBody = await this.readResponseBody(response);
651
+ this.send({
652
+ type: "client.stream.error",
653
+ clientId: frame.clientId,
654
+ streamId: frame.streamId,
655
+ message: readErrorMessage(errorBody, `HTTP ${response.status}`)
656
+ });
657
+ return null;
658
+ }
648
659
  async ensureEventSocket() {
649
660
  if (this.localEventSocket && this.localEventSocket.readyState === WebSocket2.OPEN) {
650
661
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/remote",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "private": false,
5
5
  "description": "Remote access runtime for NextClaw device registration, relay bridging, and service-managed connectivity.",
6
6
  "type": "module",
@@ -30,8 +30,8 @@
30
30
  "dependencies": {
31
31
  "commander": "^12.1.0",
32
32
  "ws": "^8.18.0",
33
- "@nextclaw/core": "0.9.8",
34
- "@nextclaw/server": "0.10.23"
33
+ "@nextclaw/server": "0.10.24",
34
+ "@nextclaw/core": "0.9.8"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/node": "^20.17.6",