@bian-womp/spark-remote 0.2.50 → 0.2.51

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/lib/cjs/index.cjs CHANGED
@@ -23,22 +23,23 @@ class SeqGenerator {
23
23
 
24
24
  const OPEN = 1;
25
25
  class WebSocketTransport {
26
- constructor(url) {
26
+ constructor(url, options) {
27
27
  this.listeners = new Set();
28
28
  this.seq = new SeqGenerator();
29
+ this.isAlive = false;
30
+ this.PING_INTERVAL_MS = 30000;
31
+ this.PONG_TIMEOUT_MS = 10000;
29
32
  this.baseUrl = url;
33
+ this.onConnectionLost = options?.onConnectionLost;
30
34
  }
31
35
  async connect(options) {
32
36
  if (this.ws && this.ws.readyState === OPEN)
33
37
  return;
34
- // Build URL with connection params
35
- // Handle both ws:///wss:// URLs and http:///https:// URLs (convert to ws)
36
38
  let url;
37
39
  if (this.baseUrl.startsWith("ws://") || this.baseUrl.startsWith("wss://")) {
38
40
  url = new URL(this.baseUrl);
39
41
  }
40
42
  else {
41
- // Convert http/https to ws/wss
42
43
  const wsBaseUrl = this.baseUrl.replace(/^http/, "ws");
43
44
  url = new URL(wsBaseUrl);
44
45
  }
@@ -56,7 +57,10 @@ class WebSocketTransport {
56
57
  await new Promise((resolve, reject) => {
57
58
  if (!this.ws)
58
59
  return reject(new Error("ws init failed"));
59
- this.ws.onopen = () => resolve();
60
+ this.ws.onopen = () => {
61
+ this.startHeartbeat();
62
+ resolve();
63
+ };
60
64
  this.ws.onerror = (e) => reject(e);
61
65
  this.ws.onmessage = (ev) => {
62
66
  try {
@@ -66,10 +70,72 @@ class WebSocketTransport {
66
70
  }
67
71
  catch { }
68
72
  };
73
+ this.ws.onclose = () => {
74
+ this.stopHeartbeat();
75
+ };
69
76
  });
70
77
  }
78
+ startHeartbeat() {
79
+ this.stopHeartbeat();
80
+ this.isAlive = true;
81
+ if (typeof window !== "undefined" || !this.ws)
82
+ return;
83
+ this.pongHandler = () => {
84
+ this.isAlive = true;
85
+ if (this.pongTimeout) {
86
+ clearTimeout(this.pongTimeout);
87
+ this.pongTimeout = undefined;
88
+ }
89
+ };
90
+ this.ws.on("pong", this.pongHandler);
91
+ this.pingInterval = setInterval(() => {
92
+ if (!this.ws || this.ws.readyState !== OPEN) {
93
+ this.stopHeartbeat();
94
+ return;
95
+ }
96
+ if (!this.isAlive) {
97
+ this.onConnectionLost?.();
98
+ this.stopHeartbeat();
99
+ this.ws.terminate();
100
+ return;
101
+ }
102
+ this.isAlive = false;
103
+ try {
104
+ this.ws.ping();
105
+ }
106
+ catch (err) {
107
+ console.warn("[WebSocketTransport] Failed to send ping:", err);
108
+ this.onConnectionLost?.();
109
+ this.stopHeartbeat();
110
+ return;
111
+ }
112
+ this.pongTimeout = setTimeout(() => {
113
+ if (!this.isAlive) {
114
+ this.onConnectionLost?.();
115
+ this.stopHeartbeat();
116
+ if (this.ws) {
117
+ this.ws.terminate();
118
+ }
119
+ }
120
+ }, this.PONG_TIMEOUT_MS);
121
+ }, this.PING_INTERVAL_MS);
122
+ }
123
+ stopHeartbeat() {
124
+ if (this.pingInterval) {
125
+ clearInterval(this.pingInterval);
126
+ this.pingInterval = undefined;
127
+ }
128
+ if (this.pongTimeout) {
129
+ clearTimeout(this.pongTimeout);
130
+ this.pongTimeout = undefined;
131
+ }
132
+ if (this.ws && this.pongHandler) {
133
+ this.ws.off("pong", this.pongHandler);
134
+ this.pongHandler = undefined;
135
+ }
136
+ this.isAlive = false;
137
+ }
71
138
  async request(msg) {
72
- // For now, just send and wait for the next message with matching seq (simple demo)
73
139
  const seq = this.seq.next();
74
140
  const env = { ...msg, seq };
75
141
  const p = new Promise((resolve) => {
@@ -93,6 +159,7 @@ class WebSocketTransport {
93
159
  return () => this.listeners.delete(cb);
94
160
  }
95
161
  async close() {
162
+ this.stopHeartbeat();
96
163
  if (!this.ws)
97
164
  return;
98
165
  const ws = this.ws;
@@ -418,15 +485,6 @@ async function createRuntimeAdapter(createRegistry, send, extensions) {
418
485
  }
419
486
  graphRuntime.setEnvironment(env);
420
487
  },
421
- setInput: (nodeId, handle, value) => {
422
- // If engine exists, use it; otherwise fall back to direct runtime access
423
- if (engine) {
424
- engine.setInput(nodeId, handle, value);
425
- }
426
- else {
427
- graphRuntime?.setInput(nodeId, handle, value);
428
- }
429
- },
430
488
  setInputs: (nodeId, inputs) => {
431
489
  if (engine) {
432
490
  engine.setInputs(nodeId, inputs);
@@ -526,7 +584,6 @@ async function createRuntimeAdapter(createRegistry, send, extensions) {
526
584
  describeRegistry: wrapMethod("describeRegistry", originalApi.describeRegistry),
527
585
  update: wrapMethod("update", originalApi.update),
528
586
  setEnvironment: wrapMethod("setEnvironment", originalApi.setEnvironment),
529
- setInput: wrapMethod("setInput", originalApi.setInput),
530
587
  setInputs: wrapMethod("setInputs", originalApi.setInputs),
531
588
  triggerExternal: wrapMethod("triggerExternal", originalApi.triggerExternal),
532
589
  step: wrapMethod("step", originalApi.step),
@@ -576,11 +633,6 @@ class RemoteEngine {
576
633
  },
577
634
  });
578
635
  }
579
- setInput(nodeId, handle, value) {
580
- this.transport.send({
581
- message: { type: "SetInput", payload: { nodeId, handle, value } },
582
- });
583
- }
584
636
  // Batch inputs for a single network round-trip
585
637
  setInputs(nodeId, inputs) {
586
638
  this.transport.send({
@@ -739,7 +791,13 @@ class RuntimeApiClient {
739
791
  if (!WebSocketTransport) {
740
792
  throw new Error("WebSocketTransport not available");
741
793
  }
742
- return new WebSocketTransport(this.config.url);
794
+ return new WebSocketTransport(this.config.url, {
795
+ onConnectionLost: () => {
796
+ this.dispose().catch((err) => {
797
+ console.warn("[RuntimeApiClient] Error disposing on connection loss:", err);
798
+ });
799
+ },
800
+ });
743
801
  }
744
802
  else if (kind === "remote-unix") {
745
803
  // Dynamic import to avoid bundling in browser builds
@@ -1096,16 +1154,6 @@ class RuntimeApiServer {
1096
1154
  ack();
1097
1155
  break;
1098
1156
  }
1099
- case "SetInput": {
1100
- this.logCommand("SetInput", env, {
1101
- nodeId: msg.payload.nodeId,
1102
- handle: msg.payload.handle,
1103
- value: summarize(msg.payload.value),
1104
- });
1105
- this.runtimeApi.setInput(msg.payload.nodeId, msg.payload.handle, msg.payload.value);
1106
- ack();
1107
- break;
1108
- }
1109
1157
  case "SetInputs": {
1110
1158
  this.logCommand("SetInputs", env, {
1111
1159
  nodeId: msg.payload.nodeId,
@@ -1224,12 +1272,6 @@ class RuntimeApiServer {
1224
1272
  ack();
1225
1273
  break;
1226
1274
  }
1227
- case "Pause":
1228
- case "Resume": {
1229
- this.logCommand(`${msg.type} (not-impl)`, env);
1230
- ack();
1231
- break;
1232
- }
1233
1275
  default: {
1234
1276
  this.logCommand("Unknown type", env);
1235
1277
  ack();