@nextclaw/remote 0.1.26 → 0.1.27
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.d.ts +6 -0
- package/dist/index.js +104 -10
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -163,8 +163,14 @@ declare class RemoteConnector {
|
|
|
163
163
|
platformClient: RemotePlatformClient;
|
|
164
164
|
relayBridgeFactory?: (localOrigin: string) => RemoteRelayBridge;
|
|
165
165
|
logger?: RemoteLogger;
|
|
166
|
+
createSocket?: (wsUrl: string) => WebSocket;
|
|
167
|
+
delayFn?: typeof delay;
|
|
168
|
+
random?: () => number;
|
|
166
169
|
});
|
|
167
170
|
private get logger();
|
|
171
|
+
private get delayFn();
|
|
172
|
+
private get random();
|
|
173
|
+
private createSocket;
|
|
168
174
|
private connectOnce;
|
|
169
175
|
private handleSocketMessage;
|
|
170
176
|
private parseRelayFrame;
|
package/dist/index.js
CHANGED
|
@@ -713,13 +713,68 @@ var TERMINAL_REMOTE_ERROR_PATTERNS = [
|
|
|
713
713
|
/missing bearer token/i,
|
|
714
714
|
/token expired/i,
|
|
715
715
|
/token is invalid/i,
|
|
716
|
-
/run "nextclaw login"/i
|
|
716
|
+
/run "nextclaw login"/i,
|
|
717
|
+
/unexpected server response:\s*400/i,
|
|
718
|
+
/unexpected server response:\s*401/i,
|
|
719
|
+
/unexpected server response:\s*403/i,
|
|
720
|
+
/unexpected server response:\s*404/i,
|
|
721
|
+
/invalid url/i,
|
|
722
|
+
/unsupported protocol/i
|
|
717
723
|
];
|
|
718
724
|
function isTerminalRemoteConnectorError(error) {
|
|
719
725
|
const message = error instanceof Error ? error.message : String(error);
|
|
720
726
|
return TERMINAL_REMOTE_ERROR_PATTERNS.some((pattern) => pattern.test(message));
|
|
721
727
|
}
|
|
722
728
|
|
|
729
|
+
// src/remote-connector-retry.utils.ts
|
|
730
|
+
var BASE_RECONNECT_DELAY_MS = 3e3;
|
|
731
|
+
var MAX_RECONNECT_DELAY_MS = 6e4;
|
|
732
|
+
var RECONNECT_JITTER_RATIO = 0.2;
|
|
733
|
+
var MAX_CONSECUTIVE_RECONNECT_FAILURES = 6;
|
|
734
|
+
function resolveReconnectDelayMs(attempt, random) {
|
|
735
|
+
const exponentialDelayMs = Math.min(
|
|
736
|
+
BASE_RECONNECT_DELAY_MS * 2 ** Math.max(0, attempt - 1),
|
|
737
|
+
MAX_RECONNECT_DELAY_MS
|
|
738
|
+
);
|
|
739
|
+
const jitterRatio = (random() * 2 - 1) * RECONNECT_JITTER_RATIO;
|
|
740
|
+
return Math.max(
|
|
741
|
+
BASE_RECONNECT_DELAY_MS,
|
|
742
|
+
Math.round(exponentialDelayMs * (1 + jitterRatio))
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
function formatReconnectDelay(delayMs) {
|
|
746
|
+
const seconds = delayMs / 1e3;
|
|
747
|
+
return Number.isInteger(seconds) ? `${seconds}s` : `${seconds.toFixed(1)}s`;
|
|
748
|
+
}
|
|
749
|
+
function buildReconnectHaltedMessage(message) {
|
|
750
|
+
return `${message} Auto-reconnect stopped after ${MAX_CONSECUTIVE_RECONNECT_FAILURES} consecutive failures to avoid wasting remote requests. Use Remote Access repair or restart the service after checking platform/network availability.`;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// src/remote-connector-websocket-error.utils.ts
|
|
754
|
+
function readRemoteConnectorSocketErrorMessage(event) {
|
|
755
|
+
const typedEvent = event;
|
|
756
|
+
if (typeof typedEvent.message === "string") {
|
|
757
|
+
const directMessage = typedEvent.message.trim();
|
|
758
|
+
if (directMessage.length > 0) {
|
|
759
|
+
return directMessage;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
const nestedError = typedEvent.error;
|
|
763
|
+
if (nestedError instanceof Error && nestedError.message.trim().length > 0) {
|
|
764
|
+
return nestedError.message.trim();
|
|
765
|
+
}
|
|
766
|
+
if (typeof nestedError === "string" && nestedError.trim().length > 0) {
|
|
767
|
+
return nestedError.trim();
|
|
768
|
+
}
|
|
769
|
+
if (typeof nestedError === "object" && nestedError && typeof nestedError.message === "string") {
|
|
770
|
+
const nestedMessage = nestedError.message.trim();
|
|
771
|
+
if (nestedMessage.length > 0) {
|
|
772
|
+
return nestedMessage;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return "Remote connector websocket failed.";
|
|
776
|
+
}
|
|
777
|
+
|
|
723
778
|
// src/remote-connector.ts
|
|
724
779
|
var RemoteConnector = class {
|
|
725
780
|
constructor(deps) {
|
|
@@ -728,9 +783,18 @@ var RemoteConnector = class {
|
|
|
728
783
|
get logger() {
|
|
729
784
|
return this.deps.logger ?? console;
|
|
730
785
|
}
|
|
786
|
+
get delayFn() {
|
|
787
|
+
return this.deps.delayFn ?? delay;
|
|
788
|
+
}
|
|
789
|
+
get random() {
|
|
790
|
+
return this.deps.random ?? Math.random;
|
|
791
|
+
}
|
|
792
|
+
createSocket(wsUrl) {
|
|
793
|
+
return this.deps.createSocket?.(wsUrl) ?? new WebSocket(wsUrl);
|
|
794
|
+
}
|
|
731
795
|
async connectOnce(params) {
|
|
732
796
|
return await new Promise((resolve, reject) => {
|
|
733
|
-
const socket =
|
|
797
|
+
const socket = this.createSocket(params.wsUrl);
|
|
734
798
|
const appAdapter = new RemoteAppAdapter(params.localOrigin, socket);
|
|
735
799
|
let settled = false;
|
|
736
800
|
let aborted = false;
|
|
@@ -796,13 +860,13 @@ var RemoteConnector = class {
|
|
|
796
860
|
appAdapter.stop();
|
|
797
861
|
finishResolve(aborted ? "aborted" : "closed");
|
|
798
862
|
});
|
|
799
|
-
socket.addEventListener("error", () => {
|
|
863
|
+
socket.addEventListener("error", (event) => {
|
|
800
864
|
appAdapter.stop();
|
|
801
865
|
if (aborted) {
|
|
802
866
|
finishResolve("aborted");
|
|
803
867
|
return;
|
|
804
868
|
}
|
|
805
|
-
finishReject(new Error(
|
|
869
|
+
finishReject(new Error(readRemoteConnectorSocketErrorMessage(event)));
|
|
806
870
|
});
|
|
807
871
|
});
|
|
808
872
|
}
|
|
@@ -882,6 +946,7 @@ var RemoteConnector = class {
|
|
|
882
946
|
statusStore?.write(next);
|
|
883
947
|
}
|
|
884
948
|
async runCycle(params) {
|
|
949
|
+
let device = params.device;
|
|
885
950
|
try {
|
|
886
951
|
this.writeRemoteState(params.opts.statusStore, {
|
|
887
952
|
enabled: true,
|
|
@@ -892,7 +957,7 @@ var RemoteConnector = class {
|
|
|
892
957
|
localOrigin: params.context.localOrigin,
|
|
893
958
|
lastError: null
|
|
894
959
|
});
|
|
895
|
-
|
|
960
|
+
device = await this.ensureDevice({ device, context: params.context });
|
|
896
961
|
const wsUrl = `${params.context.platformBase.replace(/^http/i, "ws")}/platform/remote/connect?instanceId=${encodeURIComponent(device.id)}&token=${encodeURIComponent(params.context.token)}`;
|
|
897
962
|
const outcome = await this.connectOnce({
|
|
898
963
|
wsUrl,
|
|
@@ -915,7 +980,12 @@ var RemoteConnector = class {
|
|
|
915
980
|
lastError: null
|
|
916
981
|
});
|
|
917
982
|
}
|
|
918
|
-
return {
|
|
983
|
+
return {
|
|
984
|
+
device,
|
|
985
|
+
outcome: outcome === "aborted" ? "aborted" : "retry",
|
|
986
|
+
retryFailure: false,
|
|
987
|
+
lastError: null
|
|
988
|
+
};
|
|
919
989
|
} catch (error) {
|
|
920
990
|
const message = error instanceof Error ? error.message : String(error);
|
|
921
991
|
this.writeRemoteState(params.opts.statusStore, {
|
|
@@ -929,8 +999,10 @@ var RemoteConnector = class {
|
|
|
929
999
|
});
|
|
930
1000
|
this.logger.error(`Remote connector error: ${message}`);
|
|
931
1001
|
return {
|
|
932
|
-
device
|
|
933
|
-
outcome: isTerminalRemoteConnectorError(error) ? "stop" : "retry"
|
|
1002
|
+
device,
|
|
1003
|
+
outcome: isTerminalRemoteConnectorError(error) ? "stop" : "retry",
|
|
1004
|
+
retryFailure: true,
|
|
1005
|
+
lastError: message
|
|
934
1006
|
};
|
|
935
1007
|
}
|
|
936
1008
|
}
|
|
@@ -942,9 +1014,11 @@ var RemoteConnector = class {
|
|
|
942
1014
|
await relayBridge.ensureLocalUiHealthy();
|
|
943
1015
|
let device = null;
|
|
944
1016
|
let preserveRuntimeError = false;
|
|
1017
|
+
let consecutiveReconnectFailures = 0;
|
|
945
1018
|
while (!opts.signal?.aborted) {
|
|
946
1019
|
const cycle = await this.runCycle({ device, context, relayBridge, opts });
|
|
947
1020
|
device = cycle.device;
|
|
1021
|
+
consecutiveReconnectFailures = cycle.retryFailure ? consecutiveReconnectFailures + 1 : 0;
|
|
948
1022
|
if (cycle.outcome === "stop") {
|
|
949
1023
|
preserveRuntimeError = true;
|
|
950
1024
|
break;
|
|
@@ -952,9 +1026,29 @@ var RemoteConnector = class {
|
|
|
952
1026
|
if (cycle.outcome === "aborted" || !context.autoReconnect || opts.signal?.aborted) {
|
|
953
1027
|
break;
|
|
954
1028
|
}
|
|
955
|
-
|
|
1029
|
+
if (cycle.retryFailure && consecutiveReconnectFailures >= MAX_CONSECUTIVE_RECONNECT_FAILURES) {
|
|
1030
|
+
const haltedMessage = buildReconnectHaltedMessage(
|
|
1031
|
+
cycle.lastError ?? "Remote connector websocket failed."
|
|
1032
|
+
);
|
|
1033
|
+
this.writeRemoteState(opts.statusStore, {
|
|
1034
|
+
enabled: true,
|
|
1035
|
+
state: "error",
|
|
1036
|
+
deviceId: device?.id,
|
|
1037
|
+
deviceName: context.displayName,
|
|
1038
|
+
platformBase: context.platformBase,
|
|
1039
|
+
localOrigin: context.localOrigin,
|
|
1040
|
+
lastError: haltedMessage
|
|
1041
|
+
});
|
|
1042
|
+
this.logger.error(`Remote connector error: ${haltedMessage}`);
|
|
1043
|
+
preserveRuntimeError = true;
|
|
1044
|
+
break;
|
|
1045
|
+
}
|
|
1046
|
+
const reconnectDelayMs = resolveReconnectDelayMs(cycle.retryFailure ? consecutiveReconnectFailures : 1, this.random);
|
|
1047
|
+
this.logger.warn(
|
|
1048
|
+
`Remote connector disconnected. Reconnecting in ${formatReconnectDelay(reconnectDelayMs)}...`
|
|
1049
|
+
);
|
|
956
1050
|
try {
|
|
957
|
-
await
|
|
1051
|
+
await this.delayFn(reconnectDelayMs, opts.signal);
|
|
958
1052
|
} catch {
|
|
959
1053
|
break;
|
|
960
1054
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/remote",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.27",
|
|
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,7 +30,7 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"commander": "^12.1.0",
|
|
32
32
|
"ws": "^8.18.0",
|
|
33
|
-
"@nextclaw/server": "0.10.
|
|
33
|
+
"@nextclaw/server": "0.10.33",
|
|
34
34
|
"@nextclaw/core": "0.9.11"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|