@linkdlab/funcnodes_react_flow 0.1.0
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/README.md +46 -0
- package/package copy.json +63 -0
- package/package.json +75 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +43 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +25 -0
- package/public/robots.txt +3 -0
- package/public/worker_manager +1 -0
- package/src/App.css +38 -0
- package/src/App.test.tsx +9 -0
- package/src/App.tsx +22 -0
- package/src/frontend/datarenderer/images.tsx +28 -0
- package/src/frontend/datarenderer/index.tsx +53 -0
- package/src/frontend/datarenderer/plotly.tsx +82 -0
- package/src/frontend/dialog.scss +88 -0
- package/src/frontend/dialog.tsx +70 -0
- package/src/frontend/edge.scss +15 -0
- package/src/frontend/edge.tsx +31 -0
- package/src/frontend/funcnodesreactflow.scss +63 -0
- package/src/frontend/funcnodesreactflow.tsx +283 -0
- package/src/frontend/header/header.scss +48 -0
- package/src/frontend/header/index.tsx +268 -0
- package/src/frontend/index.tsx +4 -0
- package/src/frontend/layout/htmlelements.scss +63 -0
- package/src/frontend/lib.scss +157 -0
- package/src/frontend/lib.tsx +198 -0
- package/src/frontend/node/index.tsx +3 -0
- package/src/frontend/node/io/default_input_renderer.tsx +327 -0
- package/src/frontend/node/io/default_output_render.tsx +26 -0
- package/src/frontend/node/io/handle_renderer.tsx +89 -0
- package/src/frontend/node/io/index.tsx +4 -0
- package/src/frontend/node/io/io.scss +91 -0
- package/src/frontend/node/io/io.tsx +114 -0
- package/src/frontend/node/io/nodeinput.tsx +125 -0
- package/src/frontend/node/io/nodeoutput.tsx +37 -0
- package/src/frontend/node/node.scss +265 -0
- package/src/frontend/node/node.tsx +208 -0
- package/src/frontend/nodecontextmenu.scss +18 -0
- package/src/frontend/utils/colorpicker.scss +37 -0
- package/src/frontend/utils/colorpicker.tsx +342 -0
- package/src/frontend/utils/jsondata.tsx +19 -0
- package/src/frontend/utils/table.scss +22 -0
- package/src/frontend/utils/table.tsx +159 -0
- package/src/funcnodes/funcnodesworker.ts +455 -0
- package/src/funcnodes/index.ts +4 -0
- package/src/funcnodes/websocketworker.ts +153 -0
- package/src/funcnodes/workermanager.ts +229 -0
- package/src/index.css +13 -0
- package/src/index.tsx +19 -0
- package/src/logo.svg +1 -0
- package/src/react-app-env.d.ts +1 -0
- package/src/reportWebVitals.ts +15 -0
- package/src/setupTests.ts +5 -0
- package/src/state/edge.ts +35 -0
- package/src/state/fnrfzst.ts +440 -0
- package/src/state/index.ts +139 -0
- package/src/state/lib.ts +26 -0
- package/src/state/node.ts +118 -0
- package/src/state/nodespace.ts +151 -0
- package/src/state/reactflow.ts +65 -0
- package/src/types/lib.d.ts +16 -0
- package/src/types/node.d.ts +29 -0
- package/src/types/nodeio.d.ts +82 -0
- package/src/types/worker.d.ts +56 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from "uuid";
|
|
2
|
+
import {
|
|
3
|
+
FuncNodesReactFlowZustandInterface,
|
|
4
|
+
NodeSpaceZustand,
|
|
5
|
+
deep_merge,
|
|
6
|
+
} from "../state";
|
|
7
|
+
import { NodeActionUpdate } from "../state/node"; // Import the missing type
|
|
8
|
+
import { wait } from "@testing-library/user-event/dist/utils";
|
|
9
|
+
type CmdMessage = {
|
|
10
|
+
type: string;
|
|
11
|
+
cmd: string;
|
|
12
|
+
kwargs: any;
|
|
13
|
+
id?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
interface WorkerProps {
|
|
17
|
+
zustand: FuncNodesReactFlowZustandInterface;
|
|
18
|
+
uuid: string;
|
|
19
|
+
on_error?: (error: string | Error) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
class FuncNodesWorker {
|
|
23
|
+
messagePromises: Map<string, any>;
|
|
24
|
+
_zustand: FuncNodesReactFlowZustandInterface;
|
|
25
|
+
_nodeupdates: Map<string, PartialNodeType>;
|
|
26
|
+
_nodeupdatetimer: NodeJS.Timeout;
|
|
27
|
+
uuid: string;
|
|
28
|
+
is_open: boolean;
|
|
29
|
+
on_error: (error: any) => void;
|
|
30
|
+
constructor(data: WorkerProps) {
|
|
31
|
+
this.uuid = data.uuid;
|
|
32
|
+
this.on_error = data.on_error || console.error;
|
|
33
|
+
this.messagePromises = new Map();
|
|
34
|
+
this._zustand = data.zustand;
|
|
35
|
+
this._nodeupdates = new Map();
|
|
36
|
+
this._nodeupdatetimer = setTimeout(() => {
|
|
37
|
+
this.perform_update();
|
|
38
|
+
}, 1000);
|
|
39
|
+
this.is_open = true;
|
|
40
|
+
}
|
|
41
|
+
async fullsync() {
|
|
42
|
+
const resp = (await this._send_cmd({ cmd: "full_state" })) as FullState;
|
|
43
|
+
console.log("Full state", resp);
|
|
44
|
+
this._zustand.lib.libstate.getState().set(resp.backend.lib);
|
|
45
|
+
if (resp.view.renderoptions)
|
|
46
|
+
this._zustand.update_render_options(resp.view.renderoptions);
|
|
47
|
+
const nodeview = resp.view.nodes;
|
|
48
|
+
for (const node of resp.backend.nodes) {
|
|
49
|
+
if (nodeview[node.id] !== undefined) {
|
|
50
|
+
node.frontend = nodeview[node.id];
|
|
51
|
+
}
|
|
52
|
+
this._recieve_node_added(node);
|
|
53
|
+
}
|
|
54
|
+
for (const edge of resp.backend.edges) {
|
|
55
|
+
this._recieve_edge_added(...edge);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async _recieve_edge_added(
|
|
60
|
+
src_nid: string,
|
|
61
|
+
src_ioid: string,
|
|
62
|
+
trg_nid: string,
|
|
63
|
+
trg_ioid: string
|
|
64
|
+
) {
|
|
65
|
+
this._zustand.on_edge_action({
|
|
66
|
+
type: "add",
|
|
67
|
+
from_remote: true,
|
|
68
|
+
...{ src_nid, src_ioid, trg_nid, trg_ioid },
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async trigger_node(node_id: string) {
|
|
73
|
+
await this._send_cmd({
|
|
74
|
+
cmd: "trigger_node",
|
|
75
|
+
kwargs: { nid: node_id },
|
|
76
|
+
wait_for_response: false,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async add_node(node_id: string) {
|
|
81
|
+
const resp = await this._send_cmd({
|
|
82
|
+
cmd: "add_node",
|
|
83
|
+
kwargs: { id: node_id },
|
|
84
|
+
});
|
|
85
|
+
this._recieve_node_added(resp as NodeType);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async remove_node(node_id: string) {
|
|
89
|
+
const resp = await this._send_cmd({
|
|
90
|
+
cmd: "remove_node",
|
|
91
|
+
kwargs: { id: node_id },
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async _recieve_node_added(data: NodeType) {
|
|
96
|
+
this._zustand.on_node_action({
|
|
97
|
+
type: "add",
|
|
98
|
+
node: data,
|
|
99
|
+
id: data.id,
|
|
100
|
+
from_remote: true,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
add_edge({
|
|
105
|
+
src_nid,
|
|
106
|
+
src_ioid,
|
|
107
|
+
trg_nid,
|
|
108
|
+
trg_ioid,
|
|
109
|
+
replace = false,
|
|
110
|
+
}: {
|
|
111
|
+
src_nid: string;
|
|
112
|
+
src_ioid: string;
|
|
113
|
+
trg_nid: string;
|
|
114
|
+
trg_ioid: string;
|
|
115
|
+
replace?: boolean;
|
|
116
|
+
}) {
|
|
117
|
+
return this._send_cmd({
|
|
118
|
+
cmd: "add_edge",
|
|
119
|
+
kwargs: { src_nid, src_ioid, trg_nid, trg_ioid, replace },
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
remove_edge({
|
|
124
|
+
src_nid,
|
|
125
|
+
src_ioid,
|
|
126
|
+
trg_nid,
|
|
127
|
+
trg_ioid,
|
|
128
|
+
}: {
|
|
129
|
+
src_nid: string;
|
|
130
|
+
src_ioid: string;
|
|
131
|
+
trg_nid: string;
|
|
132
|
+
trg_ioid: string;
|
|
133
|
+
}) {
|
|
134
|
+
return this._send_cmd({
|
|
135
|
+
cmd: "remove_edge",
|
|
136
|
+
kwargs: { src_nid, src_ioid, trg_nid, trg_ioid },
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
perform_update() {
|
|
141
|
+
clearTimeout(this._nodeupdatetimer);
|
|
142
|
+
this._nodeupdates.forEach(async (node, id) => {
|
|
143
|
+
const ans = await this._send_cmd({
|
|
144
|
+
cmd: "update_node",
|
|
145
|
+
kwargs: { nid: id, data: node },
|
|
146
|
+
wait_for_response: true,
|
|
147
|
+
});
|
|
148
|
+
this._zustand.on_node_action({
|
|
149
|
+
type: "update",
|
|
150
|
+
node: ans,
|
|
151
|
+
id: id,
|
|
152
|
+
from_remote: true,
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
this._nodeupdates.clear();
|
|
156
|
+
this._nodeupdatetimer = setTimeout(() => {
|
|
157
|
+
this.perform_update();
|
|
158
|
+
}, 1000);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
update_node(action: NodeActionUpdate) {
|
|
162
|
+
// Add the type to the parameter
|
|
163
|
+
const currentstate = this._nodeupdates.get(action.id);
|
|
164
|
+
if (currentstate) {
|
|
165
|
+
const { new_obj, change } = deep_merge(currentstate, action.node);
|
|
166
|
+
if (change) {
|
|
167
|
+
this._nodeupdates.set(action.id, new_obj);
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
this._nodeupdates.set(action.id, action.node);
|
|
171
|
+
}
|
|
172
|
+
if (action.immediate) {
|
|
173
|
+
this.perform_update();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
set_io_value({
|
|
178
|
+
nid,
|
|
179
|
+
ioid,
|
|
180
|
+
value,
|
|
181
|
+
set_default,
|
|
182
|
+
}: {
|
|
183
|
+
nid: string;
|
|
184
|
+
ioid: string;
|
|
185
|
+
value: any;
|
|
186
|
+
set_default?: boolean;
|
|
187
|
+
}) {
|
|
188
|
+
return this._send_cmd({
|
|
189
|
+
cmd: "set_io_value",
|
|
190
|
+
kwargs: { nid, ioid, value, set_default },
|
|
191
|
+
wait_for_response: true,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
clear() {
|
|
196
|
+
return this._send_cmd({ cmd: "clear" });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
save() {
|
|
200
|
+
return this._send_cmd({ cmd: "save", wait_for_response: true });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
load(data: any) {
|
|
204
|
+
return this._send_cmd({
|
|
205
|
+
cmd: "load_data",
|
|
206
|
+
kwargs: { data },
|
|
207
|
+
wait_for_response: true,
|
|
208
|
+
}).then(() => {
|
|
209
|
+
this.fullsync();
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
get_io_value({ nid, ioid }: { nid: string; ioid: string }) {
|
|
214
|
+
return this._send_cmd({
|
|
215
|
+
cmd: "get_io_value",
|
|
216
|
+
kwargs: { nid, ioid },
|
|
217
|
+
wait_for_response: true,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async _send_cmd({
|
|
222
|
+
cmd,
|
|
223
|
+
kwargs,
|
|
224
|
+
wait_for_response = true,
|
|
225
|
+
response_timeout = 5000,
|
|
226
|
+
}: {
|
|
227
|
+
cmd: string;
|
|
228
|
+
kwargs?: any;
|
|
229
|
+
wait_for_response?: boolean;
|
|
230
|
+
response_timeout?: number;
|
|
231
|
+
}) {
|
|
232
|
+
const msg: CmdMessage = {
|
|
233
|
+
type: "cmd",
|
|
234
|
+
cmd,
|
|
235
|
+
kwargs: kwargs || {},
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// await self.assert_connection()
|
|
239
|
+
if (wait_for_response) {
|
|
240
|
+
const msid = msg.id || uuidv4();
|
|
241
|
+
msg.id = msid;
|
|
242
|
+
const promise = new Promise<any>((resolve, reject) => {
|
|
243
|
+
const timeout = setTimeout(() => {
|
|
244
|
+
reject("Timeout");
|
|
245
|
+
}, response_timeout);
|
|
246
|
+
this.messagePromises.set(msid, {
|
|
247
|
+
resolve: (data: any) => {
|
|
248
|
+
clearTimeout(timeout);
|
|
249
|
+
resolve(data);
|
|
250
|
+
this.messagePromises.delete(msid);
|
|
251
|
+
},
|
|
252
|
+
reject: (err: any) => {
|
|
253
|
+
clearTimeout(timeout);
|
|
254
|
+
reject(err);
|
|
255
|
+
this.messagePromises.delete(msid);
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
await this.send(msg);
|
|
260
|
+
return promise;
|
|
261
|
+
}
|
|
262
|
+
return this.send(msg);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async send(data: any) {
|
|
266
|
+
// this is the abstract method that should be implemented by subclasses
|
|
267
|
+
throw new Error("Not implemented");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async recieve_nodespace_event({ event, data }: NodeSpaceEvent) {
|
|
271
|
+
switch (event) {
|
|
272
|
+
case "after_set_value":
|
|
273
|
+
return this._zustand.on_node_action({
|
|
274
|
+
type: "update",
|
|
275
|
+
node: {
|
|
276
|
+
id: data.node,
|
|
277
|
+
io: {
|
|
278
|
+
[data.io]: {
|
|
279
|
+
value: data.result,
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
id: data.node,
|
|
284
|
+
from_remote: true,
|
|
285
|
+
});
|
|
286
|
+
case "after_update_value_options":
|
|
287
|
+
return this._zustand.on_node_action({
|
|
288
|
+
type: "update",
|
|
289
|
+
node: {
|
|
290
|
+
id: data.node,
|
|
291
|
+
io: {
|
|
292
|
+
[data.io]: {
|
|
293
|
+
value_options: data.result,
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
id: data.node,
|
|
298
|
+
from_remote: true,
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
case "triggerstart":
|
|
302
|
+
return this._zustand.on_node_action({
|
|
303
|
+
type: "update",
|
|
304
|
+
node: {
|
|
305
|
+
id: data.node,
|
|
306
|
+
in_trigger: true,
|
|
307
|
+
},
|
|
308
|
+
id: data.node,
|
|
309
|
+
from_remote: true,
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
case "triggerdone":
|
|
313
|
+
return this._zustand.on_node_action({
|
|
314
|
+
type: "update",
|
|
315
|
+
node: {
|
|
316
|
+
id: data.node,
|
|
317
|
+
in_trigger: false,
|
|
318
|
+
},
|
|
319
|
+
id: data.node,
|
|
320
|
+
from_remote: true,
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
case "node_trigger_error":
|
|
324
|
+
return this._zustand.on_node_action({
|
|
325
|
+
type: "error",
|
|
326
|
+
errortype: "trigger",
|
|
327
|
+
error: data.error,
|
|
328
|
+
id: data.node,
|
|
329
|
+
from_remote: true,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
case "node_removed":
|
|
333
|
+
return this._zustand.on_node_action({
|
|
334
|
+
type: "delete",
|
|
335
|
+
id: data.node,
|
|
336
|
+
from_remote: true,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
case "node_added":
|
|
340
|
+
return this._recieve_node_added(data.node as NodeType);
|
|
341
|
+
|
|
342
|
+
case "after_disconnect":
|
|
343
|
+
if (!data.result) return;
|
|
344
|
+
if (!Array.isArray(data.result)) return;
|
|
345
|
+
if (data.result.length !== 4) return;
|
|
346
|
+
return this._zustand.on_edge_action({
|
|
347
|
+
type: "delete",
|
|
348
|
+
from_remote: true,
|
|
349
|
+
src_nid: data.result[0],
|
|
350
|
+
src_ioid: data.result[1],
|
|
351
|
+
trg_nid: data.result[2],
|
|
352
|
+
trg_ioid: data.result[3],
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
case "after_connect":
|
|
356
|
+
if (!data.result) return;
|
|
357
|
+
if (!Array.isArray(data.result)) return;
|
|
358
|
+
if (data.result.length !== 4) return;
|
|
359
|
+
return this._recieve_edge_added(
|
|
360
|
+
...(data.result as [string, string, string, string])
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
case "after_add_shelf":
|
|
364
|
+
if (!data.result) return;
|
|
365
|
+
return this._zustand.lib.libstate.getState().set(data.result);
|
|
366
|
+
|
|
367
|
+
default:
|
|
368
|
+
console.warn("Unhandled nodepsace event", event, data);
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async add_lib(lib: string) {
|
|
374
|
+
const ans = await this._send_cmd({
|
|
375
|
+
cmd: "add_shelf",
|
|
376
|
+
kwargs: { src: lib },
|
|
377
|
+
wait_for_response: false,
|
|
378
|
+
});
|
|
379
|
+
return ans;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async recieve(data: JSONMessage) {
|
|
383
|
+
let promise;
|
|
384
|
+
switch (data.type) {
|
|
385
|
+
case "nsevent":
|
|
386
|
+
return await this.recieve_nodespace_event(data);
|
|
387
|
+
case "result":
|
|
388
|
+
promise = data.id && this.messagePromises.get(data.id);
|
|
389
|
+
if (promise) {
|
|
390
|
+
return promise.resolve(data.result);
|
|
391
|
+
}
|
|
392
|
+
break;
|
|
393
|
+
case "error":
|
|
394
|
+
this.on_error(data.tb + "\n" + data.error);
|
|
395
|
+
promise = data.id && this.messagePromises.get(data.id);
|
|
396
|
+
if (promise) {
|
|
397
|
+
return promise.reject(data.error);
|
|
398
|
+
}
|
|
399
|
+
break;
|
|
400
|
+
case "progress":
|
|
401
|
+
this._zustand.set_progress(data);
|
|
402
|
+
break;
|
|
403
|
+
default:
|
|
404
|
+
console.warn("Unhandled message", data);
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
disconnect() {}
|
|
410
|
+
|
|
411
|
+
onclose() {
|
|
412
|
+
this.is_open = false;
|
|
413
|
+
this._zustand.auto_progress();
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async reconnect() {}
|
|
417
|
+
|
|
418
|
+
async stop() {
|
|
419
|
+
await this._send_cmd({ cmd: "stop_worker", wait_for_response: false });
|
|
420
|
+
const oldonclose = this.onclose.bind(this);
|
|
421
|
+
this.onclose = () => {
|
|
422
|
+
oldonclose();
|
|
423
|
+
if (this._zustand.worker === this) {
|
|
424
|
+
this._zustand.clear_all();
|
|
425
|
+
}
|
|
426
|
+
this.onclose = oldonclose;
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async get_io_full_value({ nid, ioid }: { nid: string; ioid: string }) {
|
|
431
|
+
const res = await this._send_cmd({
|
|
432
|
+
cmd: "get_io_full_value",
|
|
433
|
+
kwargs: { nid, ioid },
|
|
434
|
+
wait_for_response: true,
|
|
435
|
+
});
|
|
436
|
+
//console.log("Full value", res);
|
|
437
|
+
|
|
438
|
+
this._zustand.on_node_action({
|
|
439
|
+
type: "update",
|
|
440
|
+
node: {
|
|
441
|
+
io: {
|
|
442
|
+
[ioid]: {
|
|
443
|
+
fullvalue: res,
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
id: nid,
|
|
448
|
+
from_remote: true,
|
|
449
|
+
});
|
|
450
|
+
return res;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export default FuncNodesWorker;
|
|
455
|
+
export type { WorkerProps };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import FuncNodesWorker, { WorkerProps } from "./funcnodesworker";
|
|
2
|
+
import { FuncNodesReactFlowZustandInterface } from "../state";
|
|
3
|
+
|
|
4
|
+
interface WebSocketWorkerProps extends WorkerProps {
|
|
5
|
+
url: string;
|
|
6
|
+
}
|
|
7
|
+
class WebSocketWorker extends FuncNodesWorker {
|
|
8
|
+
private _url: string;
|
|
9
|
+
private _websocket: WebSocket | null = null;
|
|
10
|
+
private reconnectAttempts: number = 0;
|
|
11
|
+
private maxReconnectAttempts: number = 999;
|
|
12
|
+
private initialTimeout: number = 200; // Initial reconnect delay in ms
|
|
13
|
+
private maxTimeout: number = 5000; // Maximum reconnect delay
|
|
14
|
+
private _reconnect: boolean = true;
|
|
15
|
+
constructor(data: WebSocketWorkerProps) {
|
|
16
|
+
super(data);
|
|
17
|
+
this._url = data.url;
|
|
18
|
+
this.connect();
|
|
19
|
+
this._zustand.auto_progress();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private connect(): void {
|
|
23
|
+
console.log("Connecting to websocket");
|
|
24
|
+
this.is_open = false;
|
|
25
|
+
this._websocket = new WebSocket(this._url);
|
|
26
|
+
|
|
27
|
+
this._websocket.onopen = () => {
|
|
28
|
+
this.onopen();
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
this._websocket.onclose = () => {
|
|
32
|
+
this.onclose();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
this._websocket.onerror = () => {
|
|
36
|
+
this.on_ws_error();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
this._websocket.onmessage = (event) => {
|
|
40
|
+
this.onmessage(event.data);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private calculateReconnectTimeout(): number {
|
|
45
|
+
// Increase timeout exponentially, capped at maxTimeout
|
|
46
|
+
let timeout = Math.min(
|
|
47
|
+
this.initialTimeout * Math.pow(2, this.reconnectAttempts),
|
|
48
|
+
this.maxTimeout
|
|
49
|
+
);
|
|
50
|
+
return timeout;
|
|
51
|
+
}
|
|
52
|
+
private auto_reconnect(): void {
|
|
53
|
+
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
54
|
+
let timeout = this.calculateReconnectTimeout();
|
|
55
|
+
console.log(`Attempting to reconnect in ${timeout} ms`);
|
|
56
|
+
|
|
57
|
+
setTimeout(() => {
|
|
58
|
+
if (this._websocket) {
|
|
59
|
+
if (this._websocket.readyState === WebSocket.OPEN) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
this.reconnectAttempts++;
|
|
64
|
+
this.connect();
|
|
65
|
+
}, timeout);
|
|
66
|
+
} else {
|
|
67
|
+
console.warn("Maximum reconnect attempts reached. Giving up.");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async onmessage(data: string) {
|
|
72
|
+
await this.recieve(JSON.parse(data));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
onopen() {
|
|
76
|
+
console.log("Websocket opened");
|
|
77
|
+
this.is_open = true;
|
|
78
|
+
this._zustand.auto_progress();
|
|
79
|
+
this.reconnectAttempts = 0;
|
|
80
|
+
this.fullsync();
|
|
81
|
+
}
|
|
82
|
+
onclose() {
|
|
83
|
+
console.log("Websocket closed", this);
|
|
84
|
+
super.onclose();
|
|
85
|
+
if (this._reconnect) {
|
|
86
|
+
console.log("Websocket closed,reconnecting");
|
|
87
|
+
this.auto_reconnect(); // Attempt to reconnect
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
on_ws_error() {
|
|
92
|
+
console.warn("Websocket error");
|
|
93
|
+
if (this._websocket) {
|
|
94
|
+
this._websocket.close(); // Ensure the connection is closed before attempting to reconnect
|
|
95
|
+
} else {
|
|
96
|
+
this.auto_reconnect();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async send(data: any) {
|
|
101
|
+
if (!this._websocket || this._websocket.readyState !== WebSocket.OPEN) {
|
|
102
|
+
throw new Error("Websocket not connected");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this._websocket.send(JSON.stringify(data));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async stop() {
|
|
109
|
+
await super.stop();
|
|
110
|
+
this._reconnect = false;
|
|
111
|
+
// this.close();
|
|
112
|
+
}
|
|
113
|
+
close() {
|
|
114
|
+
if (this._websocket) this._websocket.close();
|
|
115
|
+
}
|
|
116
|
+
disconnect() {
|
|
117
|
+
super.disconnect();
|
|
118
|
+
this._reconnect = false;
|
|
119
|
+
this.close();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async reconnect() {
|
|
123
|
+
await super.reconnect();
|
|
124
|
+
this._reconnect = true;
|
|
125
|
+
if (this._websocket) {
|
|
126
|
+
console.log("Reconnecting", this._websocket.readyState);
|
|
127
|
+
if (
|
|
128
|
+
this._websocket.readyState === WebSocket.OPEN ||
|
|
129
|
+
this._websocket.readyState === WebSocket.CONNECTING
|
|
130
|
+
) {
|
|
131
|
+
if (this._websocket.readyState === WebSocket.CONNECTING) {
|
|
132
|
+
//await to ensure the websocket is connected, with a timeout of 2 seconds
|
|
133
|
+
await new Promise((resolve, reject) => {
|
|
134
|
+
let timeout = setTimeout(() => {
|
|
135
|
+
reject("Timeout");
|
|
136
|
+
}, 2000);
|
|
137
|
+
this._websocket?.addEventListener("open", () => {
|
|
138
|
+
clearTimeout(timeout);
|
|
139
|
+
resolve(null);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (this._websocket.readyState === WebSocket.OPEN) {
|
|
144
|
+
this.fullsync();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
this.connect();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export default WebSocketWorker;
|