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