@gjsify/ws 0.3.12 → 0.3.14
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/esm/constants.js +12 -11
- package/lib/esm/index.js +6 -8
- package/lib/esm/stream.js +82 -80
- package/lib/esm/websocket-server.js +393 -399
- package/lib/esm/websocket.js +273 -246
- package/package.json +11 -11
package/lib/esm/websocket.js
CHANGED
|
@@ -1,249 +1,276 @@
|
|
|
1
|
+
import { BINARY_TYPES, CLOSED, CLOSING, CONNECTING, OPEN } from "./constants.js";
|
|
1
2
|
import { EventEmitter } from "@gjsify/events";
|
|
2
3
|
import { Buffer } from "@gjsify/buffer";
|
|
3
|
-
import { WebSocket as
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
4
|
+
import { WebSocket as WebSocket$1 } from "@gjsify/websocket";
|
|
5
|
+
|
|
6
|
+
//#region src/websocket.ts
|
|
7
|
+
/** `ws.WebSocket` — EventEmitter-based WebSocket client.
|
|
8
|
+
*
|
|
9
|
+
* Events (ws-compatible):
|
|
10
|
+
* - 'open' → ()
|
|
11
|
+
* - 'message' → (data: Buffer | ArrayBuffer | string, isBinary: boolean)
|
|
12
|
+
* - 'close' → (code: number, reason: Buffer)
|
|
13
|
+
* - 'error' → (error: Error)
|
|
14
|
+
* - 'ping' / 'pong' → NOT EMITTED on Gjs (Soup handles control
|
|
15
|
+
* frames internally; no JS hook exposed).
|
|
16
|
+
* Consumers that rely on 'ping'/'pong' for
|
|
17
|
+
* keep-alive application logic need to use
|
|
18
|
+
* data messages instead. Tracked as a
|
|
19
|
+
* known limitation in STATUS.md.
|
|
20
|
+
* - 'upgrade' / 'unexpected-response' / 'redirect'
|
|
21
|
+
* → NOT EMITTED on Gjs (Soup does not expose
|
|
22
|
+
* the raw HTTP upgrade response). Code
|
|
23
|
+
* that only branches on `open` vs `error`
|
|
24
|
+
* still works.
|
|
25
|
+
*
|
|
26
|
+
* Also implements W3C DOM `EventTarget` methods (addEventListener /
|
|
27
|
+
* removeEventListener) so `ws` users who follow W3C-style handlers via the
|
|
28
|
+
* `ws.EventTarget` flag still work without special-casing the Gjs path.
|
|
29
|
+
*/
|
|
30
|
+
var WebSocket = class extends EventEmitter {
|
|
31
|
+
/** Static readyState constants — match the W3C and `ws` values. */
|
|
32
|
+
static CONNECTING = 0;
|
|
33
|
+
static OPEN = 1;
|
|
34
|
+
static CLOSING = 2;
|
|
35
|
+
static CLOSED = 3;
|
|
36
|
+
/** Instance-side copies for `ws.readyState === ws.OPEN` style code. */
|
|
37
|
+
CONNECTING = 0;
|
|
38
|
+
OPEN = 1;
|
|
39
|
+
CLOSING = 2;
|
|
40
|
+
CLOSED = 3;
|
|
41
|
+
/** Per-instance state. Populated from the underlying native WebSocket as
|
|
42
|
+
* events arrive; exposed as properties so that ws-calling code like
|
|
43
|
+
* `if (ws.readyState === ws.OPEN)` works identically to Node's `ws`. */
|
|
44
|
+
readyState = 0;
|
|
45
|
+
url = "";
|
|
46
|
+
protocol = "";
|
|
47
|
+
extensions = "";
|
|
48
|
+
bufferedAmount = 0;
|
|
49
|
+
binaryType = "nodebuffer";
|
|
50
|
+
/** The real WebSocket we delegate to. Typed as `any` because the W3C
|
|
51
|
+
* ambient type comes from multiple realms depending on where this bundle
|
|
52
|
+
* ends up (GJS browser-like globals vs. Node's undici). */
|
|
53
|
+
_native = null;
|
|
54
|
+
constructor(address, protocols, options = {}) {
|
|
55
|
+
super();
|
|
56
|
+
if (address === null) {
|
|
57
|
+
queueMicrotask(() => this._fail("Constructing ws.WebSocket with null address is not supported on Gjs"));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
this.url = typeof address === "string" ? address : String(address);
|
|
61
|
+
const protos = this._resolveProtocols(protocols, options);
|
|
62
|
+
this._openNative(this.url, protos, options);
|
|
63
|
+
}
|
|
64
|
+
/** Merge `protocols` arg and `options.protocols` / `options.protocol`. */
|
|
65
|
+
_resolveProtocols(protocols, options) {
|
|
66
|
+
if (protocols !== undefined) {
|
|
67
|
+
return Array.isArray(protocols) ? protocols : [protocols];
|
|
68
|
+
}
|
|
69
|
+
if (options.protocols !== undefined) {
|
|
70
|
+
return Array.isArray(options.protocols) ? options.protocols : [options.protocols];
|
|
71
|
+
}
|
|
72
|
+
if (options.protocol !== undefined) return [options.protocol];
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
/** Lazy-open the underlying native WebSocket. Separated from the constructor
|
|
76
|
+
* so subclasses or future socket-adoption paths can bypass it. */
|
|
77
|
+
_openNative(url, protocols, options) {
|
|
78
|
+
if (typeof WebSocket$1 !== "function") {
|
|
79
|
+
queueMicrotask(() => this._fail("@gjsify/websocket provided no WebSocket constructor. On Node.js 22+ " + "globalThis.WebSocket is native; on older Node install `ws` directly, " + "or ensure globalThis.WebSocket is set before @gjsify/ws is imported."));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const nativeOpts = {
|
|
83
|
+
perMessageDeflate: options.perMessageDeflate !== false,
|
|
84
|
+
headers: options.headers,
|
|
85
|
+
origin: options.origin,
|
|
86
|
+
handshakeTimeout: options.handshakeTimeout
|
|
87
|
+
};
|
|
88
|
+
try {
|
|
89
|
+
this._native = new WebSocket$1(url, protocols, nativeOpts);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
queueMicrotask(() => this._fail(err instanceof Error ? err : new Error(String(err))));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
this._native.binaryType = "arraybuffer";
|
|
95
|
+
this._native.addEventListener("open", () => this._onOpen());
|
|
96
|
+
this._native.addEventListener("message", (ev) => this._onMessage(ev));
|
|
97
|
+
this._native.addEventListener("close", (ev) => this._onClose(ev));
|
|
98
|
+
this._native.addEventListener("error", (ev) => this._onError(ev));
|
|
99
|
+
}
|
|
100
|
+
_fail(err) {
|
|
101
|
+
const error = typeof err === "string" ? new Error(err) : err;
|
|
102
|
+
this.readyState = 3;
|
|
103
|
+
this.emit("error", error);
|
|
104
|
+
this._dispatchEvent("error", {
|
|
105
|
+
error,
|
|
106
|
+
message: error.message
|
|
107
|
+
});
|
|
108
|
+
this.emit("close", 1006, Buffer.from(error.message));
|
|
109
|
+
this._dispatchEvent("close", {
|
|
110
|
+
code: 1006,
|
|
111
|
+
reason: error.message,
|
|
112
|
+
wasClean: false
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
_onOpen() {
|
|
116
|
+
this.readyState = 1;
|
|
117
|
+
if (typeof this._native.protocol === "string") this.protocol = this._native.protocol;
|
|
118
|
+
if (typeof this._native.extensions === "string") this.extensions = this._native.extensions;
|
|
119
|
+
this.emit("open");
|
|
120
|
+
this._dispatchEvent("open", {});
|
|
121
|
+
}
|
|
122
|
+
_onMessage(ev) {
|
|
123
|
+
const raw = ev?.data;
|
|
124
|
+
let data;
|
|
125
|
+
let isBinary = false;
|
|
126
|
+
if (typeof raw === "string") {
|
|
127
|
+
data = raw;
|
|
128
|
+
isBinary = false;
|
|
129
|
+
} else if (raw instanceof ArrayBuffer) {
|
|
130
|
+
isBinary = true;
|
|
131
|
+
data = this._decodeBinary(raw);
|
|
132
|
+
} else if (ArrayBuffer.isView(raw)) {
|
|
133
|
+
isBinary = true;
|
|
134
|
+
data = this._decodeBinary(raw.buffer);
|
|
135
|
+
} else {
|
|
136
|
+
data = raw;
|
|
137
|
+
isBinary = false;
|
|
138
|
+
}
|
|
139
|
+
this.emit("message", data, isBinary);
|
|
140
|
+
this._dispatchEvent("message", {
|
|
141
|
+
data,
|
|
142
|
+
type: isBinary ? "binary" : "text"
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
/** Convert an ArrayBuffer to the Buffer flavor requested by `binaryType`. */
|
|
146
|
+
_decodeBinary(buf) {
|
|
147
|
+
switch (this.binaryType) {
|
|
148
|
+
case "arraybuffer": return buf;
|
|
149
|
+
case "fragments": return [Buffer.from(buf)];
|
|
150
|
+
case "blob": {
|
|
151
|
+
const BlobCtor = globalThis.Blob;
|
|
152
|
+
return BlobCtor ? new BlobCtor([new Uint8Array(buf)]) : Buffer.from(buf);
|
|
153
|
+
}
|
|
154
|
+
case "nodebuffer":
|
|
155
|
+
default: return Buffer.from(buf);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
_onClose(ev) {
|
|
159
|
+
const code = typeof ev?.code === "number" ? ev.code : 1006;
|
|
160
|
+
const reason = typeof ev?.reason === "string" ? ev.reason : "";
|
|
161
|
+
this.readyState = 3;
|
|
162
|
+
this.emit("close", code, Buffer.from(reason));
|
|
163
|
+
this._dispatchEvent("close", {
|
|
164
|
+
code,
|
|
165
|
+
reason,
|
|
166
|
+
wasClean: !!ev?.wasClean
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
_onError(ev) {
|
|
170
|
+
const msg = ev?.message || "WebSocket error";
|
|
171
|
+
const err = ev?.error instanceof Error ? ev.error : new Error(msg);
|
|
172
|
+
this.emit("error", err);
|
|
173
|
+
this._dispatchEvent("error", {
|
|
174
|
+
error: err,
|
|
175
|
+
message: msg
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
send(data, optionsOrCb, cb) {
|
|
179
|
+
const callback = typeof optionsOrCb === "function" ? optionsOrCb : cb;
|
|
180
|
+
if (this.readyState === 0) {
|
|
181
|
+
throw new Error("WebSocket is not open: readyState 0 (CONNECTING)");
|
|
182
|
+
}
|
|
183
|
+
if (this.readyState !== 1) {
|
|
184
|
+
const err = new Error("WebSocket is not open: readyState " + this.readyState);
|
|
185
|
+
if (callback) queueMicrotask(() => callback(err));
|
|
186
|
+
else queueMicrotask(() => this.emit("error", err));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
this._nativeSend(data, callback);
|
|
190
|
+
}
|
|
191
|
+
_nativeSend(data, cb) {
|
|
192
|
+
try {
|
|
193
|
+
let payload = data;
|
|
194
|
+
if (typeof data === "number" || typeof data === "boolean") {
|
|
195
|
+
payload = String(data);
|
|
196
|
+
} else if (Buffer.isBuffer(data)) {
|
|
197
|
+
const b = data;
|
|
198
|
+
payload = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);
|
|
199
|
+
}
|
|
200
|
+
this._native.send(payload);
|
|
201
|
+
if (cb) queueMicrotask(() => cb());
|
|
202
|
+
} catch (err) {
|
|
203
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
204
|
+
if (cb) queueMicrotask(() => cb(e));
|
|
205
|
+
else queueMicrotask(() => this.emit("error", e));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
_sizeOf(data) {
|
|
209
|
+
if (typeof data === "string") return data.length;
|
|
210
|
+
if (data instanceof ArrayBuffer) return data.byteLength;
|
|
211
|
+
if (ArrayBuffer.isView(data)) return data.byteLength;
|
|
212
|
+
return 0;
|
|
213
|
+
}
|
|
214
|
+
close(code, reason) {
|
|
215
|
+
if (this.readyState === 3) return;
|
|
216
|
+
if (this.readyState === 2) return;
|
|
217
|
+
this.readyState = 2;
|
|
218
|
+
try {
|
|
219
|
+
const reasonStr = reason === undefined ? undefined : Buffer.isBuffer(reason) ? reason.toString("utf8") : String(reason);
|
|
220
|
+
if (code === undefined) this._native?.close();
|
|
221
|
+
else if (reasonStr === undefined) this._native?.close(code);
|
|
222
|
+
else this._native?.close(code, reasonStr);
|
|
223
|
+
} catch (err) {
|
|
224
|
+
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/** ws-only: force-close without sending a Close frame. On Gjs we can't bypass
|
|
228
|
+
* Soup's close handshake, so terminate is approximated as close(1006).
|
|
229
|
+
* Known gap vs. ws semantics — documented. */
|
|
230
|
+
terminate() {
|
|
231
|
+
if (this.readyState === 3) return;
|
|
232
|
+
this.readyState = 2;
|
|
233
|
+
try {
|
|
234
|
+
this._native?.close(1006, "terminated");
|
|
235
|
+
} catch {}
|
|
236
|
+
}
|
|
237
|
+
/** Convenience: returns true if the socket is closed or closing. Matches
|
|
238
|
+
* ws.isPaused() / internal state checks that some consumers rely on. */
|
|
239
|
+
get isPaused() {
|
|
240
|
+
return this.readyState === 2 || this.readyState === 3;
|
|
241
|
+
}
|
|
242
|
+
_eventTargetListeners = new Map();
|
|
243
|
+
addEventListener(type, listener) {
|
|
244
|
+
let set = this._eventTargetListeners.get(type);
|
|
245
|
+
if (!set) {
|
|
246
|
+
set = new Set();
|
|
247
|
+
this._eventTargetListeners.set(type, set);
|
|
248
|
+
}
|
|
249
|
+
set.add(listener);
|
|
250
|
+
}
|
|
251
|
+
removeEventListener(type, listener) {
|
|
252
|
+
this._eventTargetListeners.get(type)?.delete(listener);
|
|
253
|
+
}
|
|
254
|
+
_dispatchEvent(type, detail) {
|
|
255
|
+
const set = this._eventTargetListeners.get(type);
|
|
256
|
+
if (!set || set.size === 0) return;
|
|
257
|
+
const ev = Object.assign({
|
|
258
|
+
type,
|
|
259
|
+
target: this
|
|
260
|
+
}, detail);
|
|
261
|
+
for (const listener of set) {
|
|
262
|
+
try {
|
|
263
|
+
listener(ev);
|
|
264
|
+
} catch (err) {
|
|
265
|
+
queueMicrotask(() => this.emit("error", err));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
static get BINARY_TYPES() {
|
|
270
|
+
return BINARY_TYPES;
|
|
271
|
+
}
|
|
249
272
|
};
|
|
273
|
+
WebSocket.WebSocket = WebSocket;
|
|
274
|
+
|
|
275
|
+
//#endregion
|
|
276
|
+
export { WebSocket };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/ws",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.14",
|
|
4
4
|
"description": "Drop-in replacement for the `ws` npm package on Gjs — wraps globalThis.WebSocket (Soup.WebsocketConnection) and Soup.Server for the WebSocketServer side",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "lib/esm/index.js",
|
|
@@ -31,18 +31,18 @@
|
|
|
31
31
|
"websocket"
|
|
32
32
|
],
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@girs/gio-2.0": "
|
|
35
|
-
"@girs/glib-2.0": "
|
|
36
|
-
"@girs/soup-3.0": "
|
|
37
|
-
"@gjsify/buffer": "^0.3.
|
|
38
|
-
"@gjsify/crypto": "^0.3.
|
|
39
|
-
"@gjsify/events": "^0.3.
|
|
40
|
-
"@gjsify/utils": "^0.3.
|
|
41
|
-
"@gjsify/websocket": "^0.3.
|
|
34
|
+
"@girs/gio-2.0": "2.88.0-4.0.0-rc.9",
|
|
35
|
+
"@girs/glib-2.0": "2.88.0-4.0.0-rc.9",
|
|
36
|
+
"@girs/soup-3.0": "3.6.6-4.0.0-rc.9",
|
|
37
|
+
"@gjsify/buffer": "^0.3.14",
|
|
38
|
+
"@gjsify/crypto": "^0.3.14",
|
|
39
|
+
"@gjsify/events": "^0.3.14",
|
|
40
|
+
"@gjsify/utils": "^0.3.14",
|
|
41
|
+
"@gjsify/websocket": "^0.3.14"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@gjsify/cli": "^0.3.
|
|
45
|
-
"@gjsify/unit": "^0.3.
|
|
44
|
+
"@gjsify/cli": "^0.3.14",
|
|
45
|
+
"@gjsify/unit": "^0.3.14",
|
|
46
46
|
"@types/node": "^25.6.0",
|
|
47
47
|
"typescript": "^6.0.3"
|
|
48
48
|
}
|