@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.
- package/dist/index.js +130 -119
- 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
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
|
|
450
|
-
|
|
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
|
|
528
|
-
|
|
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
|
|
545
|
-
|
|
546
|
-
|
|
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.
|
|
611
|
+
type: "client.stream.event",
|
|
619
612
|
clientId: frame.clientId,
|
|
620
613
|
streamId: frame.streamId,
|
|
621
|
-
|
|
614
|
+
event: event.event,
|
|
615
|
+
payload: event.payload
|
|
622
616
|
});
|
|
623
|
-
return;
|
|
624
617
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
}
|
|
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.
|
|
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/
|
|
34
|
-
"@nextclaw/
|
|
33
|
+
"@nextclaw/server": "0.10.24",
|
|
34
|
+
"@nextclaw/core": "0.9.8"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^20.17.6",
|