@blaxel/core 0.2.49-dev.210 → 0.2.49-dev.212

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.
Files changed (57) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/common/settings.js +2 -2
  3. package/dist/cjs/sandbox/codegen/codegen-ws.js +30 -0
  4. package/dist/cjs/sandbox/filesystem/filesystem-ws.js +106 -0
  5. package/dist/cjs/sandbox/filesystem/filesystem.js +4 -15
  6. package/dist/cjs/sandbox/network/network-ws.js +12 -0
  7. package/dist/cjs/sandbox/process/process-ws.js +139 -0
  8. package/dist/cjs/sandbox/sandbox.js +67 -10
  9. package/dist/cjs/sandbox/websocket/client.js +275 -0
  10. package/dist/cjs/sandbox/websocket/index.js +17 -0
  11. package/dist/cjs/types/sandbox/codegen/codegen-ws.d.ts +10 -0
  12. package/dist/cjs/types/sandbox/filesystem/filesystem-ws.d.ts +35 -0
  13. package/dist/cjs/types/sandbox/network/network-ws.d.ts +7 -0
  14. package/dist/cjs/types/sandbox/process/process-ws.d.ts +27 -0
  15. package/dist/cjs/types/sandbox/sandbox.d.ts +12 -6
  16. package/dist/cjs/types/sandbox/types.d.ts +3 -0
  17. package/dist/cjs/types/sandbox/websocket/client.d.ts +49 -0
  18. package/dist/cjs/types/sandbox/websocket/index.d.ts +1 -0
  19. package/dist/cjs-browser/.tsbuildinfo +1 -1
  20. package/dist/cjs-browser/common/settings.js +2 -2
  21. package/dist/cjs-browser/sandbox/codegen/codegen-ws.js +30 -0
  22. package/dist/cjs-browser/sandbox/filesystem/filesystem-ws.js +106 -0
  23. package/dist/cjs-browser/sandbox/filesystem/filesystem.js +4 -15
  24. package/dist/cjs-browser/sandbox/network/network-ws.js +12 -0
  25. package/dist/cjs-browser/sandbox/process/process-ws.js +139 -0
  26. package/dist/cjs-browser/sandbox/sandbox.js +67 -10
  27. package/dist/cjs-browser/sandbox/websocket/client.js +275 -0
  28. package/dist/cjs-browser/sandbox/websocket/index.js +17 -0
  29. package/dist/cjs-browser/types/sandbox/codegen/codegen-ws.d.ts +10 -0
  30. package/dist/cjs-browser/types/sandbox/filesystem/filesystem-ws.d.ts +35 -0
  31. package/dist/cjs-browser/types/sandbox/network/network-ws.d.ts +7 -0
  32. package/dist/cjs-browser/types/sandbox/process/process-ws.d.ts +27 -0
  33. package/dist/cjs-browser/types/sandbox/sandbox.d.ts +12 -6
  34. package/dist/cjs-browser/types/sandbox/types.d.ts +3 -0
  35. package/dist/cjs-browser/types/sandbox/websocket/client.d.ts +49 -0
  36. package/dist/cjs-browser/types/sandbox/websocket/index.d.ts +1 -0
  37. package/dist/esm/.tsbuildinfo +1 -1
  38. package/dist/esm/common/settings.js +2 -2
  39. package/dist/esm/sandbox/codegen/codegen-ws.js +26 -0
  40. package/dist/esm/sandbox/filesystem/filesystem-ws.js +102 -0
  41. package/dist/esm/sandbox/filesystem/filesystem.js +4 -15
  42. package/dist/esm/sandbox/network/network-ws.js +8 -0
  43. package/dist/esm/sandbox/process/process-ws.js +135 -0
  44. package/dist/esm/sandbox/sandbox.js +67 -10
  45. package/dist/esm/sandbox/websocket/client.js +271 -0
  46. package/dist/esm/sandbox/websocket/index.js +1 -0
  47. package/dist/esm-browser/.tsbuildinfo +1 -1
  48. package/dist/esm-browser/common/settings.js +2 -2
  49. package/dist/esm-browser/sandbox/codegen/codegen-ws.js +26 -0
  50. package/dist/esm-browser/sandbox/filesystem/filesystem-ws.js +102 -0
  51. package/dist/esm-browser/sandbox/filesystem/filesystem.js +4 -15
  52. package/dist/esm-browser/sandbox/network/network-ws.js +8 -0
  53. package/dist/esm-browser/sandbox/process/process-ws.js +135 -0
  54. package/dist/esm-browser/sandbox/sandbox.js +67 -10
  55. package/dist/esm-browser/sandbox/websocket/client.js +271 -0
  56. package/dist/esm-browser/sandbox/websocket/index.js +1 -0
  57. package/package.json +2 -2
@@ -0,0 +1,271 @@
1
+ import { v4 as uuidv4 } from "uuid";
2
+ import { getWebSocket } from "../../common/node.js";
3
+ export class WebSocketClient {
4
+ ws = null;
5
+ WebSocketClass = null;
6
+ url;
7
+ headers;
8
+ reconnect;
9
+ reconnectInterval;
10
+ maxReconnectAttempts;
11
+ reconnectAttempts = 0;
12
+ pendingRequests = new Map();
13
+ streamHandlers = new Map();
14
+ isClosing = false;
15
+ connectionPromise = null;
16
+ heartbeatInterval = null;
17
+ reconnectTimeout = null;
18
+ lastPongReceived = Date.now();
19
+ constructor(options) {
20
+ this.url = options.url;
21
+ this.headers = options.headers || {};
22
+ this.reconnect = options.reconnect ?? true;
23
+ this.reconnectInterval = options.reconnectInterval ?? 5000;
24
+ this.maxReconnectAttempts = options.maxReconnectAttempts ?? 5;
25
+ }
26
+ async connect() {
27
+ if (this.connectionPromise) {
28
+ return this.connectionPromise;
29
+ }
30
+ this.connectionPromise = this.initializeConnection();
31
+ return this.connectionPromise;
32
+ }
33
+ async initializeConnection() {
34
+ return new Promise((resolve, reject) => {
35
+ // Get WebSocket class and connect
36
+ void (async () => {
37
+ try {
38
+ // Get WebSocket class if not already loaded
39
+ if (!this.WebSocketClass) {
40
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
41
+ this.WebSocketClass = await getWebSocket();
42
+ }
43
+ // Convert http/https URL to ws/wss
44
+ let wsUrl = this.url;
45
+ if (wsUrl.startsWith("http://")) {
46
+ wsUrl = wsUrl.replace("http://", "ws://");
47
+ }
48
+ else if (wsUrl.startsWith("https://")) {
49
+ wsUrl = wsUrl.replace("https://", "wss://");
50
+ }
51
+ // Add /ws endpoint if not present
52
+ if (!wsUrl.endsWith("/ws")) {
53
+ wsUrl = `${wsUrl}/ws`;
54
+ }
55
+ // Create WebSocket with headers (if supported by the environment)
56
+ const wsOptions = {};
57
+ if (Object.keys(this.headers).length > 0) {
58
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
59
+ wsOptions.headers = this.headers;
60
+ }
61
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
62
+ this.ws = new this.WebSocketClass(wsUrl, wsOptions);
63
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
64
+ this.ws.onopen = () => {
65
+ this.reconnectAttempts = 0;
66
+ this.startHeartbeat();
67
+ resolve();
68
+ };
69
+ this.ws.onmessage = (event) => {
70
+ this.handleMessage(event);
71
+ };
72
+ this.ws.onerror = (error) => {
73
+ console.error("WebSocket error:", error);
74
+ reject(new Error("WebSocket connection error"));
75
+ };
76
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
77
+ this.ws.onclose = () => {
78
+ this.stopHeartbeat();
79
+ this.connectionPromise = null;
80
+ if (!this.isClosing && this.reconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
81
+ this.reconnectAttempts++;
82
+ this.reconnectTimeout = setTimeout(() => {
83
+ this.connect().catch(console.error);
84
+ }, this.reconnectInterval);
85
+ // Allow process to exit even if reconnect timeout is pending
86
+ if (this.reconnectTimeout.unref) {
87
+ this.reconnectTimeout.unref();
88
+ }
89
+ }
90
+ else {
91
+ // Reject all pending requests
92
+ this.pendingRequests.forEach(({ reject }) => {
93
+ reject(new Error("WebSocket connection closed"));
94
+ });
95
+ this.pendingRequests.clear();
96
+ }
97
+ };
98
+ // Handle pong messages for heartbeat
99
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
100
+ if (typeof this.ws.on === 'function') {
101
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
102
+ this.ws.on('pong', () => {
103
+ this.lastPongReceived = Date.now();
104
+ });
105
+ }
106
+ }
107
+ catch (error) {
108
+ reject(error instanceof Error ? error : new Error(String(error)));
109
+ }
110
+ })();
111
+ });
112
+ }
113
+ startHeartbeat() {
114
+ // Send ping every 30 seconds
115
+ this.heartbeatInterval = setInterval(() => {
116
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
117
+ if (this.ws && this.WebSocketClass && this.ws.readyState === this.WebSocketClass.OPEN) {
118
+ // Check if we received a pong recently (within 60 seconds)
119
+ if (Date.now() - this.lastPongReceived > 60000) {
120
+ console.warn("WebSocket heartbeat timeout, closing connection");
121
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
122
+ this.ws.close();
123
+ return;
124
+ }
125
+ // Send ping
126
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
127
+ if (typeof this.ws.ping === 'function') {
128
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
129
+ this.ws.ping();
130
+ }
131
+ }
132
+ }, 30000);
133
+ // Allow process to exit even if heartbeat interval is active
134
+ if (this.heartbeatInterval.unref) {
135
+ this.heartbeatInterval.unref();
136
+ }
137
+ }
138
+ stopHeartbeat() {
139
+ if (this.heartbeatInterval) {
140
+ clearInterval(this.heartbeatInterval);
141
+ this.heartbeatInterval = null;
142
+ }
143
+ }
144
+ handleMessage(event) {
145
+ try {
146
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
147
+ const response = JSON.parse(String(event.data));
148
+ // Check if this is a streaming response
149
+ if (response.stream) {
150
+ const streamHandler = this.streamHandlers.get(response.id);
151
+ if (streamHandler) {
152
+ // Call the data handler with the response data
153
+ streamHandler.onData(response.data);
154
+ // If stream is done, call end handler and clean up
155
+ if (response.done) {
156
+ streamHandler.onEnd();
157
+ this.streamHandlers.delete(response.id);
158
+ }
159
+ }
160
+ return;
161
+ }
162
+ // Regular request-response handling
163
+ const pending = this.pendingRequests.get(response.id);
164
+ if (pending) {
165
+ this.pendingRequests.delete(response.id);
166
+ if (response.success) {
167
+ pending.resolve(response.data);
168
+ }
169
+ else {
170
+ pending.reject(new Error(response.error || "Unknown error"));
171
+ }
172
+ }
173
+ }
174
+ catch (error) {
175
+ console.error("Failed to parse WebSocket message:", error);
176
+ }
177
+ }
178
+ async send(operation, data = {}) {
179
+ // Ensure we're connected
180
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
181
+ if (!this.ws || !this.WebSocketClass || this.ws.readyState !== this.WebSocketClass.OPEN) {
182
+ await this.connect();
183
+ }
184
+ return new Promise((resolve, reject) => {
185
+ const id = uuidv4();
186
+ const message = {
187
+ id,
188
+ operation,
189
+ data,
190
+ };
191
+ // Store the promise handlers
192
+ this.pendingRequests.set(id, { resolve, reject });
193
+ // Set a timeout for the request (60 seconds)
194
+ setTimeout(() => {
195
+ if (this.pendingRequests.has(id)) {
196
+ this.pendingRequests.delete(id);
197
+ reject(new Error("Request timeout"));
198
+ }
199
+ }, 60000);
200
+ // Send the message
201
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
202
+ if (this.ws && this.WebSocketClass && this.ws.readyState === this.WebSocketClass.OPEN) {
203
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
204
+ this.ws.send(JSON.stringify(message));
205
+ }
206
+ else {
207
+ this.pendingRequests.delete(id);
208
+ reject(new Error("WebSocket not connected"));
209
+ }
210
+ });
211
+ }
212
+ sendStream(operation, data, onData, onEnd) {
213
+ // Ensure we're connected
214
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
215
+ if (!this.ws || !this.WebSocketClass || this.ws.readyState !== this.WebSocketClass.OPEN) {
216
+ throw new Error("WebSocket not connected");
217
+ }
218
+ const id = uuidv4();
219
+ const message = {
220
+ id,
221
+ operation,
222
+ data,
223
+ };
224
+ // Store the stream handlers
225
+ this.streamHandlers.set(id, { onData, onEnd });
226
+ // Send the message
227
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
228
+ this.ws.send(JSON.stringify(message));
229
+ return id;
230
+ }
231
+ cancelStream(id) {
232
+ this.streamHandlers.delete(id);
233
+ }
234
+ close() {
235
+ this.isClosing = true;
236
+ this.reconnect = false;
237
+ this.stopHeartbeat();
238
+ // Clear reconnect timeout if any
239
+ if (this.reconnectTimeout) {
240
+ clearTimeout(this.reconnectTimeout);
241
+ this.reconnectTimeout = null;
242
+ }
243
+ if (this.ws) {
244
+ // In Node.js (ws package), use terminate() to forcefully close the connection
245
+ // This immediately closes the socket without waiting for the close handshake
246
+ // In browser, terminate() doesn't exist, so we fall back to close()
247
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
248
+ if (typeof this.ws.terminate === 'function') {
249
+ // Node.js ws package - force immediate close
250
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
251
+ this.ws.terminate();
252
+ }
253
+ else {
254
+ // Browser WebSocket - graceful close
255
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
256
+ this.ws.close();
257
+ }
258
+ this.ws = null;
259
+ }
260
+ // Reject all pending requests
261
+ this.pendingRequests.forEach(({ reject }) => {
262
+ reject(new Error("WebSocket client closed"));
263
+ });
264
+ this.pendingRequests.clear();
265
+ this.connectionPromise = null;
266
+ }
267
+ get isConnected() {
268
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
269
+ return this.ws !== null && this.WebSocketClass !== null && this.ws.readyState === this.WebSocketClass.OPEN;
270
+ }
271
+ }
@@ -0,0 +1 @@
1
+ export * from "./client.js";