@xmanrui/dsh-im 3.0.7 → 3.0.8

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.
@@ -0,0 +1,123 @@
1
+ // The client owns callback ordering and reconnects. Hooks run immediately;
2
+ // rejected hook promises also close this subscription, without adding a queue.
3
+ function invoke(callback, value, onError) {
4
+ try {
5
+ const result = callback?.(value);
6
+ if (result && typeof result.then === 'function') Promise.resolve(result).catch(onError);
7
+ } catch (error) {
8
+ onError(error);
9
+ }
10
+ }
11
+
12
+ async function watchInProcessMux({ apiProxy, rpcId, signal, onOpen, onEnvelope }) {
13
+ const controller = new AbortController();
14
+ const close = () => controller.abort(signal.reason);
15
+ let iterator;
16
+ let pending;
17
+ let failure;
18
+ const fail = (error) => {
19
+ if (controller.signal.aborted) return;
20
+ failure = error;
21
+ close();
22
+ };
23
+ signal.addEventListener('abort', close, { once: true });
24
+ try {
25
+ iterator = apiProxy.events.mux({ rpcId, payload: {} }, controller.signal)[Symbol.asyncIterator]();
26
+ // mux registers listeners eagerly, but its generator installs abort/finally
27
+ // cleanup only on the first next(). Start it even if mux itself aborted us.
28
+ pending = Promise.resolve(iterator.next());
29
+ if (!controller.signal.aborted) invoke(onOpen, close, fail);
30
+ while (!controller.signal.aborted) {
31
+ const { value, done } = await pending;
32
+ if (done || controller.signal.aborted) break;
33
+ invoke(onEnvelope, {
34
+ type: 'server-request',
35
+ rpcId: value?.rpcId,
36
+ method: value?.payload?.type,
37
+ payload: value?.payload,
38
+ }, fail);
39
+ if (!controller.signal.aborted) pending = Promise.resolve(iterator.next());
40
+ }
41
+ } catch (error) {
42
+ fail(error);
43
+ } finally {
44
+ const cancelled = controller.signal.aborted;
45
+ signal.removeEventListener('abort', close);
46
+ // return() alone cannot wake an idle generator's pending next(). Abort
47
+ // first, then drain that read and return (discarding any queued replay).
48
+ close();
49
+ await pending?.catch(() => undefined);
50
+ try {
51
+ await iterator?.return?.();
52
+ } catch (error) {
53
+ if (!cancelled) failure ??= error;
54
+ }
55
+ }
56
+ if (failure) throw failure;
57
+ }
58
+
59
+ function watchWebSocketMux({ baseUrl, createWebSocket, signal, onOpen, onEnvelope, onMalformed }) {
60
+ const url = new URL('/api/events.mux', baseUrl);
61
+ url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
62
+ return new Promise((resolve, reject) => {
63
+ const socket = createWebSocket(url.toString());
64
+ let opened = false;
65
+ let finished = false;
66
+ const finish = (error) => {
67
+ if (finished) return;
68
+ finished = true;
69
+ socket.removeEventListener('open', handleOpen);
70
+ socket.removeEventListener('message', handleMessage);
71
+ socket.removeEventListener('close', handleClose);
72
+ socket.removeEventListener('error', handleError);
73
+ signal.removeEventListener('abort', handleAbort);
74
+ try {
75
+ if (socket.readyState === 0 || socket.readyState === 1) socket.close();
76
+ } catch {
77
+ // Some implementations reject closing while still connecting.
78
+ }
79
+ if (error && !signal.aborted) reject(error);
80
+ else resolve();
81
+ };
82
+ const close = () => finish();
83
+ const handleOpen = () => {
84
+ if (finished || opened) return;
85
+ opened = true;
86
+ invoke(onOpen, close, finish);
87
+ };
88
+ const handleMessage = (event) => {
89
+ if (finished) return;
90
+ let envelope;
91
+ try {
92
+ if (typeof event.data !== 'string') throw new Error('binary WebSocket frame');
93
+ envelope = JSON.parse(event.data);
94
+ } catch (error) {
95
+ invoke(onMalformed, error, () => undefined);
96
+ return;
97
+ }
98
+ invoke(onEnvelope, envelope, finish);
99
+ };
100
+ const handleClose = () => finish(opened ? undefined : new Error(
101
+ 'Harness event mux WebSocket closed before opening',
102
+ ));
103
+ const handleError = () => finish(new Error(opened
104
+ ? 'Harness event mux WebSocket failed'
105
+ : 'Harness event mux WebSocket failed before opening'));
106
+ const handleAbort = () => finish();
107
+ socket.addEventListener('open', handleOpen);
108
+ socket.addEventListener('message', handleMessage);
109
+ socket.addEventListener('close', handleClose, { once: true });
110
+ socket.addEventListener('error', handleError, { once: true });
111
+ signal.addEventListener('abort', handleAbort, { once: true });
112
+ if (signal.aborted) handleAbort();
113
+ });
114
+ }
115
+
116
+ /** Watch one mux subscription; an explicit URL retains the HTTP/WS transport. */
117
+ export async function watchHarnessMux(options) {
118
+ if (options.signal.aborted) return;
119
+ if (options.baseUrl !== undefined && options.baseUrl !== null) {
120
+ return watchWebSocketMux(options);
121
+ }
122
+ return watchInProcessMux(options);
123
+ }