@wireflowapp/websocket-core 0.0.0-stage → 1.0.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 CHANGED
@@ -1,3 +1,35 @@
1
- # Temporary Holding Version
1
+ # @wireflowapp/websocket-core
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Framework-independent native WebSocket client and connection manager for **Wireflow**.
4
+
5
+ ## Features
6
+
7
+ - **Standard WebSocket API**: Clean abstraction over the browser native WebSocket API.
8
+ - **Resilient Reconnection**: Backed by `@wireflowapp/core`'s `RetryManager` for predictable backoff and exhaustion.
9
+ - **SSR Safe**: Safe to import and evaluate in server-side rendering environments without throwing.
10
+ - **Typed Events**: Simple and strongly-typed listener subscription (`open`, `message`, `error`, `close`, `statusChange`).
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ pnpm add @wireflowapp/websocket-core @wireflowapp/core
16
+ ```
17
+
18
+ ## Quick Start
19
+
20
+ ```ts
21
+ import { WebSocketClient } from "@wireflowapp/websocket-core";
22
+
23
+ const client = new WebSocketClient("wss://example.com/ws", {
24
+ retryAttempts: 5,
25
+ retryDelay: 3000,
26
+ });
27
+
28
+ client.on("open", () => {
29
+ client.send(JSON.stringify({ type: "hello" }));
30
+ });
31
+
32
+ client.on("message", (event) => {
33
+ console.log("Received:", event.data);
34
+ });
35
+ ```
@@ -0,0 +1,115 @@
1
+ import { ConnectionStatus, WireFlowConfig, ConnectionResult } from '@wireflowapp/core';
2
+
3
+ type WebSocketDataType = string | ArrayBufferLike | Blob | ArrayBufferView;
4
+ interface WebSocketEventMap {
5
+ open: Event;
6
+ close: CloseEvent;
7
+ error: Event;
8
+ message: MessageEvent;
9
+ statusChange: ConnectionStatus;
10
+ }
11
+ type WebSocketEventListener<K extends keyof WebSocketEventMap> = (event: WebSocketEventMap[K]) => void;
12
+ interface WebSocketOptions extends WireFlowConfig {
13
+ /**
14
+ * Sub-protocol string or array of sub-protocol strings.
15
+ */
16
+ protocols?: string | string[];
17
+ /**
18
+ * The binary type for received data: "blob" or "arraybuffer".
19
+ */
20
+ binaryType?: BinaryType;
21
+ /**
22
+ * Callback fired when connection opens.
23
+ */
24
+ onOpen?: (event: Event) => void;
25
+ /**
26
+ * Callback fired when connection closes.
27
+ */
28
+ onClose?: (event: CloseEvent) => void;
29
+ /**
30
+ * Callback fired when a WebSocket error occurs.
31
+ */
32
+ onError?: (event: Event) => void;
33
+ /**
34
+ * Callback fired when a message is received.
35
+ */
36
+ onMessage?: (event: MessageEvent) => void;
37
+ /**
38
+ * Callback fired when the connection status changes.
39
+ */
40
+ onStatusChange?: (status: ConnectionStatus) => void;
41
+ }
42
+ interface IWebSocketClient {
43
+ readonly status: ConnectionStatus;
44
+ readonly socket: WebSocket | null;
45
+ readonly url: string;
46
+ connect(): void;
47
+ retry(): void;
48
+ disconnect(): void;
49
+ send(data: WebSocketDataType): boolean;
50
+ on<K extends keyof WebSocketEventMap>(event: K, listener: WebSocketEventListener<K>): () => void;
51
+ off<K extends keyof WebSocketEventMap>(event: K, listener: WebSocketEventListener<K>): void;
52
+ once<K extends keyof WebSocketEventMap>(event: K, listener: WebSocketEventListener<K>): () => void;
53
+ dispose(): void;
54
+ }
55
+
56
+ declare class WebSocketClient implements IWebSocketClient, ConnectionResult {
57
+ private readonly _url;
58
+ private readonly _options;
59
+ private readonly _retryManager;
60
+ private _socket;
61
+ private _status;
62
+ private _isManualDisconnect;
63
+ private _listeners;
64
+ constructor(url: string, options?: WebSocketOptions);
65
+ get url(): string;
66
+ get status(): ConnectionStatus;
67
+ get socket(): WebSocket | null;
68
+ get isRetrying(): boolean;
69
+ get retryAttempts(): number;
70
+ connect(): void;
71
+ retry(): void;
72
+ disconnect(): void;
73
+ send(data: WebSocketDataType): boolean;
74
+ on<K extends keyof WebSocketEventMap>(event: K, listener: WebSocketEventListener<K>): () => void;
75
+ off<K extends keyof WebSocketEventMap>(event: K, listener: WebSocketEventListener<K>): void;
76
+ once<K extends keyof WebSocketEventMap>(event: K, listener: WebSocketEventListener<K>): () => void;
77
+ dispose(): void;
78
+ private createSocket;
79
+ private cleanUpExistingSocket;
80
+ private attachSocketEvents;
81
+ private detachSocketEvents;
82
+ private handleSocketFailure;
83
+ private setStatus;
84
+ private emit;
85
+ }
86
+
87
+ /**
88
+ * WebSocketManager provides explicit instance management and lifecycle
89
+ * disposal for multiple WebSocketClient connections.
90
+ */
91
+ declare class WebSocketManager {
92
+ private _clients;
93
+ /**
94
+ * Retrieves an existing client or creates and registers a new one.
95
+ */
96
+ getOrCreate(url: string, options?: WebSocketOptions, key?: string): WebSocketClient;
97
+ /**
98
+ * Retrieves a client by key, if present.
99
+ */
100
+ get(key: string): WebSocketClient | undefined;
101
+ /**
102
+ * Checks if a client is registered under the given key.
103
+ */
104
+ has(key: string): boolean;
105
+ /**
106
+ * Disconnects, disposes, and removes a client by key.
107
+ */
108
+ remove(key: string): boolean;
109
+ /**
110
+ * Disposes and clears all tracked WebSocket clients.
111
+ */
112
+ clear(): void;
113
+ }
114
+
115
+ export { type IWebSocketClient, WebSocketClient, type WebSocketDataType, type WebSocketEventListener, type WebSocketEventMap, WebSocketManager, type WebSocketOptions };
@@ -0,0 +1,115 @@
1
+ import { ConnectionStatus, WireFlowConfig, ConnectionResult } from '@wireflowapp/core';
2
+
3
+ type WebSocketDataType = string | ArrayBufferLike | Blob | ArrayBufferView;
4
+ interface WebSocketEventMap {
5
+ open: Event;
6
+ close: CloseEvent;
7
+ error: Event;
8
+ message: MessageEvent;
9
+ statusChange: ConnectionStatus;
10
+ }
11
+ type WebSocketEventListener<K extends keyof WebSocketEventMap> = (event: WebSocketEventMap[K]) => void;
12
+ interface WebSocketOptions extends WireFlowConfig {
13
+ /**
14
+ * Sub-protocol string or array of sub-protocol strings.
15
+ */
16
+ protocols?: string | string[];
17
+ /**
18
+ * The binary type for received data: "blob" or "arraybuffer".
19
+ */
20
+ binaryType?: BinaryType;
21
+ /**
22
+ * Callback fired when connection opens.
23
+ */
24
+ onOpen?: (event: Event) => void;
25
+ /**
26
+ * Callback fired when connection closes.
27
+ */
28
+ onClose?: (event: CloseEvent) => void;
29
+ /**
30
+ * Callback fired when a WebSocket error occurs.
31
+ */
32
+ onError?: (event: Event) => void;
33
+ /**
34
+ * Callback fired when a message is received.
35
+ */
36
+ onMessage?: (event: MessageEvent) => void;
37
+ /**
38
+ * Callback fired when the connection status changes.
39
+ */
40
+ onStatusChange?: (status: ConnectionStatus) => void;
41
+ }
42
+ interface IWebSocketClient {
43
+ readonly status: ConnectionStatus;
44
+ readonly socket: WebSocket | null;
45
+ readonly url: string;
46
+ connect(): void;
47
+ retry(): void;
48
+ disconnect(): void;
49
+ send(data: WebSocketDataType): boolean;
50
+ on<K extends keyof WebSocketEventMap>(event: K, listener: WebSocketEventListener<K>): () => void;
51
+ off<K extends keyof WebSocketEventMap>(event: K, listener: WebSocketEventListener<K>): void;
52
+ once<K extends keyof WebSocketEventMap>(event: K, listener: WebSocketEventListener<K>): () => void;
53
+ dispose(): void;
54
+ }
55
+
56
+ declare class WebSocketClient implements IWebSocketClient, ConnectionResult {
57
+ private readonly _url;
58
+ private readonly _options;
59
+ private readonly _retryManager;
60
+ private _socket;
61
+ private _status;
62
+ private _isManualDisconnect;
63
+ private _listeners;
64
+ constructor(url: string, options?: WebSocketOptions);
65
+ get url(): string;
66
+ get status(): ConnectionStatus;
67
+ get socket(): WebSocket | null;
68
+ get isRetrying(): boolean;
69
+ get retryAttempts(): number;
70
+ connect(): void;
71
+ retry(): void;
72
+ disconnect(): void;
73
+ send(data: WebSocketDataType): boolean;
74
+ on<K extends keyof WebSocketEventMap>(event: K, listener: WebSocketEventListener<K>): () => void;
75
+ off<K extends keyof WebSocketEventMap>(event: K, listener: WebSocketEventListener<K>): void;
76
+ once<K extends keyof WebSocketEventMap>(event: K, listener: WebSocketEventListener<K>): () => void;
77
+ dispose(): void;
78
+ private createSocket;
79
+ private cleanUpExistingSocket;
80
+ private attachSocketEvents;
81
+ private detachSocketEvents;
82
+ private handleSocketFailure;
83
+ private setStatus;
84
+ private emit;
85
+ }
86
+
87
+ /**
88
+ * WebSocketManager provides explicit instance management and lifecycle
89
+ * disposal for multiple WebSocketClient connections.
90
+ */
91
+ declare class WebSocketManager {
92
+ private _clients;
93
+ /**
94
+ * Retrieves an existing client or creates and registers a new one.
95
+ */
96
+ getOrCreate(url: string, options?: WebSocketOptions, key?: string): WebSocketClient;
97
+ /**
98
+ * Retrieves a client by key, if present.
99
+ */
100
+ get(key: string): WebSocketClient | undefined;
101
+ /**
102
+ * Checks if a client is registered under the given key.
103
+ */
104
+ has(key: string): boolean;
105
+ /**
106
+ * Disconnects, disposes, and removes a client by key.
107
+ */
108
+ remove(key: string): boolean;
109
+ /**
110
+ * Disposes and clears all tracked WebSocket clients.
111
+ */
112
+ clear(): void;
113
+ }
114
+
115
+ export { type IWebSocketClient, WebSocketClient, type WebSocketDataType, type WebSocketEventListener, type WebSocketEventMap, WebSocketManager, type WebSocketOptions };
package/dist/index.js ADDED
@@ -0,0 +1,292 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ WebSocketClient: () => WebSocketClient,
24
+ WebSocketManager: () => WebSocketManager
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/WebSocketClient.ts
29
+ var import_core = require("@wireflowapp/core");
30
+ var WebSocketClient = class {
31
+ _url;
32
+ _options;
33
+ _retryManager;
34
+ _socket = null;
35
+ _status = "Idle";
36
+ _isManualDisconnect = false;
37
+ _listeners = /* @__PURE__ */ new Map();
38
+ constructor(url, options = {}) {
39
+ this._url = url;
40
+ this._options = options;
41
+ const resolved = import_core.WireFlowManager.resolve(options);
42
+ this._retryManager = new import_core.RetryManager({
43
+ retryAttempts: resolved.retryAttempts,
44
+ retryDelay: resolved.retryDelay,
45
+ onRetryAttempt: () => {
46
+ this.setStatus("Connecting");
47
+ },
48
+ onExhausted: () => {
49
+ this.setStatus("Failed");
50
+ }
51
+ });
52
+ if (resolved.autoConnect) {
53
+ this.connect();
54
+ }
55
+ }
56
+ get url() {
57
+ return this._url;
58
+ }
59
+ get status() {
60
+ return this._status;
61
+ }
62
+ get socket() {
63
+ return this._socket;
64
+ }
65
+ get isRetrying() {
66
+ return this._retryManager.isRetrying;
67
+ }
68
+ get retryAttempts() {
69
+ return this._retryManager.attempts;
70
+ }
71
+ connect() {
72
+ if (this._status === "Connected") {
73
+ return;
74
+ }
75
+ if (this._socket && this._socket.readyState === WebSocket.CONNECTING) {
76
+ return;
77
+ }
78
+ if (typeof WebSocket === "undefined") {
79
+ this.setStatus("Idle");
80
+ return;
81
+ }
82
+ this._retryManager.reset();
83
+ this._isManualDisconnect = false;
84
+ this.createSocket();
85
+ }
86
+ retry() {
87
+ if (this._status === "Connected") {
88
+ return;
89
+ }
90
+ this._retryManager.reset();
91
+ this._isManualDisconnect = false;
92
+ this.createSocket();
93
+ }
94
+ disconnect() {
95
+ this._isManualDisconnect = true;
96
+ this._retryManager.cancel();
97
+ if (this._socket) {
98
+ this.detachSocketEvents(this._socket);
99
+ try {
100
+ if (this._socket.readyState === WebSocket.OPEN || this._socket.readyState === WebSocket.CONNECTING) {
101
+ this._socket.close(1e3, "Normal Closure");
102
+ }
103
+ } catch {
104
+ }
105
+ this._socket = null;
106
+ }
107
+ this.setStatus("Idle");
108
+ }
109
+ send(data) {
110
+ if (this._socket && this._socket.readyState === WebSocket.OPEN) {
111
+ this._socket.send(data);
112
+ return true;
113
+ }
114
+ return false;
115
+ }
116
+ on(event, listener) {
117
+ if (!this._listeners.has(event)) {
118
+ this._listeners.set(event, /* @__PURE__ */ new Set());
119
+ }
120
+ this._listeners.get(event).add(listener);
121
+ return () => this.off(event, listener);
122
+ }
123
+ off(event, listener) {
124
+ const set = this._listeners.get(event);
125
+ if (set) {
126
+ set.delete(listener);
127
+ }
128
+ }
129
+ once(event, listener) {
130
+ const onceWrapper = ((data) => {
131
+ this.off(event, onceWrapper);
132
+ listener(data);
133
+ });
134
+ return this.on(event, onceWrapper);
135
+ }
136
+ dispose() {
137
+ this.disconnect();
138
+ this._listeners.clear();
139
+ this._retryManager.dispose();
140
+ }
141
+ createSocket() {
142
+ this.setStatus("Connecting");
143
+ try {
144
+ this.cleanUpExistingSocket();
145
+ const protocols = this._options.protocols;
146
+ const socket = protocols ? new WebSocket(this._url, protocols) : new WebSocket(this._url);
147
+ if (this._options.binaryType) {
148
+ socket.binaryType = this._options.binaryType;
149
+ }
150
+ this._socket = socket;
151
+ this.attachSocketEvents(socket);
152
+ } catch {
153
+ this.handleSocketFailure();
154
+ }
155
+ }
156
+ cleanUpExistingSocket() {
157
+ if (this._socket) {
158
+ this.detachSocketEvents(this._socket);
159
+ try {
160
+ this._socket.close();
161
+ } catch {
162
+ }
163
+ this._socket = null;
164
+ }
165
+ }
166
+ attachSocketEvents(socket) {
167
+ socket.onopen = (event) => {
168
+ if (this._socket !== socket) return;
169
+ this._retryManager.reset();
170
+ this.setStatus("Connected");
171
+ this.emit("open", event);
172
+ this._options.onOpen?.(event);
173
+ };
174
+ socket.onmessage = (event) => {
175
+ if (this._socket !== socket) return;
176
+ this.emit("message", event);
177
+ this._options.onMessage?.(event);
178
+ };
179
+ socket.onerror = (event) => {
180
+ if (this._socket !== socket) return;
181
+ this.emit("error", event);
182
+ this._options.onError?.(event);
183
+ };
184
+ socket.onclose = (event) => {
185
+ if (this._socket !== socket) return;
186
+ this.emit("close", event);
187
+ this._options.onClose?.(event);
188
+ this.detachSocketEvents(socket);
189
+ this._socket = null;
190
+ if (this._isManualDisconnect) {
191
+ this.setStatus("Idle");
192
+ } else {
193
+ this.handleSocketFailure();
194
+ }
195
+ };
196
+ }
197
+ detachSocketEvents(socket) {
198
+ socket.onopen = null;
199
+ socket.onmessage = null;
200
+ socket.onerror = null;
201
+ socket.onclose = null;
202
+ }
203
+ handleSocketFailure() {
204
+ const resolved = import_core.WireFlowManager.resolve(this._options);
205
+ if (!resolved.retryOnDisconnect) {
206
+ this.setStatus("Failed");
207
+ return;
208
+ }
209
+ const scheduled = this._retryManager.scheduleRetry(() => {
210
+ if (!this._isManualDisconnect) {
211
+ this.createSocket();
212
+ }
213
+ });
214
+ if (scheduled) {
215
+ this.setStatus("Connecting");
216
+ } else {
217
+ this.setStatus("Failed");
218
+ }
219
+ }
220
+ setStatus(status) {
221
+ if (this._status !== status) {
222
+ this._status = status;
223
+ this.emit("statusChange", status);
224
+ this._options.onStatusChange?.(status);
225
+ }
226
+ }
227
+ emit(event, data) {
228
+ const set = this._listeners.get(event);
229
+ if (set) {
230
+ const listeners = Array.from(set);
231
+ for (const listener of listeners) {
232
+ try {
233
+ listener(data);
234
+ } catch {
235
+ }
236
+ }
237
+ }
238
+ }
239
+ };
240
+
241
+ // src/WebSocketManager.ts
242
+ var WebSocketManager = class {
243
+ _clients = /* @__PURE__ */ new Map();
244
+ /**
245
+ * Retrieves an existing client or creates and registers a new one.
246
+ */
247
+ getOrCreate(url, options, key = url) {
248
+ let client = this._clients.get(key);
249
+ if (!client) {
250
+ client = new WebSocketClient(url, options);
251
+ this._clients.set(key, client);
252
+ }
253
+ return client;
254
+ }
255
+ /**
256
+ * Retrieves a client by key, if present.
257
+ */
258
+ get(key) {
259
+ return this._clients.get(key);
260
+ }
261
+ /**
262
+ * Checks if a client is registered under the given key.
263
+ */
264
+ has(key) {
265
+ return this._clients.has(key);
266
+ }
267
+ /**
268
+ * Disconnects, disposes, and removes a client by key.
269
+ */
270
+ remove(key) {
271
+ const client = this._clients.get(key);
272
+ if (client) {
273
+ client.dispose();
274
+ return this._clients.delete(key);
275
+ }
276
+ return false;
277
+ }
278
+ /**
279
+ * Disposes and clears all tracked WebSocket clients.
280
+ */
281
+ clear() {
282
+ for (const client of this._clients.values()) {
283
+ client.dispose();
284
+ }
285
+ this._clients.clear();
286
+ }
287
+ };
288
+ // Annotate the CommonJS export names for ESM import in node:
289
+ 0 && (module.exports = {
290
+ WebSocketClient,
291
+ WebSocketManager
292
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,267 @@
1
+ // src/WebSocketClient.ts
2
+ import {
3
+ RetryManager,
4
+ WireFlowManager
5
+ } from "@wireflowapp/core";
6
+ var WebSocketClient = class {
7
+ _url;
8
+ _options;
9
+ _retryManager;
10
+ _socket = null;
11
+ _status = "Idle";
12
+ _isManualDisconnect = false;
13
+ _listeners = /* @__PURE__ */ new Map();
14
+ constructor(url, options = {}) {
15
+ this._url = url;
16
+ this._options = options;
17
+ const resolved = WireFlowManager.resolve(options);
18
+ this._retryManager = new RetryManager({
19
+ retryAttempts: resolved.retryAttempts,
20
+ retryDelay: resolved.retryDelay,
21
+ onRetryAttempt: () => {
22
+ this.setStatus("Connecting");
23
+ },
24
+ onExhausted: () => {
25
+ this.setStatus("Failed");
26
+ }
27
+ });
28
+ if (resolved.autoConnect) {
29
+ this.connect();
30
+ }
31
+ }
32
+ get url() {
33
+ return this._url;
34
+ }
35
+ get status() {
36
+ return this._status;
37
+ }
38
+ get socket() {
39
+ return this._socket;
40
+ }
41
+ get isRetrying() {
42
+ return this._retryManager.isRetrying;
43
+ }
44
+ get retryAttempts() {
45
+ return this._retryManager.attempts;
46
+ }
47
+ connect() {
48
+ if (this._status === "Connected") {
49
+ return;
50
+ }
51
+ if (this._socket && this._socket.readyState === WebSocket.CONNECTING) {
52
+ return;
53
+ }
54
+ if (typeof WebSocket === "undefined") {
55
+ this.setStatus("Idle");
56
+ return;
57
+ }
58
+ this._retryManager.reset();
59
+ this._isManualDisconnect = false;
60
+ this.createSocket();
61
+ }
62
+ retry() {
63
+ if (this._status === "Connected") {
64
+ return;
65
+ }
66
+ this._retryManager.reset();
67
+ this._isManualDisconnect = false;
68
+ this.createSocket();
69
+ }
70
+ disconnect() {
71
+ this._isManualDisconnect = true;
72
+ this._retryManager.cancel();
73
+ if (this._socket) {
74
+ this.detachSocketEvents(this._socket);
75
+ try {
76
+ if (this._socket.readyState === WebSocket.OPEN || this._socket.readyState === WebSocket.CONNECTING) {
77
+ this._socket.close(1e3, "Normal Closure");
78
+ }
79
+ } catch {
80
+ }
81
+ this._socket = null;
82
+ }
83
+ this.setStatus("Idle");
84
+ }
85
+ send(data) {
86
+ if (this._socket && this._socket.readyState === WebSocket.OPEN) {
87
+ this._socket.send(data);
88
+ return true;
89
+ }
90
+ return false;
91
+ }
92
+ on(event, listener) {
93
+ if (!this._listeners.has(event)) {
94
+ this._listeners.set(event, /* @__PURE__ */ new Set());
95
+ }
96
+ this._listeners.get(event).add(listener);
97
+ return () => this.off(event, listener);
98
+ }
99
+ off(event, listener) {
100
+ const set = this._listeners.get(event);
101
+ if (set) {
102
+ set.delete(listener);
103
+ }
104
+ }
105
+ once(event, listener) {
106
+ const onceWrapper = ((data) => {
107
+ this.off(event, onceWrapper);
108
+ listener(data);
109
+ });
110
+ return this.on(event, onceWrapper);
111
+ }
112
+ dispose() {
113
+ this.disconnect();
114
+ this._listeners.clear();
115
+ this._retryManager.dispose();
116
+ }
117
+ createSocket() {
118
+ this.setStatus("Connecting");
119
+ try {
120
+ this.cleanUpExistingSocket();
121
+ const protocols = this._options.protocols;
122
+ const socket = protocols ? new WebSocket(this._url, protocols) : new WebSocket(this._url);
123
+ if (this._options.binaryType) {
124
+ socket.binaryType = this._options.binaryType;
125
+ }
126
+ this._socket = socket;
127
+ this.attachSocketEvents(socket);
128
+ } catch {
129
+ this.handleSocketFailure();
130
+ }
131
+ }
132
+ cleanUpExistingSocket() {
133
+ if (this._socket) {
134
+ this.detachSocketEvents(this._socket);
135
+ try {
136
+ this._socket.close();
137
+ } catch {
138
+ }
139
+ this._socket = null;
140
+ }
141
+ }
142
+ attachSocketEvents(socket) {
143
+ socket.onopen = (event) => {
144
+ if (this._socket !== socket) return;
145
+ this._retryManager.reset();
146
+ this.setStatus("Connected");
147
+ this.emit("open", event);
148
+ this._options.onOpen?.(event);
149
+ };
150
+ socket.onmessage = (event) => {
151
+ if (this._socket !== socket) return;
152
+ this.emit("message", event);
153
+ this._options.onMessage?.(event);
154
+ };
155
+ socket.onerror = (event) => {
156
+ if (this._socket !== socket) return;
157
+ this.emit("error", event);
158
+ this._options.onError?.(event);
159
+ };
160
+ socket.onclose = (event) => {
161
+ if (this._socket !== socket) return;
162
+ this.emit("close", event);
163
+ this._options.onClose?.(event);
164
+ this.detachSocketEvents(socket);
165
+ this._socket = null;
166
+ if (this._isManualDisconnect) {
167
+ this.setStatus("Idle");
168
+ } else {
169
+ this.handleSocketFailure();
170
+ }
171
+ };
172
+ }
173
+ detachSocketEvents(socket) {
174
+ socket.onopen = null;
175
+ socket.onmessage = null;
176
+ socket.onerror = null;
177
+ socket.onclose = null;
178
+ }
179
+ handleSocketFailure() {
180
+ const resolved = WireFlowManager.resolve(this._options);
181
+ if (!resolved.retryOnDisconnect) {
182
+ this.setStatus("Failed");
183
+ return;
184
+ }
185
+ const scheduled = this._retryManager.scheduleRetry(() => {
186
+ if (!this._isManualDisconnect) {
187
+ this.createSocket();
188
+ }
189
+ });
190
+ if (scheduled) {
191
+ this.setStatus("Connecting");
192
+ } else {
193
+ this.setStatus("Failed");
194
+ }
195
+ }
196
+ setStatus(status) {
197
+ if (this._status !== status) {
198
+ this._status = status;
199
+ this.emit("statusChange", status);
200
+ this._options.onStatusChange?.(status);
201
+ }
202
+ }
203
+ emit(event, data) {
204
+ const set = this._listeners.get(event);
205
+ if (set) {
206
+ const listeners = Array.from(set);
207
+ for (const listener of listeners) {
208
+ try {
209
+ listener(data);
210
+ } catch {
211
+ }
212
+ }
213
+ }
214
+ }
215
+ };
216
+
217
+ // src/WebSocketManager.ts
218
+ var WebSocketManager = class {
219
+ _clients = /* @__PURE__ */ new Map();
220
+ /**
221
+ * Retrieves an existing client or creates and registers a new one.
222
+ */
223
+ getOrCreate(url, options, key = url) {
224
+ let client = this._clients.get(key);
225
+ if (!client) {
226
+ client = new WebSocketClient(url, options);
227
+ this._clients.set(key, client);
228
+ }
229
+ return client;
230
+ }
231
+ /**
232
+ * Retrieves a client by key, if present.
233
+ */
234
+ get(key) {
235
+ return this._clients.get(key);
236
+ }
237
+ /**
238
+ * Checks if a client is registered under the given key.
239
+ */
240
+ has(key) {
241
+ return this._clients.has(key);
242
+ }
243
+ /**
244
+ * Disconnects, disposes, and removes a client by key.
245
+ */
246
+ remove(key) {
247
+ const client = this._clients.get(key);
248
+ if (client) {
249
+ client.dispose();
250
+ return this._clients.delete(key);
251
+ }
252
+ return false;
253
+ }
254
+ /**
255
+ * Disposes and clears all tracked WebSocket clients.
256
+ */
257
+ clear() {
258
+ for (const client of this._clients.values()) {
259
+ client.dispose();
260
+ }
261
+ this._clients.clear();
262
+ }
263
+ };
264
+ export {
265
+ WebSocketClient,
266
+ WebSocketManager
267
+ };
package/package.json CHANGED
@@ -1,6 +1,43 @@
1
1
  {
2
2
  "name": "@wireflowapp/websocket-core",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "1.0.0",
4
+ "description": "Framework-independent native WebSocket engine for Wireflow",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/index.d.mts",
12
+ "default": "./dist/index.mjs"
13
+ },
14
+ "require": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ }
18
+ }
19
+ },
20
+ "sideEffects": false,
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "dependencies": {
28
+ "@wireflowapp/core": "1.0.0"
29
+ },
30
+ "keywords": [
31
+ "websocket",
32
+ "real-time",
33
+ "connection",
34
+ "wireflow"
35
+ ],
36
+ "author": "",
37
+ "license": "MIT",
38
+ "scripts": {
39
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean",
40
+ "typecheck": "tsc --noEmit",
41
+ "test": "vitest run"
42
+ }
6
43
  }