wreq-js 1.6.1 → 2.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/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";
@@ -15,7 +15,9 @@ var RequestError = class extends Error {
15
15
  // src/wreq-js.ts
16
16
  var nativeBinding;
17
17
  var cachedProfiles;
18
+ var cachedProfileSet;
18
19
  var cachedOperatingSystems;
20
+ var cachedOperatingSystemSet;
19
21
  function detectLibc() {
20
22
  if (process.platform !== "linux") {
21
23
  return void 0;
@@ -84,8 +86,13 @@ var bodyHandleFinalizer = typeof FinalizationRegistry === "function" ? new Final
84
86
  }) : void 0;
85
87
  var DEFAULT_BROWSER = "chrome_142";
86
88
  var DEFAULT_OS = "macos";
89
+ var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
87
90
  var SUPPORTED_OSES = ["windows", "macos", "linux", "android", "ios"];
88
91
  var UTF8_DECODER = new TextDecoder("utf-8");
92
+ var ephemeralIdCounter = 0;
93
+ function generateEphemeralSessionId() {
94
+ return `_e${++ephemeralIdCounter}`;
95
+ }
89
96
  function generateSessionId() {
90
97
  return randomUUID();
91
98
  }
@@ -248,37 +255,37 @@ var Headers = class _Headers {
248
255
  }
249
256
  };
250
257
  function headersToTuples(init) {
251
- if (Array.isArray(init)) {
252
- return init;
253
- }
254
- if (init instanceof Headers) {
255
- return init.toTuples();
258
+ return new Headers(init).toTuples();
259
+ }
260
+ function hasHeaderName(tuples, name) {
261
+ if (!tuples) {
262
+ return false;
256
263
  }
257
- const out = [];
258
- if (isPlainObject(init)) {
259
- for (const name in init) {
260
- if (!Object.hasOwn(init, name)) {
261
- continue;
262
- }
263
- const value = init[name];
264
- if (value === void 0 || value === null) {
265
- continue;
266
- }
267
- out.push([name, String(value)]);
264
+ const target = name.toLowerCase();
265
+ for (const [headerName] of tuples) {
266
+ if (headerName.toLowerCase() === target) {
267
+ return true;
268
268
  }
269
- return out;
270
269
  }
271
- if (isIterable(init)) {
272
- for (const tuple of init) {
273
- if (!tuple) {
274
- continue;
275
- }
276
- const [name, value] = tuple;
277
- out.push([name, value]);
278
- }
279
- return out;
270
+ return false;
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;
280
287
  }
281
- return out;
288
+ return typeof protocols === "string" ? [protocols] : [...protocols];
282
289
  }
283
290
  function mergeHeaderTuples(defaults, overrides) {
284
291
  if (!defaults) {
@@ -291,9 +298,19 @@ function mergeHeaderTuples(defaults, overrides) {
291
298
  if (overrideTuples.length === 0) {
292
299
  return defaults;
293
300
  }
294
- const overrideKeys = new Set(overrideTuples.map(([name]) => name.toLowerCase()));
295
- const merged = defaults.filter(([name]) => !overrideKeys.has(name.toLowerCase()));
296
- merged.push(...overrideTuples);
301
+ const overrideKeys = /* @__PURE__ */ new Set();
302
+ for (const tuple of overrideTuples) {
303
+ overrideKeys.add(tuple[0].toLowerCase());
304
+ }
305
+ const merged = [];
306
+ for (const tuple of defaults) {
307
+ if (!overrideKeys.has(tuple[0].toLowerCase())) {
308
+ merged.push(tuple);
309
+ }
310
+ }
311
+ for (const tuple of overrideTuples) {
312
+ merged.push(tuple);
313
+ }
297
314
  return merged;
298
315
  }
299
316
  function cloneNativeResponse(payload) {
@@ -350,13 +367,16 @@ function createNativeBodyStream(handle) {
350
367
  }
351
368
  function wrapBodyStream(source, onFirstUse) {
352
369
  let started = false;
353
- const reader = source.getReader();
370
+ let reader = null;
354
371
  return new ReadableStream({
355
372
  async pull(controller) {
356
373
  if (!started) {
357
374
  started = true;
358
375
  onFirstUse();
359
376
  }
377
+ if (!reader) {
378
+ reader = source.getReader();
379
+ }
360
380
  try {
361
381
  const { done, value } = await reader.read();
362
382
  if (done) {
@@ -369,6 +389,9 @@ function wrapBodyStream(source, onFirstUse) {
369
389
  }
370
390
  },
371
391
  cancel(reason) {
392
+ if (!reader) {
393
+ return source.cancel(reason);
394
+ }
372
395
  return reader.cancel(reason);
373
396
  }
374
397
  });
@@ -502,6 +525,9 @@ var Response = class _Response {
502
525
  const bytes = await this.consumeBody();
503
526
  const { buffer, byteOffset, byteLength } = bytes;
504
527
  if (buffer instanceof ArrayBuffer) {
528
+ if (byteOffset === 0 && byteLength === buffer.byteLength) {
529
+ return buffer;
530
+ }
505
531
  return buffer.slice(byteOffset, byteOffset + byteLength);
506
532
  }
507
533
  const view = new Uint8Array(byteLength);
@@ -512,8 +538,22 @@ var Response = class _Response {
512
538
  const bytes = await this.consumeBody();
513
539
  return UTF8_DECODER.decode(bytes);
514
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
+ }
515
555
  clone() {
516
- if (this.bodyUsed || this.bodyStream) {
556
+ if (this.bodyUsed) {
517
557
  throw new TypeError("Cannot clone a Response whose body is already used");
518
558
  }
519
559
  if (this.nativeHandleAvailable && this.payload.bodyHandle !== null) {
@@ -551,7 +591,7 @@ var Response = class _Response {
551
591
  try {
552
592
  return await nativeBinding.readBodyAll(this.payload.bodyHandle);
553
593
  } catch (error) {
554
- if (String(error).includes("not found")) {
594
+ if (String(error).includes("Body handle") && String(error).includes("not found")) {
555
595
  return Buffer.alloc(0);
556
596
  }
557
597
  throw error;
@@ -648,6 +688,36 @@ var Session = class {
648
688
  throw new RequestError(String(error));
649
689
  }
650
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
+ }
651
721
  async close() {
652
722
  if (this.disposed) {
653
723
  return;
@@ -715,7 +785,7 @@ function resolveSessionContext(config) {
715
785
  throw new RequestError("cookieMode 'session' requires a session or sessionId");
716
786
  }
717
787
  return {
718
- sessionId: generateSessionId(),
788
+ sessionId: generateEphemeralSessionId(),
719
789
  cookieMode: "ephemeral",
720
790
  dropAfterRequest: true
721
791
  };
@@ -728,7 +798,7 @@ function resolveTransportContext(config, sessionDefaults) {
728
798
  if (config.transport.closed) {
729
799
  throw new RequestError("Transport has been closed");
730
800
  }
731
- const hasProxy = Object.hasOwn(config, "proxy");
801
+ const hasProxy = config.proxy !== void 0;
732
802
  if (config.browser !== void 0 || config.os !== void 0 || hasProxy || config.insecure !== void 0) {
733
803
  throw new RequestError("`transport` cannot be combined with browser/os/proxy/insecure options");
734
804
  }
@@ -779,8 +849,10 @@ function createAbortError(reason) {
779
849
  return reason.name === "AbortError" ? reason : new DOMException(reason.message || fallbackMessage, "AbortError");
780
850
  }
781
851
  if (reason instanceof Error) {
782
- reason.name = "AbortError";
783
- return reason;
852
+ const error2 = new Error(reason.message);
853
+ error2.name = "AbortError";
854
+ error2.cause = reason;
855
+ return error2;
784
856
  }
785
857
  if (typeof DOMException !== "undefined") {
786
858
  return new DOMException(fallbackMessage, "AbortError");
@@ -826,11 +898,55 @@ function setupAbort(signal, cancelNative) {
826
898
  return { promise, cleanup };
827
899
  }
828
900
  function coerceUrlInput(input) {
829
- const value = typeof input === "string" ? input.trim() : input.href;
830
- if (!value) {
901
+ if (input instanceof URL) {
902
+ return input.href;
903
+ }
904
+ if (input.length === 0) {
831
905
  throw new RequestError("URL is required");
832
906
  }
833
- return value;
907
+ if (input.charCodeAt(0) > 32 && input.charCodeAt(input.length - 1) > 32) {
908
+ return input;
909
+ }
910
+ const trimmed = input.trim();
911
+ if (trimmed.length === 0) {
912
+ throw new RequestError("URL is required");
913
+ }
914
+ return trimmed;
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 };
834
950
  }
835
951
  function normalizeUrlForComparison(value) {
836
952
  try {
@@ -845,30 +961,58 @@ function validateRedirectMode(mode) {
845
961
  }
846
962
  throw new RequestError(`Redirect mode '${mode}' is not supported`);
847
963
  }
848
- function serializeBody(body) {
964
+ async function serializeBody(body) {
849
965
  if (body === null || body === void 0) {
850
- return void 0;
966
+ return {};
851
967
  }
852
968
  if (typeof body === "string") {
853
- return Buffer.from(body, "utf8");
969
+ return { body: Buffer.from(body, "utf8") };
854
970
  }
855
971
  if (Buffer.isBuffer(body)) {
856
- return body;
972
+ return { body };
857
973
  }
858
974
  if (body instanceof URLSearchParams) {
859
- return Buffer.from(body.toString(), "utf8");
975
+ return {
976
+ body: Buffer.from(body.toString(), "utf8"),
977
+ contentType: "application/x-www-form-urlencoded;charset=UTF-8"
978
+ };
860
979
  }
861
980
  if (body instanceof ArrayBuffer) {
862
- return Buffer.from(body);
981
+ return { body: Buffer.from(body) };
863
982
  }
864
983
  if (ArrayBuffer.isView(body)) {
865
- return Buffer.from(body.buffer, body.byteOffset, body.byteLength);
984
+ return { body: Buffer.from(body.buffer, body.byteOffset, body.byteLength) };
985
+ }
986
+ if (typeof Blob !== "undefined" && body instanceof Blob) {
987
+ const buffer = Buffer.from(await body.arrayBuffer());
988
+ return { body: buffer, ...body.type ? { contentType: body.type } : {} };
989
+ }
990
+ if (typeof FormData !== "undefined" && body instanceof FormData) {
991
+ const encoded = new globalThis.Response(body);
992
+ const contentType = encoded.headers.get("content-type") ?? void 0;
993
+ const buffer = Buffer.from(await encoded.arrayBuffer());
994
+ return { body: buffer, ...contentType ? { contentType } : {} };
866
995
  }
867
- throw new TypeError("Unsupported body type; expected string, Buffer, ArrayBuffer, or URLSearchParams");
996
+ throw new TypeError(
997
+ "Unsupported body type; expected string, Buffer, ArrayBuffer, ArrayBufferView, URLSearchParams, Blob, or FormData"
998
+ );
868
999
  }
869
1000
  function ensureMethod(method) {
870
- const normalized = method?.trim().toUpperCase();
871
- return normalized && normalized.length > 0 ? normalized : "GET";
1001
+ if (method === void 0 || method.length === 0) {
1002
+ return "GET";
1003
+ }
1004
+ switch (method) {
1005
+ case "GET":
1006
+ case "POST":
1007
+ case "PUT":
1008
+ case "DELETE":
1009
+ case "PATCH":
1010
+ case "HEAD":
1011
+ case "OPTIONS":
1012
+ return method;
1013
+ }
1014
+ const normalized = method.trim().toUpperCase();
1015
+ return normalized.length > 0 ? normalized : "GET";
872
1016
  }
873
1017
  function ensureBodyAllowed(method, body) {
874
1018
  if (body === void 0) {
@@ -879,21 +1023,25 @@ function ensureBodyAllowed(method, body) {
879
1023
  }
880
1024
  }
881
1025
  function validateBrowserProfile(browser) {
882
- if (!browser) {
1026
+ if (browser === void 0) {
883
1027
  return;
884
1028
  }
885
- const profiles = getProfiles();
886
- if (!profiles.includes(browser)) {
887
- throw new RequestError(`Invalid browser profile: ${browser}. Available profiles: ${profiles.join(", ")}`);
1029
+ if (typeof browser !== "string" || browser.trim().length === 0) {
1030
+ throw new RequestError("Browser profile must not be empty");
1031
+ }
1032
+ if (!getProfileSet().has(browser)) {
1033
+ throw new RequestError(`Invalid browser profile: ${browser}. Available profiles: ${getProfiles().join(", ")}`);
888
1034
  }
889
1035
  }
890
1036
  function validateOperatingSystem(os) {
891
- if (!os) {
1037
+ if (os === void 0) {
892
1038
  return;
893
1039
  }
894
- const operatingSystems = getOperatingSystems();
895
- if (!operatingSystems.includes(os)) {
896
- throw new RequestError(`Invalid operating system: ${os}. Available options: ${operatingSystems.join(", ")}`);
1040
+ if (typeof os !== "string" || os.trim().length === 0) {
1041
+ throw new RequestError("Operating system must not be empty");
1042
+ }
1043
+ if (!getOperatingSystemSet().has(os)) {
1044
+ throw new RequestError(`Invalid operating system: ${os}. Available options: ${getOperatingSystems().join(", ")}`);
897
1045
  }
898
1046
  }
899
1047
  function validateTimeout(timeout) {
@@ -903,8 +1051,8 @@ function validateTimeout(timeout) {
903
1051
  if (typeof timeout !== "number" || !Number.isFinite(timeout)) {
904
1052
  throw new RequestError("Timeout must be a finite number");
905
1053
  }
906
- if (timeout <= 0) {
907
- 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");
908
1056
  }
909
1057
  }
910
1058
  function validatePositiveNumber(value, label) {
@@ -953,10 +1101,6 @@ async function dispatchRequest(options, requestUrl, signal) {
953
1101
  }
954
1102
  };
955
1103
  const abortHandler = setupAbort(signal, cancelNative);
956
- if (!abortHandler) {
957
- cancelNative();
958
- throw createAbortError(signal.reason);
959
- }
960
1104
  const pending = Promise.race([nativeBinding.request(options, requestId, true), abortHandler.promise]);
961
1105
  let payload;
962
1106
  try {
@@ -975,8 +1119,9 @@ async function dispatchRequest(options, requestUrl, signal) {
975
1119
  return new Response(payload, requestUrl);
976
1120
  }
977
1121
  async function fetch(input, init) {
978
- const url = coerceUrlInput(input);
979
- const config = init ?? {};
1122
+ const resolved = await resolveFetchArgs(input, init);
1123
+ const url = resolved.url;
1124
+ const config = resolved.init;
980
1125
  const sessionContext = resolveSessionContext(config);
981
1126
  const sessionDefaults = sessionContext.defaults;
982
1127
  validateRedirectMode(config.redirect);
@@ -984,11 +1129,18 @@ async function fetch(input, init) {
984
1129
  validateTimeout(config.timeout);
985
1130
  }
986
1131
  const method = ensureMethod(config.method);
987
- const body = serializeBody(config.body ?? null);
1132
+ const serializedBody = await serializeBody(config.body ?? null);
1133
+ const body = serializedBody.body;
988
1134
  ensureBodyAllowed(method, body);
989
- const headerTuples = mergeHeaderTuples(sessionDefaults?.defaultHeaders, config.headers);
1135
+ let headerTuples = mergeHeaderTuples(sessionDefaults?.defaultHeaders, config.headers);
1136
+ if (serializedBody.contentType && !hasHeaderName(headerTuples, "content-type")) {
1137
+ if (!headerTuples) {
1138
+ headerTuples = [];
1139
+ }
1140
+ headerTuples.push(["Content-Type", serializedBody.contentType]);
1141
+ }
990
1142
  const transport = resolveTransportContext(config, sessionDefaults);
991
- const timeout = config.timeout ?? sessionDefaults?.timeout;
1143
+ const timeout = config.timeout ?? sessionDefaults?.timeout ?? DEFAULT_REQUEST_TIMEOUT_MS;
992
1144
  const requestOptions = {
993
1145
  url,
994
1146
  method,
@@ -1010,9 +1162,7 @@ async function fetch(input, init) {
1010
1162
  requestOptions.insecure = transport.insecure;
1011
1163
  }
1012
1164
  }
1013
- if (timeout !== void 0) {
1014
- requestOptions.timeout = timeout;
1015
- }
1165
+ requestOptions.timeout = timeout;
1016
1166
  if (config.redirect !== void 0) {
1017
1167
  requestOptions.redirect = config.redirect;
1018
1168
  }
@@ -1106,6 +1256,7 @@ async function request(options) {
1106
1256
  }
1107
1257
  const { url, ...rest } = options;
1108
1258
  const init = {};
1259
+ const legacy = rest;
1109
1260
  if (rest.method !== void 0) {
1110
1261
  init.method = rest.method;
1111
1262
  }
@@ -1133,12 +1284,26 @@ async function request(options) {
1133
1284
  if (rest.transport !== void 0) {
1134
1285
  init.transport = rest.transport;
1135
1286
  }
1287
+ if (rest.insecure !== void 0) {
1288
+ init.insecure = rest.insecure;
1289
+ }
1136
1290
  if (rest.disableDefaultHeaders !== void 0) {
1137
1291
  init.disableDefaultHeaders = rest.disableDefaultHeaders;
1138
1292
  }
1139
1293
  if (rest.redirect !== void 0) {
1140
1294
  init.redirect = rest.redirect;
1141
1295
  }
1296
+ if (legacy.signal !== void 0) {
1297
+ init.signal = legacy.signal;
1298
+ }
1299
+ if (legacy.session !== void 0) {
1300
+ init.session = legacy.session;
1301
+ }
1302
+ if (legacy.cookieMode !== void 0) {
1303
+ init.cookieMode = legacy.cookieMode;
1304
+ } else if (legacy.ephemeral === true) {
1305
+ init.cookieMode = "ephemeral";
1306
+ }
1142
1307
  return fetch(url, init);
1143
1308
  }
1144
1309
  function getProfiles() {
@@ -1147,6 +1312,12 @@ function getProfiles() {
1147
1312
  }
1148
1313
  return cachedProfiles;
1149
1314
  }
1315
+ function getProfileSet() {
1316
+ if (!cachedProfileSet) {
1317
+ cachedProfileSet = new Set(getProfiles());
1318
+ }
1319
+ return cachedProfileSet;
1320
+ }
1150
1321
  function getOperatingSystems() {
1151
1322
  if (!cachedOperatingSystems) {
1152
1323
  const fromNative = nativeBinding.getOperatingSystems?.();
@@ -1154,6 +1325,12 @@ function getOperatingSystems() {
1154
1325
  }
1155
1326
  return cachedOperatingSystems;
1156
1327
  }
1328
+ function getOperatingSystemSet() {
1329
+ if (!cachedOperatingSystemSet) {
1330
+ cachedOperatingSystemSet = new Set(getOperatingSystems());
1331
+ }
1332
+ return cachedOperatingSystemSet;
1333
+ }
1157
1334
  async function get(url, init) {
1158
1335
  const config = {};
1159
1336
  if (init) {
@@ -1173,72 +1350,696 @@ async function post(url, body, init) {
1173
1350
  }
1174
1351
  return fetch(url, config);
1175
1352
  }
1176
- 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;
1177
1528
  _connection;
1529
+ _connectPromise;
1530
+ _closeOptions;
1178
1531
  _finalizerToken;
1179
- _closed = false;
1180
- constructor(connection) {
1181
- this._connection = connection;
1182
- if (websocketFinalizer) {
1183
- this._finalizerToken = connection;
1184
- websocketFinalizer.register(this, connection, connection);
1185
- }
1186
- }
1187
- /**
1188
- * Send a message (text or binary)
1189
- */
1190
- async send(data) {
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) {
1191
1631
  try {
1192
- await nativeBinding.websocketSend(this._connection, data);
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
+ }
1193
1658
  } catch (error) {
1659
+ this.handleNativeError(String(error));
1660
+ this.finalizeClosed({ code: 1006, reason: "" }, false);
1194
1661
  throw new RequestError(String(error));
1195
1662
  }
1196
1663
  }
1197
- /**
1198
- * Close the WebSocket connection
1199
- */
1200
- async close() {
1201
- 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) {
1202
1675
  return;
1203
1676
  }
1204
- this._closed = true;
1205
- if (this._finalizerToken && websocketFinalizer) {
1206
- websocketFinalizer.unregister(this._finalizerToken);
1207
- this._finalizerToken = void 0;
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;
1208
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) {
1209
1716
  try {
1210
- await nativeBinding.websocketClose(this._connection);
1211
- } catch (error) {
1212
- throw new RequestError(String(error));
1717
+ if (typeof listener === "function") {
1718
+ listener.call(this, event);
1719
+ } else {
1720
+ listener.handleEvent(event);
1721
+ }
1722
+ } catch {
1213
1723
  }
1214
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) {
1849
+ return;
1850
+ }
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;
1925
+ }
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 () => {
1954
+ try {
1955
+ const payload = await this.normalizeSendPayload(data);
1956
+ await nativeBinding.websocketSend(connection, payload);
1957
+ } catch (error) {
1958
+ this.handleNativeError(String(error));
1959
+ this.finalizeClosed({ code: 1006, reason: "" }, false);
1960
+ } finally {
1961
+ this._bufferedAmount = Math.max(0, this._bufferedAmount - queuedBytes);
1962
+ }
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();
1973
+ }
1215
1974
  };
1216
- async function websocket(options) {
1217
- if (!options.url) {
1218
- throw new RequestError("URL is required");
1975
+ function isInternalWebSocketInit(value) {
1976
+ if (!isPlainObject(value)) {
1977
+ return false;
1219
1978
  }
1220
- if (!options.onMessage) {
1221
- 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
+ };
1222
1990
  }
1223
- validateBrowserProfile(options.browser);
1224
- 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;
1225
2022
  validateOperatingSystem(os);
1226
- const browser = options.browser ?? DEFAULT_BROWSER;
1227
- try {
1228
- const connection = await nativeBinding.websocketConnect({
1229
- 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,
1230
2032
  browser,
1231
2033
  os,
1232
- headers: options.headers ?? {},
1233
- ...options.proxy !== void 0 && { proxy: options.proxy },
1234
- onMessage: options.onMessage,
1235
- ...options.onClose !== void 0 && { onClose: options.onClose },
1236
- ...options.onError !== void 0 && { onError: options.onError }
1237
- });
1238
- return new WebSocket(connection);
1239
- } catch (error) {
1240
- throw new RequestError(String(error));
1241
- }
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
+ });
1242
2043
  }
1243
2044
  var wreq_js_default = {
1244
2045
  fetch,
@@ -1255,7 +2056,8 @@ var wreq_js_default = {
1255
2056
  Headers,
1256
2057
  Response,
1257
2058
  Transport,
1258
- Session
2059
+ Session,
2060
+ RequestError
1259
2061
  };
1260
2062
  export {
1261
2063
  Headers,