@bian-womp/spark-remote 0.2.49 → 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,9 +584,11 @@ 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),
589
+ step: wrapMethod("step", originalApi.step),
590
+ computeNode: wrapMethod("computeNode", originalApi.computeNode),
591
+ flush: wrapMethod("flush", originalApi.flush),
532
592
  launch: wrapMethod("launch", originalApi.launch),
533
593
  whenIdle: wrapMethod("whenIdle", originalApi.whenIdle),
534
594
  dispose: wrapMethod("dispose", originalApi.dispose),
@@ -573,11 +633,6 @@ class RemoteEngine {
573
633
  },
574
634
  });
575
635
  }
576
- setInput(nodeId, handle, value) {
577
- this.transport.send({
578
- message: { type: "SetInput", payload: { nodeId, handle, value } },
579
- });
580
- }
581
636
  // Batch inputs for a single network round-trip
582
637
  setInputs(nodeId, inputs) {
583
638
  this.transport.send({
@@ -736,7 +791,13 @@ class RuntimeApiClient {
736
791
  if (!WebSocketTransport) {
737
792
  throw new Error("WebSocketTransport not available");
738
793
  }
739
- 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
+ });
740
801
  }
741
802
  else if (kind === "remote-unix") {
742
803
  // Dynamic import to avoid bundling in browser builds
@@ -1093,16 +1154,6 @@ class RuntimeApiServer {
1093
1154
  ack();
1094
1155
  break;
1095
1156
  }
1096
- case "SetInput": {
1097
- this.logCommand("SetInput", env, {
1098
- nodeId: msg.payload.nodeId,
1099
- handle: msg.payload.handle,
1100
- value: summarize(msg.payload.value),
1101
- });
1102
- this.runtimeApi.setInput(msg.payload.nodeId, msg.payload.handle, msg.payload.value);
1103
- ack();
1104
- break;
1105
- }
1106
1157
  case "SetInputs": {
1107
1158
  this.logCommand("SetInputs", env, {
1108
1159
  nodeId: msg.payload.nodeId,
@@ -1221,12 +1272,6 @@ class RuntimeApiServer {
1221
1272
  ack();
1222
1273
  break;
1223
1274
  }
1224
- case "Pause":
1225
- case "Resume": {
1226
- this.logCommand(`${msg.type} (not-impl)`, env);
1227
- ack();
1228
- break;
1229
- }
1230
1275
  default: {
1231
1276
  this.logCommand("Unknown type", env);
1232
1277
  ack();