@bian-womp/spark-remote 0.2.50 → 0.2.52

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;
@@ -347,6 +414,7 @@ async function createRuntimeAdapter(createRegistry, send, extensions) {
347
414
  },
348
415
  snapshotFull: () => {
349
416
  const snap = originalApi.snapshot();
417
+ const extData = originalApi.getExtData();
350
418
  const env = graphRuntime?.getEnvironment?.() ?? {};
351
419
  const def = graphRuntime?.getGraphDef();
352
420
  return {
@@ -354,6 +422,7 @@ async function createRuntimeAdapter(createRegistry, send, extensions) {
354
422
  environment: env,
355
423
  inputs: snap.inputs,
356
424
  outputs: snap.outputs,
425
+ ui: extData?.ui || undefined,
357
426
  };
358
427
  },
359
428
  applySnapshotFull: async (payload) => {
@@ -418,15 +487,6 @@ async function createRuntimeAdapter(createRegistry, send, extensions) {
418
487
  }
419
488
  graphRuntime.setEnvironment(env);
420
489
  },
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
490
  setInputs: (nodeId, inputs) => {
431
491
  if (engine) {
432
492
  engine.setInputs(nodeId, inputs);
@@ -526,7 +586,6 @@ async function createRuntimeAdapter(createRegistry, send, extensions) {
526
586
  describeRegistry: wrapMethod("describeRegistry", originalApi.describeRegistry),
527
587
  update: wrapMethod("update", originalApi.update),
528
588
  setEnvironment: wrapMethod("setEnvironment", originalApi.setEnvironment),
529
- setInput: wrapMethod("setInput", originalApi.setInput),
530
589
  setInputs: wrapMethod("setInputs", originalApi.setInputs),
531
590
  triggerExternal: wrapMethod("triggerExternal", originalApi.triggerExternal),
532
591
  step: wrapMethod("step", originalApi.step),
@@ -576,11 +635,6 @@ class RemoteEngine {
576
635
  },
577
636
  });
578
637
  }
579
- setInput(nodeId, handle, value) {
580
- this.transport.send({
581
- message: { type: "SetInput", payload: { nodeId, handle, value } },
582
- });
583
- }
584
638
  // Batch inputs for a single network round-trip
585
639
  setInputs(nodeId, inputs) {
586
640
  this.transport.send({
@@ -739,7 +793,13 @@ class RuntimeApiClient {
739
793
  if (!WebSocketTransport) {
740
794
  throw new Error("WebSocketTransport not available");
741
795
  }
742
- return new WebSocketTransport(this.config.url);
796
+ return new WebSocketTransport(this.config.url, {
797
+ onConnectionLost: () => {
798
+ this.dispose().catch((err) => {
799
+ console.warn("[RuntimeApiClient] Error disposing on connection loss:", err);
800
+ });
801
+ },
802
+ });
743
803
  }
744
804
  else if (kind === "remote-unix") {
745
805
  // Dynamic import to avoid bundling in browser builds
@@ -985,6 +1045,12 @@ class RuntimeApiClient {
985
1045
  message: { type: "Flush" },
986
1046
  });
987
1047
  }
1048
+ async setExtData(data) {
1049
+ const transport = await this.ensureConnected();
1050
+ await transport.request({
1051
+ message: { type: "SetExtData", payload: data },
1052
+ });
1053
+ }
988
1054
  /**
989
1055
  * Dispose the client and close the transport connection.
990
1056
  * Idempotent: safe to call multiple times.
@@ -1096,16 +1162,6 @@ class RuntimeApiServer {
1096
1162
  ack();
1097
1163
  break;
1098
1164
  }
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
1165
  case "SetInputs": {
1110
1166
  this.logCommand("SetInputs", env, {
1111
1167
  nodeId: msg.payload.nodeId,
@@ -1218,15 +1274,14 @@ class RuntimeApiServer {
1218
1274
  ack();
1219
1275
  break;
1220
1276
  }
1221
- case "Dispose": {
1222
- this.logCommand("Dispose", env);
1223
- this.runtimeApi.dispose();
1277
+ case "SetExtData": {
1278
+ this.runtimeApi.setExtData(msg.payload);
1224
1279
  ack();
1225
1280
  break;
1226
1281
  }
1227
- case "Pause":
1228
- case "Resume": {
1229
- this.logCommand(`${msg.type} (not-impl)`, env);
1282
+ case "Dispose": {
1283
+ this.logCommand("Dispose", env);
1284
+ this.runtimeApi.dispose();
1230
1285
  ack();
1231
1286
  break;
1232
1287
  }