wreq-js 1.7.0 → 2.0.1

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/dist/wreq-js.js CHANGED
@@ -5,7 +5,7 @@ import { createRequire } from "module";
5
5
  import { ReadableStream } from "stream/web";
6
6
 
7
7
  // src/types.ts
8
- var RequestError = class extends Error {
8
+ var RequestError = class extends TypeError {
9
9
  constructor(message) {
10
10
  super(message);
11
11
  this.name = "RequestError";
@@ -86,6 +86,7 @@ var bodyHandleFinalizer = typeof FinalizationRegistry === "function" ? new Final
86
86
  }) : void 0;
87
87
  var DEFAULT_BROWSER = "chrome_142";
88
88
  var DEFAULT_OS = "macos";
89
+ var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
89
90
  var SUPPORTED_OSES = ["windows", "macos", "linux", "android", "ios"];
90
91
  var UTF8_DECODER = new TextDecoder("utf-8");
91
92
  var ephemeralIdCounter = 0;
@@ -254,27 +255,7 @@ var Headers = class _Headers {
254
255
  }
255
256
  };
256
257
  function headersToTuples(init) {
257
- if (Array.isArray(init)) {
258
- return init;
259
- }
260
- if (init instanceof Headers) {
261
- return init.toTuples();
262
- }
263
- const out = [];
264
- if (isPlainObject(init)) {
265
- return new Headers(init).toTuples();
266
- }
267
- if (isIterable(init)) {
268
- for (const tuple of init) {
269
- if (!tuple) {
270
- continue;
271
- }
272
- const [name, value] = tuple;
273
- out.push([name, value]);
274
- }
275
- return out;
276
- }
277
- return out;
258
+ return new Headers(init).toTuples();
278
259
  }
279
260
  function hasHeaderName(tuples, name) {
280
261
  if (!tuples) {
@@ -288,6 +269,24 @@ function hasHeaderName(tuples, name) {
288
269
  }
289
270
  return false;
290
271
  }
272
+ function hasWebSocketProtocolHeader(headers) {
273
+ const protocolHeaderName = "Sec-WebSocket-Protocol";
274
+ if (!headers) {
275
+ return false;
276
+ }
277
+ return hasHeaderName(headersToTuples(headers), protocolHeaderName);
278
+ }
279
+ function assertNoManualWebSocketProtocolHeader(headers) {
280
+ if (hasWebSocketProtocolHeader(headers)) {
281
+ throw new RequestError("Do not set `Sec-WebSocket-Protocol` header manually; use the `protocols` option instead.");
282
+ }
283
+ }
284
+ function normalizeWebSocketProtocolList(protocols) {
285
+ if (protocols === void 0) {
286
+ return void 0;
287
+ }
288
+ return typeof protocols === "string" ? [protocols] : [...protocols];
289
+ }
291
290
  function mergeHeaderTuples(defaults, overrides) {
292
291
  if (!defaults) {
293
292
  return overrides === void 0 ? void 0 : headersToTuples(overrides);
@@ -368,13 +367,16 @@ function createNativeBodyStream(handle) {
368
367
  }
369
368
  function wrapBodyStream(source, onFirstUse) {
370
369
  let started = false;
371
- const reader = source.getReader();
370
+ let reader = null;
372
371
  return new ReadableStream({
373
372
  async pull(controller) {
374
373
  if (!started) {
375
374
  started = true;
376
375
  onFirstUse();
377
376
  }
377
+ if (!reader) {
378
+ reader = source.getReader();
379
+ }
378
380
  try {
379
381
  const { done, value } = await reader.read();
380
382
  if (done) {
@@ -387,6 +389,9 @@ function wrapBodyStream(source, onFirstUse) {
387
389
  }
388
390
  },
389
391
  cancel(reason) {
392
+ if (!reader) {
393
+ return source.cancel(reason);
394
+ }
390
395
  return reader.cancel(reason);
391
396
  }
392
397
  });
@@ -533,8 +538,22 @@ var Response = class _Response {
533
538
  const bytes = await this.consumeBody();
534
539
  return UTF8_DECODER.decode(bytes);
535
540
  }
541
+ async blob() {
542
+ const bytes = await this.consumeBody();
543
+ const contentType = this.headers.get("content-type") ?? "";
544
+ return new Blob([bytes], contentType ? { type: contentType } : void 0);
545
+ }
546
+ async formData() {
547
+ const bytes = await this.consumeBody();
548
+ const contentType = this.headers.get("content-type");
549
+ const response = new globalThis.Response(
550
+ bytes,
551
+ contentType ? { headers: { "content-type": contentType } } : void 0
552
+ );
553
+ return response.formData();
554
+ }
536
555
  clone() {
537
- if (this.bodyUsed || this.bodyStream) {
556
+ if (this.bodyUsed) {
538
557
  throw new TypeError("Cannot clone a Response whose body is already used");
539
558
  }
540
559
  if (this.nativeHandleAvailable && this.payload.bodyHandle !== null) {
@@ -669,6 +688,36 @@ var Session = class {
669
688
  throw new RequestError(String(error));
670
689
  }
671
690
  }
691
+ async websocket(urlOrOptions, options) {
692
+ this.ensureActive();
693
+ const normalized = normalizeSessionWebSocketArgs(urlOrOptions, options);
694
+ validateWebSocketProtocols(normalized.options.protocols);
695
+ assertNoManualWebSocketProtocolHeader(normalized.options.headers);
696
+ const protocols = normalizeWebSocketProtocolList(normalized.options.protocols);
697
+ const transportId = this.defaults.transportId;
698
+ if (!transportId) {
699
+ throw new RequestError(
700
+ "Session has no transport. Create the session with browser/os options or pass a transport to use session.websocket()."
701
+ );
702
+ }
703
+ return WebSocket._connectWithInit({
704
+ _internal: true,
705
+ url: normalized.url,
706
+ options: normalized.options,
707
+ openDispatchMode: "deferred",
708
+ connect: (callbacks) => nativeBinding.websocketConnectSession({
709
+ url: normalized.url,
710
+ sessionId: this.id,
711
+ transportId,
712
+ headers: headersToTuples(normalized.options.headers ?? {}),
713
+ ...protocols && protocols.length > 0 && { protocols },
714
+ onMessage: callbacks.onMessage,
715
+ onClose: callbacks.onClose,
716
+ onError: callbacks.onError
717
+ }),
718
+ legacyCallbacks: normalized.legacyCallbacks
719
+ });
720
+ }
672
721
  async close() {
673
722
  if (this.disposed) {
674
723
  return;
@@ -749,7 +798,7 @@ function resolveTransportContext(config, sessionDefaults) {
749
798
  if (config.transport.closed) {
750
799
  throw new RequestError("Transport has been closed");
751
800
  }
752
- const hasProxy = Object.hasOwn(config, "proxy");
801
+ const hasProxy = config.proxy !== void 0;
753
802
  if (config.browser !== void 0 || config.os !== void 0 || hasProxy || config.insecure !== void 0) {
754
803
  throw new RequestError("`transport` cannot be combined with browser/os/proxy/insecure options");
755
804
  }
@@ -849,7 +898,7 @@ function setupAbort(signal, cancelNative) {
849
898
  return { promise, cleanup };
850
899
  }
851
900
  function coerceUrlInput(input) {
852
- if (typeof input !== "string") {
901
+ if (input instanceof URL) {
853
902
  return input.href;
854
903
  }
855
904
  if (input.length === 0) {
@@ -864,6 +913,41 @@ function coerceUrlInput(input) {
864
913
  }
865
914
  return trimmed;
866
915
  }
916
+ function isRequestLike(input) {
917
+ if (!input || typeof input !== "object") {
918
+ return false;
919
+ }
920
+ if (typeof Request !== "undefined" && input instanceof Request) {
921
+ return true;
922
+ }
923
+ const candidate = input;
924
+ return typeof candidate.url === "string" && typeof candidate.method === "string" && typeof candidate.arrayBuffer === "function" && typeof candidate.redirect === "string";
925
+ }
926
+ async function resolveFetchArgs(input, init) {
927
+ if (!isRequestLike(input)) {
928
+ return { url: coerceUrlInput(input), init: init ?? {} };
929
+ }
930
+ const mergedInit = init ? { ...init } : {};
931
+ if (mergedInit.method === void 0) {
932
+ mergedInit.method = input.method;
933
+ }
934
+ if (mergedInit.headers === void 0) {
935
+ mergedInit.headers = input.headers;
936
+ }
937
+ if (mergedInit.redirect === void 0 && (input.redirect === "follow" || input.redirect === "manual" || input.redirect === "error")) {
938
+ mergedInit.redirect = input.redirect;
939
+ }
940
+ if (mergedInit.signal === void 0) {
941
+ mergedInit.signal = input.signal;
942
+ }
943
+ if (mergedInit.body === void 0 && input.body !== null) {
944
+ if (input.bodyUsed) {
945
+ throw new TypeError("Request body is already used");
946
+ }
947
+ mergedInit.body = Buffer.from(await input.arrayBuffer());
948
+ }
949
+ return { url: coerceUrlInput(input.url), init: mergedInit };
950
+ }
867
951
  function normalizeUrlForComparison(value) {
868
952
  try {
869
953
  return new URL(value).toString();
@@ -939,17 +1023,23 @@ function ensureBodyAllowed(method, body) {
939
1023
  }
940
1024
  }
941
1025
  function validateBrowserProfile(browser) {
942
- if (!browser) {
1026
+ if (browser === void 0) {
943
1027
  return;
944
1028
  }
1029
+ if (typeof browser !== "string" || browser.trim().length === 0) {
1030
+ throw new RequestError("Browser profile must not be empty");
1031
+ }
945
1032
  if (!getProfileSet().has(browser)) {
946
1033
  throw new RequestError(`Invalid browser profile: ${browser}. Available profiles: ${getProfiles().join(", ")}`);
947
1034
  }
948
1035
  }
949
1036
  function validateOperatingSystem(os) {
950
- if (!os) {
1037
+ if (os === void 0) {
951
1038
  return;
952
1039
  }
1040
+ if (typeof os !== "string" || os.trim().length === 0) {
1041
+ throw new RequestError("Operating system must not be empty");
1042
+ }
953
1043
  if (!getOperatingSystemSet().has(os)) {
954
1044
  throw new RequestError(`Invalid operating system: ${os}. Available options: ${getOperatingSystems().join(", ")}`);
955
1045
  }
@@ -961,8 +1051,8 @@ function validateTimeout(timeout) {
961
1051
  if (typeof timeout !== "number" || !Number.isFinite(timeout)) {
962
1052
  throw new RequestError("Timeout must be a finite number");
963
1053
  }
964
- if (timeout <= 0) {
965
- throw new RequestError("Timeout must be greater than 0");
1054
+ if (timeout < 0) {
1055
+ throw new RequestError("Timeout must be 0 (no timeout) or a positive number");
966
1056
  }
967
1057
  }
968
1058
  function validatePositiveNumber(value, label) {
@@ -1029,8 +1119,9 @@ async function dispatchRequest(options, requestUrl, signal) {
1029
1119
  return new Response(payload, requestUrl);
1030
1120
  }
1031
1121
  async function fetch(input, init) {
1032
- const url = coerceUrlInput(input);
1033
- const config = init ?? {};
1122
+ const resolved = await resolveFetchArgs(input, init);
1123
+ const url = resolved.url;
1124
+ const config = resolved.init;
1034
1125
  const sessionContext = resolveSessionContext(config);
1035
1126
  const sessionDefaults = sessionContext.defaults;
1036
1127
  validateRedirectMode(config.redirect);
@@ -1049,7 +1140,7 @@ async function fetch(input, init) {
1049
1140
  headerTuples.push(["Content-Type", serializedBody.contentType]);
1050
1141
  }
1051
1142
  const transport = resolveTransportContext(config, sessionDefaults);
1052
- const timeout = config.timeout ?? sessionDefaults?.timeout;
1143
+ const timeout = config.timeout ?? sessionDefaults?.timeout ?? DEFAULT_REQUEST_TIMEOUT_MS;
1053
1144
  const requestOptions = {
1054
1145
  url,
1055
1146
  method,
@@ -1071,9 +1162,7 @@ async function fetch(input, init) {
1071
1162
  requestOptions.insecure = transport.insecure;
1072
1163
  }
1073
1164
  }
1074
- if (timeout !== void 0) {
1075
- requestOptions.timeout = timeout;
1076
- }
1165
+ requestOptions.timeout = timeout;
1077
1166
  if (config.redirect !== void 0) {
1078
1167
  requestOptions.redirect = config.redirect;
1079
1168
  }
@@ -1261,84 +1350,696 @@ async function post(url, body, init) {
1261
1350
  }
1262
1351
  return fetch(url, config);
1263
1352
  }
1264
- var WebSocket = class {
1353
+ function normalizeWebSocketUrl(url) {
1354
+ const normalized = String(url).trim();
1355
+ if (!normalized) {
1356
+ throw new RequestError("URL is required");
1357
+ }
1358
+ let parsed;
1359
+ try {
1360
+ parsed = new URL(normalized);
1361
+ } catch (error) {
1362
+ throw new RequestError(String(error));
1363
+ }
1364
+ if (parsed.hash) {
1365
+ throw new RequestError("WebSocket URL must not include a hash fragment");
1366
+ }
1367
+ if (parsed.protocol === "http:") {
1368
+ parsed.protocol = "ws:";
1369
+ } else if (parsed.protocol === "https:") {
1370
+ parsed.protocol = "wss:";
1371
+ }
1372
+ if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") {
1373
+ throw new RequestError("expected a ws: or wss: url");
1374
+ }
1375
+ return parsed.toString();
1376
+ }
1377
+ function validateWebSocketProtocols(protocols) {
1378
+ if (protocols === void 0) {
1379
+ return;
1380
+ }
1381
+ const protocolList = typeof protocols === "string" ? [protocols] : protocols;
1382
+ const seen = /* @__PURE__ */ new Set();
1383
+ const validToken = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
1384
+ for (const protocol of protocolList) {
1385
+ if (typeof protocol !== "string" || protocol.length === 0) {
1386
+ throw new RequestError("WebSocket protocol values must be non-empty strings");
1387
+ }
1388
+ if (!validToken.test(protocol)) {
1389
+ throw new RequestError(`Invalid WebSocket protocol value: ${protocol}`);
1390
+ }
1391
+ if (seen.has(protocol)) {
1392
+ throw new RequestError(`Duplicate WebSocket protocol: ${protocol}`);
1393
+ }
1394
+ seen.add(protocol);
1395
+ }
1396
+ }
1397
+ function normalizeStandaloneWebSocketOptions(options) {
1398
+ const normalized = {};
1399
+ if (!options) {
1400
+ return normalized;
1401
+ }
1402
+ if (options.browser !== void 0) {
1403
+ normalized.browser = options.browser;
1404
+ }
1405
+ if (options.os !== void 0) {
1406
+ normalized.os = options.os;
1407
+ }
1408
+ if (options.headers !== void 0) {
1409
+ normalized.headers = options.headers;
1410
+ }
1411
+ if (options.proxy !== void 0) {
1412
+ normalized.proxy = options.proxy;
1413
+ }
1414
+ if (options.protocols !== void 0) {
1415
+ normalized.protocols = options.protocols;
1416
+ }
1417
+ if (options.binaryType !== void 0) {
1418
+ if (options.binaryType !== "nodebuffer" && options.binaryType !== "arraybuffer" && options.binaryType !== "blob") {
1419
+ throw new RequestError("binaryType must be one of: 'nodebuffer', 'arraybuffer', 'blob'");
1420
+ }
1421
+ normalized.binaryType = options.binaryType;
1422
+ }
1423
+ return normalized;
1424
+ }
1425
+ function normalizeSessionWebSocketOptions(options) {
1426
+ const normalized = {};
1427
+ if (!options) {
1428
+ return normalized;
1429
+ }
1430
+ const optionsWithOverrides = options;
1431
+ if (optionsWithOverrides.browser !== void 0) {
1432
+ throw new RequestError(
1433
+ "`browser` is not supported in session.websocket(); the session controls browser emulation."
1434
+ );
1435
+ }
1436
+ if (optionsWithOverrides.os !== void 0) {
1437
+ throw new RequestError("`os` is not supported in session.websocket(); the session controls OS emulation.");
1438
+ }
1439
+ if (optionsWithOverrides.proxy !== void 0) {
1440
+ throw new RequestError("`proxy` is not supported in session.websocket(); the session transport controls proxying.");
1441
+ }
1442
+ if (options.headers !== void 0) {
1443
+ normalized.headers = options.headers;
1444
+ }
1445
+ if (options.protocols !== void 0) {
1446
+ normalized.protocols = options.protocols;
1447
+ }
1448
+ if (options.binaryType !== void 0) {
1449
+ if (options.binaryType !== "nodebuffer" && options.binaryType !== "arraybuffer" && options.binaryType !== "blob") {
1450
+ throw new RequestError("binaryType must be one of: 'nodebuffer', 'arraybuffer', 'blob'");
1451
+ }
1452
+ normalized.binaryType = options.binaryType;
1453
+ }
1454
+ return normalized;
1455
+ }
1456
+ function extractLegacyWebSocketCallbacks(options) {
1457
+ if (!isPlainObject(options)) {
1458
+ return void 0;
1459
+ }
1460
+ const maybeCallbacks = options;
1461
+ const callbacks = {};
1462
+ if (typeof maybeCallbacks.onMessage === "function") {
1463
+ callbacks.onMessage = maybeCallbacks.onMessage;
1464
+ }
1465
+ if (typeof maybeCallbacks.onClose === "function") {
1466
+ callbacks.onClose = maybeCallbacks.onClose;
1467
+ }
1468
+ if (typeof maybeCallbacks.onError === "function") {
1469
+ callbacks.onError = maybeCallbacks.onError;
1470
+ }
1471
+ return Object.keys(callbacks).length > 0 ? callbacks : void 0;
1472
+ }
1473
+ function normalizeWebSocketCloseOptions(code, reason) {
1474
+ if (code === void 0 && reason === void 0) {
1475
+ return void 0;
1476
+ }
1477
+ if (code === void 0) {
1478
+ throw new RequestError("A close code is required when providing a close reason");
1479
+ }
1480
+ if (!Number.isInteger(code)) {
1481
+ throw new RequestError("Close code must be an integer");
1482
+ }
1483
+ if (code !== 1e3 && (code < 3e3 || code > 4999)) {
1484
+ throw new RequestError("Close code must be 1000 or in range 3000-4999");
1485
+ }
1486
+ const normalizedReason = reason ?? "";
1487
+ if (Buffer.byteLength(normalizedReason, "utf8") > 123) {
1488
+ throw new RequestError("Close reason must be 123 bytes or fewer");
1489
+ }
1490
+ return {
1491
+ code,
1492
+ reason: normalizedReason
1493
+ };
1494
+ }
1495
+ function isWebSocketListenerType(type) {
1496
+ return type === "open" || type === "message" || type === "close" || type === "error";
1497
+ }
1498
+ var WebSocket = class _WebSocket {
1499
+ static CONNECTING = 0;
1500
+ static OPEN = 1;
1501
+ static CLOSING = 2;
1502
+ static CLOSED = 3;
1503
+ url;
1504
+ protocol = "";
1505
+ extensions = "";
1506
+ readyState = _WebSocket.CONNECTING;
1507
+ _binaryType = "nodebuffer";
1508
+ _bufferedAmount = 0;
1509
+ _onopen = null;
1510
+ _onmessage = null;
1511
+ _onclose = null;
1512
+ _onerror = null;
1513
+ _onHandlerOrder = {
1514
+ open: -1,
1515
+ message: -1,
1516
+ close: -1,
1517
+ error: -1
1518
+ };
1519
+ _listenerOrderCounter = 0;
1520
+ _listeners = {
1521
+ open: /* @__PURE__ */ new Map(),
1522
+ message: /* @__PURE__ */ new Map(),
1523
+ close: /* @__PURE__ */ new Map(),
1524
+ error: /* @__PURE__ */ new Map()
1525
+ };
1526
+ _legacyCallbacks;
1527
+ _openDispatchMode;
1265
1528
  _connection;
1529
+ _connectPromise;
1530
+ _closeOptions;
1266
1531
  _finalizerToken;
1267
- _closed = false;
1268
- _closing;
1269
- constructor(connection) {
1270
- this._connection = connection;
1271
- if (websocketFinalizer) {
1272
- this._finalizerToken = connection;
1273
- websocketFinalizer.register(this, connection, connection);
1274
- }
1275
- }
1276
- /**
1277
- * Send a message (text or binary)
1278
- */
1279
- async send(data) {
1280
- const payload = typeof data === "string" ? data : Buffer.isBuffer(data) ? data : data instanceof ArrayBuffer ? Buffer.from(data) : ArrayBuffer.isView(data) ? Buffer.from(data.buffer, data.byteOffset, data.byteLength) : (() => {
1281
- throw new TypeError("WebSocket data must be a string, Buffer, ArrayBuffer, or ArrayBufferView");
1282
- })();
1532
+ _openEventDispatched = false;
1533
+ _openEventQueued = false;
1534
+ _closeEventDispatched = false;
1535
+ _nativeCloseStarted = false;
1536
+ _pendingMessages = [];
1537
+ _sendChain = Promise.resolve();
1538
+ constructor(urlOrInit, protocolsOrOptions, maybeOptions) {
1539
+ let init;
1540
+ if (isInternalWebSocketInit(urlOrInit)) {
1541
+ init = urlOrInit;
1542
+ } else {
1543
+ init = _WebSocket.buildStandaloneInit(urlOrInit, protocolsOrOptions, maybeOptions);
1544
+ }
1545
+ this.url = init.url;
1546
+ this.binaryType = init.options.binaryType ?? "nodebuffer";
1547
+ this._legacyCallbacks = init.legacyCallbacks;
1548
+ this._openDispatchMode = init.openDispatchMode;
1549
+ this._connectPromise = this.connect(init.connect);
1550
+ void this._connectPromise.catch(() => void 0);
1551
+ }
1552
+ get binaryType() {
1553
+ return this._binaryType;
1554
+ }
1555
+ set binaryType(value) {
1556
+ if (value === "arraybuffer" || value === "blob" || value === "nodebuffer") {
1557
+ this._binaryType = value;
1558
+ }
1559
+ }
1560
+ get bufferedAmount() {
1561
+ return this._bufferedAmount;
1562
+ }
1563
+ get onopen() {
1564
+ return this._onopen;
1565
+ }
1566
+ set onopen(listener) {
1567
+ this._onopen = listener;
1568
+ this._onHandlerOrder.open = listener ? ++this._listenerOrderCounter : -1;
1569
+ }
1570
+ get onmessage() {
1571
+ return this._onmessage;
1572
+ }
1573
+ set onmessage(listener) {
1574
+ this._onmessage = listener;
1575
+ this._onHandlerOrder.message = listener ? ++this._listenerOrderCounter : -1;
1576
+ }
1577
+ get onclose() {
1578
+ return this._onclose;
1579
+ }
1580
+ set onclose(listener) {
1581
+ this._onclose = listener;
1582
+ this._onHandlerOrder.close = listener ? ++this._listenerOrderCounter : -1;
1583
+ }
1584
+ get onerror() {
1585
+ return this._onerror;
1586
+ }
1587
+ set onerror(listener) {
1588
+ this._onerror = listener;
1589
+ this._onHandlerOrder.error = listener ? ++this._listenerOrderCounter : -1;
1590
+ }
1591
+ static async _connectWithInit(init) {
1592
+ const ws = new _WebSocket(init);
1593
+ await ws._waitUntilConnected();
1594
+ ws.scheduleOpenEventAfterAwait();
1595
+ return ws;
1596
+ }
1597
+ static buildStandaloneInit(url, protocolsOrOptions, maybeOptions) {
1598
+ const optionsCandidate = typeof protocolsOrOptions === "string" || Array.isArray(protocolsOrOptions) ? maybeOptions : protocolsOrOptions ?? maybeOptions;
1599
+ const normalizedOptions = normalizeStandaloneWebSocketOptions(optionsCandidate);
1600
+ validateWebSocketProtocols(
1601
+ typeof protocolsOrOptions === "string" || Array.isArray(protocolsOrOptions) ? protocolsOrOptions : normalizedOptions.protocols
1602
+ );
1603
+ assertNoManualWebSocketProtocolHeader(normalizedOptions.headers);
1604
+ validateBrowserProfile(normalizedOptions.browser);
1605
+ const os = normalizedOptions.os ?? DEFAULT_OS;
1606
+ validateOperatingSystem(os);
1607
+ const browser = normalizedOptions.browser ?? DEFAULT_BROWSER;
1608
+ const protocols = normalizeWebSocketProtocolList(
1609
+ typeof protocolsOrOptions === "string" || Array.isArray(protocolsOrOptions) ? protocolsOrOptions : normalizedOptions.protocols
1610
+ );
1611
+ return {
1612
+ _internal: true,
1613
+ url: normalizeWebSocketUrl(url),
1614
+ options: normalizedOptions,
1615
+ openDispatchMode: "automatic",
1616
+ connect: (callbacks) => nativeBinding.websocketConnect({
1617
+ url: normalizeWebSocketUrl(url),
1618
+ browser,
1619
+ os,
1620
+ headers: headersToTuples(normalizedOptions.headers ?? {}),
1621
+ ...protocols && protocols.length > 0 && { protocols },
1622
+ ...normalizedOptions.proxy !== void 0 && { proxy: normalizedOptions.proxy },
1623
+ onMessage: callbacks.onMessage,
1624
+ onClose: callbacks.onClose,
1625
+ onError: callbacks.onError
1626
+ }),
1627
+ legacyCallbacks: extractLegacyWebSocketCallbacks(optionsCandidate)
1628
+ };
1629
+ }
1630
+ async connect(connectFn) {
1283
1631
  try {
1284
- await nativeBinding.websocketSend(this._connection, payload);
1632
+ const connection = await connectFn({
1633
+ onMessage: (data) => {
1634
+ this.handleNativeMessage(data);
1635
+ },
1636
+ onClose: (event) => {
1637
+ this.handleNativeClose(event);
1638
+ },
1639
+ onError: (message) => {
1640
+ this.handleNativeError(message);
1641
+ }
1642
+ });
1643
+ this._connection = connection;
1644
+ this.protocol = connection.protocol ?? "";
1645
+ this.extensions = connection.extensions ?? "";
1646
+ if (websocketFinalizer) {
1647
+ this._finalizerToken = connection;
1648
+ websocketFinalizer.register(this, connection, connection);
1649
+ }
1650
+ if (this.readyState === _WebSocket.CLOSING) {
1651
+ this.startNativeClose();
1652
+ return;
1653
+ }
1654
+ this.readyState = _WebSocket.OPEN;
1655
+ if (this._openDispatchMode === "automatic") {
1656
+ this.scheduleOpenEventAfterConnect();
1657
+ }
1285
1658
  } catch (error) {
1659
+ this.handleNativeError(String(error));
1660
+ this.finalizeClosed({ code: 1006, reason: "" }, false);
1286
1661
  throw new RequestError(String(error));
1287
1662
  }
1288
1663
  }
1289
- /**
1290
- * Close the WebSocket connection
1291
- */
1292
- async close() {
1293
- if (this._closed) {
1664
+ _waitUntilConnected() {
1665
+ return this._connectPromise;
1666
+ }
1667
+ scheduleOpenEventAfterConnect() {
1668
+ this.scheduleOpenEventWithDepth(2);
1669
+ }
1670
+ scheduleOpenEventAfterAwait() {
1671
+ this.scheduleOpenEventWithDepth(3);
1672
+ }
1673
+ scheduleOpenEventWithDepth(depth) {
1674
+ if (this._openEventDispatched || this._openEventQueued || this.readyState !== _WebSocket.OPEN) {
1675
+ return;
1676
+ }
1677
+ this._openEventQueued = true;
1678
+ const queue = (remaining) => {
1679
+ if (remaining === 0) {
1680
+ this._openEventQueued = false;
1681
+ if (this._openEventDispatched || this.readyState !== _WebSocket.OPEN) {
1682
+ return;
1683
+ }
1684
+ this._openEventDispatched = true;
1685
+ this.dispatchOpenEvent();
1686
+ return;
1687
+ }
1688
+ queueMicrotask(() => {
1689
+ queue(remaining - 1);
1690
+ });
1691
+ };
1692
+ queue(depth);
1693
+ }
1694
+ releaseConnectionTracking() {
1695
+ if (!this._finalizerToken || !websocketFinalizer) {
1696
+ return;
1697
+ }
1698
+ websocketFinalizer.unregister(this._finalizerToken);
1699
+ this._finalizerToken = void 0;
1700
+ }
1701
+ toMessageEventData(data) {
1702
+ if (typeof data === "string") {
1703
+ return data;
1704
+ }
1705
+ if (this._binaryType === "arraybuffer") {
1706
+ const arrayBuffer = new ArrayBuffer(data.byteLength);
1707
+ new Uint8Array(arrayBuffer).set(data);
1708
+ return arrayBuffer;
1709
+ }
1710
+ if (this._binaryType === "blob") {
1711
+ return new Blob([data]);
1712
+ }
1713
+ return data;
1714
+ }
1715
+ invokeListener(listener, event) {
1716
+ try {
1717
+ if (typeof listener === "function") {
1718
+ listener.call(this, event);
1719
+ } else {
1720
+ listener.handleEvent(event);
1721
+ }
1722
+ } catch {
1723
+ }
1724
+ }
1725
+ createBaseEvent(type) {
1726
+ return {
1727
+ type,
1728
+ isTrusted: false,
1729
+ timeStamp: Date.now(),
1730
+ target: this,
1731
+ currentTarget: this
1732
+ };
1733
+ }
1734
+ getOnHandler(type) {
1735
+ switch (type) {
1736
+ case "open":
1737
+ return this._onopen;
1738
+ case "message":
1739
+ return this._onmessage;
1740
+ case "close":
1741
+ return this._onclose;
1742
+ case "error":
1743
+ return this._onerror;
1744
+ default:
1745
+ return null;
1746
+ }
1747
+ }
1748
+ getOnHandlerOrder(type) {
1749
+ return this._onHandlerOrder[type];
1750
+ }
1751
+ getListenerMap(type) {
1752
+ return this._listeners[type];
1753
+ }
1754
+ dispatchEvent(type, event) {
1755
+ const listenerMap = this.getListenerMap(type);
1756
+ const onHandler = this.getOnHandler(type);
1757
+ if (listenerMap.size === 0 && !onHandler) {
1758
+ return;
1759
+ }
1760
+ const ordered = [];
1761
+ for (const descriptor of listenerMap.values()) {
1762
+ ordered.push({
1763
+ order: descriptor.order,
1764
+ listener: descriptor.listener,
1765
+ once: descriptor.once
1766
+ });
1767
+ }
1768
+ if (onHandler) {
1769
+ ordered.push({
1770
+ order: this.getOnHandlerOrder(type),
1771
+ listener: onHandler,
1772
+ once: false
1773
+ });
1774
+ }
1775
+ ordered.sort((a, b) => a.order - b.order);
1776
+ for (const entry of ordered) {
1777
+ if (entry.once) {
1778
+ this.removeEventListener(type, entry.listener);
1779
+ }
1780
+ this.invokeListener(entry.listener, event);
1781
+ }
1782
+ }
1783
+ dispatchOpenEvent() {
1784
+ const event = this.createBaseEvent("open");
1785
+ this.dispatchEvent("open", event);
1786
+ if (!this._closeEventDispatched && this._pendingMessages.length > 0) {
1787
+ const pending = this._pendingMessages;
1788
+ this._pendingMessages = [];
1789
+ for (const data of pending) {
1790
+ this.dispatchMessageEvent(this.toMessageEventData(data));
1791
+ }
1792
+ }
1793
+ }
1794
+ dispatchMessageEvent(data) {
1795
+ const event = {
1796
+ ...this.createBaseEvent("message"),
1797
+ data
1798
+ };
1799
+ this.dispatchEvent("message", event);
1800
+ }
1801
+ dispatchCloseEvent(event) {
1802
+ this.dispatchEvent("close", event);
1803
+ }
1804
+ dispatchErrorEvent(message) {
1805
+ const event = {
1806
+ ...this.createBaseEvent("error"),
1807
+ ...message !== void 0 && { message }
1808
+ };
1809
+ this.dispatchEvent("error", event);
1810
+ }
1811
+ handleNativeMessage(data) {
1812
+ if (this._closeEventDispatched) {
1813
+ return;
1814
+ }
1815
+ this._legacyCallbacks?.onMessage?.(data);
1816
+ if (!this._openEventDispatched && this.readyState === _WebSocket.OPEN) {
1817
+ this._pendingMessages.push(data);
1818
+ return;
1819
+ }
1820
+ this.dispatchMessageEvent(this.toMessageEventData(data));
1821
+ }
1822
+ handleNativeError(message) {
1823
+ this._legacyCallbacks?.onError?.(message);
1824
+ this.dispatchErrorEvent(message);
1825
+ }
1826
+ handleNativeClose(event) {
1827
+ const wasClean = this.readyState === _WebSocket.CLOSING || event.code === 1e3;
1828
+ this.finalizeClosed(event, wasClean);
1829
+ }
1830
+ finalizeClosed(event, wasClean) {
1831
+ if (this._closeEventDispatched) {
1832
+ return;
1833
+ }
1834
+ this.readyState = _WebSocket.CLOSED;
1835
+ this._closeEventDispatched = true;
1836
+ this._pendingMessages = [];
1837
+ this.releaseConnectionTracking();
1838
+ const closeEvent = {
1839
+ ...this.createBaseEvent("close"),
1840
+ code: event.code,
1841
+ reason: event.reason,
1842
+ wasClean
1843
+ };
1844
+ this._legacyCallbacks?.onClose?.(closeEvent);
1845
+ this.dispatchCloseEvent(closeEvent);
1846
+ }
1847
+ startNativeClose() {
1848
+ if (this._nativeCloseStarted || !this._connection) {
1294
1849
  return;
1295
1850
  }
1296
- if (this._closing) {
1297
- return this._closing;
1851
+ this._nativeCloseStarted = true;
1852
+ const connection = this._connection;
1853
+ const closeOptions = this._closeOptions;
1854
+ void nativeBinding.websocketClose(connection, closeOptions).catch((error) => {
1855
+ this.handleNativeError(String(error));
1856
+ this.finalizeClosed({ code: 1006, reason: "" }, false);
1857
+ });
1858
+ }
1859
+ addEventListener(type, listener, options) {
1860
+ if (!listener || !isWebSocketListenerType(type)) {
1861
+ return;
1862
+ }
1863
+ const normalizedListener = listener;
1864
+ if (typeof normalizedListener !== "function" && (typeof normalizedListener !== "object" || normalizedListener === null || typeof normalizedListener.handleEvent !== "function")) {
1865
+ return;
1866
+ }
1867
+ const listenerMap = this.getListenerMap(type);
1868
+ if (listenerMap.has(normalizedListener)) {
1869
+ return;
1870
+ }
1871
+ const parsedOptions = typeof options === "boolean" ? {} : options ?? {};
1872
+ const once = parsedOptions.once === true;
1873
+ const signal = parsedOptions.signal;
1874
+ if (signal?.aborted) {
1875
+ return;
1876
+ }
1877
+ const descriptor = {
1878
+ listener: normalizedListener,
1879
+ order: ++this._listenerOrderCounter,
1880
+ once
1881
+ };
1882
+ if (signal) {
1883
+ const onAbort = () => {
1884
+ this.removeEventListener(type, normalizedListener);
1885
+ };
1886
+ descriptor.abortSignal = signal;
1887
+ descriptor.abortHandler = onAbort;
1888
+ signal.addEventListener("abort", onAbort, { once: true });
1889
+ }
1890
+ listenerMap.set(normalizedListener, descriptor);
1891
+ }
1892
+ removeEventListener(type, listener) {
1893
+ if (!listener || !isWebSocketListenerType(type)) {
1894
+ return;
1895
+ }
1896
+ const normalizedListener = listener;
1897
+ if (typeof normalizedListener !== "function" && typeof normalizedListener !== "object") {
1898
+ return;
1899
+ }
1900
+ const listenerMap = this.getListenerMap(type);
1901
+ const descriptor = listenerMap.get(normalizedListener);
1902
+ if (!descriptor) {
1903
+ return;
1904
+ }
1905
+ if (descriptor.abortSignal && descriptor.abortHandler) {
1906
+ descriptor.abortSignal.removeEventListener("abort", descriptor.abortHandler);
1907
+ }
1908
+ listenerMap.delete(normalizedListener);
1909
+ }
1910
+ getSendByteLength(data) {
1911
+ if (typeof data === "string") {
1912
+ return Buffer.byteLength(data);
1913
+ }
1914
+ if (Buffer.isBuffer(data)) {
1915
+ return data.byteLength;
1916
+ }
1917
+ if (data instanceof ArrayBuffer) {
1918
+ return data.byteLength;
1919
+ }
1920
+ if (ArrayBuffer.isView(data)) {
1921
+ return data.byteLength;
1922
+ }
1923
+ if (typeof Blob !== "undefined" && data instanceof Blob) {
1924
+ return data.size;
1298
1925
  }
1299
- this._closing = (async () => {
1926
+ throw new TypeError("WebSocket data must be a string, Buffer, ArrayBuffer, ArrayBufferView, or Blob");
1927
+ }
1928
+ async normalizeSendPayload(data) {
1929
+ if (typeof data === "string") {
1930
+ return data;
1931
+ }
1932
+ if (Buffer.isBuffer(data)) {
1933
+ return data;
1934
+ }
1935
+ if (data instanceof ArrayBuffer) {
1936
+ return Buffer.from(data);
1937
+ }
1938
+ if (ArrayBuffer.isView(data)) {
1939
+ return Buffer.from(data.buffer, data.byteOffset, data.byteLength);
1940
+ }
1941
+ if (typeof Blob !== "undefined" && data instanceof Blob) {
1942
+ return Buffer.from(await data.arrayBuffer());
1943
+ }
1944
+ throw new TypeError("WebSocket data must be a string, Buffer, ArrayBuffer, ArrayBufferView, or Blob");
1945
+ }
1946
+ send(data) {
1947
+ if (this.readyState !== _WebSocket.OPEN || !this._connection) {
1948
+ throw new RequestError("WebSocket is not open");
1949
+ }
1950
+ const queuedBytes = this.getSendByteLength(data);
1951
+ const connection = this._connection;
1952
+ this._bufferedAmount += queuedBytes;
1953
+ const sendTask = async () => {
1300
1954
  try {
1301
- await nativeBinding.websocketClose(this._connection);
1302
- this._closed = true;
1303
- if (this._finalizerToken && websocketFinalizer) {
1304
- websocketFinalizer.unregister(this._finalizerToken);
1305
- this._finalizerToken = void 0;
1306
- }
1955
+ const payload = await this.normalizeSendPayload(data);
1956
+ await nativeBinding.websocketSend(connection, payload);
1307
1957
  } catch (error) {
1308
- throw new RequestError(String(error));
1958
+ this.handleNativeError(String(error));
1959
+ this.finalizeClosed({ code: 1006, reason: "" }, false);
1309
1960
  } finally {
1310
- this._closing = void 0;
1961
+ this._bufferedAmount = Math.max(0, this._bufferedAmount - queuedBytes);
1311
1962
  }
1312
- })();
1313
- return this._closing;
1963
+ };
1964
+ this._sendChain = this._sendChain.then(sendTask, sendTask);
1965
+ }
1966
+ close(code, reason) {
1967
+ if (this.readyState === _WebSocket.CLOSING || this.readyState === _WebSocket.CLOSED) {
1968
+ return;
1969
+ }
1970
+ this._closeOptions = normalizeWebSocketCloseOptions(code, reason);
1971
+ this.readyState = _WebSocket.CLOSING;
1972
+ this.startNativeClose();
1314
1973
  }
1315
1974
  };
1316
- async function websocket(options) {
1317
- if (!options.url) {
1318
- throw new RequestError("URL is required");
1975
+ function isInternalWebSocketInit(value) {
1976
+ if (!isPlainObject(value)) {
1977
+ return false;
1319
1978
  }
1320
- if (!options.onMessage) {
1321
- throw new RequestError("onMessage callback is required");
1979
+ const candidate = value;
1980
+ return candidate._internal === true && typeof candidate.url === "string" && typeof candidate.connect === "function";
1981
+ }
1982
+ function normalizeStandaloneWebSocketArgs(urlOrOptions, options) {
1983
+ if (typeof urlOrOptions === "string" || urlOrOptions instanceof URL) {
1984
+ const normalizedOptions2 = normalizeStandaloneWebSocketOptions(options);
1985
+ return {
1986
+ url: normalizeWebSocketUrl(urlOrOptions),
1987
+ options: normalizedOptions2,
1988
+ legacyCallbacks: extractLegacyWebSocketCallbacks(options)
1989
+ };
1322
1990
  }
1323
- validateBrowserProfile(options.browser);
1324
- const os = options.os ?? DEFAULT_OS;
1991
+ const legacy = urlOrOptions;
1992
+ const normalizedOptions = normalizeStandaloneWebSocketOptions(legacy);
1993
+ return {
1994
+ url: normalizeWebSocketUrl(legacy.url),
1995
+ options: normalizedOptions,
1996
+ legacyCallbacks: extractLegacyWebSocketCallbacks(legacy)
1997
+ };
1998
+ }
1999
+ function normalizeSessionWebSocketArgs(urlOrOptions, options) {
2000
+ if (typeof urlOrOptions === "string" || urlOrOptions instanceof URL) {
2001
+ const normalizedOptions2 = normalizeSessionWebSocketOptions(options);
2002
+ return {
2003
+ url: normalizeWebSocketUrl(urlOrOptions),
2004
+ options: normalizedOptions2,
2005
+ legacyCallbacks: extractLegacyWebSocketCallbacks(options)
2006
+ };
2007
+ }
2008
+ const legacy = urlOrOptions;
2009
+ const normalizedOptions = normalizeSessionWebSocketOptions(legacy);
2010
+ return {
2011
+ url: normalizeWebSocketUrl(legacy.url),
2012
+ options: normalizedOptions,
2013
+ legacyCallbacks: extractLegacyWebSocketCallbacks(legacy)
2014
+ };
2015
+ }
2016
+ async function websocket(urlOrOptions, options) {
2017
+ const normalized = normalizeStandaloneWebSocketArgs(urlOrOptions, options);
2018
+ validateWebSocketProtocols(normalized.options.protocols);
2019
+ assertNoManualWebSocketProtocolHeader(normalized.options.headers);
2020
+ validateBrowserProfile(normalized.options.browser);
2021
+ const os = normalized.options.os ?? DEFAULT_OS;
1325
2022
  validateOperatingSystem(os);
1326
- const browser = options.browser ?? DEFAULT_BROWSER;
1327
- try {
1328
- const connection = await nativeBinding.websocketConnect({
1329
- url: options.url,
2023
+ const browser = normalized.options.browser ?? DEFAULT_BROWSER;
2024
+ const protocols = normalizeWebSocketProtocolList(normalized.options.protocols);
2025
+ return WebSocket._connectWithInit({
2026
+ _internal: true,
2027
+ url: normalized.url,
2028
+ options: normalized.options,
2029
+ openDispatchMode: "deferred",
2030
+ connect: (callbacks) => nativeBinding.websocketConnect({
2031
+ url: normalized.url,
1330
2032
  browser,
1331
2033
  os,
1332
- headers: options.headers ?? {},
1333
- ...options.proxy !== void 0 && { proxy: options.proxy },
1334
- onMessage: options.onMessage,
1335
- ...options.onClose !== void 0 && { onClose: options.onClose },
1336
- ...options.onError !== void 0 && { onError: options.onError }
1337
- });
1338
- return new WebSocket(connection);
1339
- } catch (error) {
1340
- throw new RequestError(String(error));
1341
- }
2034
+ headers: headersToTuples(normalized.options.headers ?? {}),
2035
+ ...protocols && protocols.length > 0 && { protocols },
2036
+ ...normalized.options.proxy !== void 0 && { proxy: normalized.options.proxy },
2037
+ onMessage: callbacks.onMessage,
2038
+ onClose: callbacks.onClose,
2039
+ onError: callbacks.onError
2040
+ }),
2041
+ legacyCallbacks: normalized.legacyCallbacks
2042
+ });
1342
2043
  }
1343
2044
  var wreq_js_default = {
1344
2045
  fetch,
@@ -1355,7 +2056,8 @@ var wreq_js_default = {
1355
2056
  Headers,
1356
2057
  Response,
1357
2058
  Transport,
1358
- Session
2059
+ Session,
2060
+ RequestError
1359
2061
  };
1360
2062
  export {
1361
2063
  Headers,