@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/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,7 +582,6 @@ 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),
530
587
  step: wrapMethod("step", originalApi.step),
@@ -574,11 +631,6 @@ class RemoteEngine {
574
631
  },
575
632
  });
576
633
  }
577
- setInput(nodeId, handle, value) {
578
- this.transport.send({
579
- message: { type: "SetInput", payload: { nodeId, handle, value } },
580
- });
581
- }
582
634
  // Batch inputs for a single network round-trip
583
635
  setInputs(nodeId, inputs) {
584
636
  this.transport.send({
@@ -737,7 +789,13 @@ class RuntimeApiClient {
737
789
  if (!WebSocketTransport) {
738
790
  throw new Error("WebSocketTransport not available");
739
791
  }
740
- 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
+ });
741
799
  }
742
800
  else if (kind === "remote-unix") {
743
801
  // Dynamic import to avoid bundling in browser builds
@@ -1094,16 +1152,6 @@ class RuntimeApiServer {
1094
1152
  ack();
1095
1153
  break;
1096
1154
  }
1097
- case "SetInput": {
1098
- this.logCommand("SetInput", env, {
1099
- nodeId: msg.payload.nodeId,
1100
- handle: msg.payload.handle,
1101
- value: summarize(msg.payload.value),
1102
- });
1103
- this.runtimeApi.setInput(msg.payload.nodeId, msg.payload.handle, msg.payload.value);
1104
- ack();
1105
- break;
1106
- }
1107
1155
  case "SetInputs": {
1108
1156
  this.logCommand("SetInputs", env, {
1109
1157
  nodeId: msg.payload.nodeId,
@@ -1222,12 +1270,6 @@ class RuntimeApiServer {
1222
1270
  ack();
1223
1271
  break;
1224
1272
  }
1225
- case "Pause":
1226
- case "Resume": {
1227
- this.logCommand(`${msg.type} (not-impl)`, env);
1228
- ack();
1229
- break;
1230
- }
1231
1273
  default: {
1232
1274
  this.logCommand("Unknown type", env);
1233
1275
  ack();