kinetex 1.1.0 → 1.2.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.
@@ -1,4 +1,4 @@
1
- /* kinetex v1.1.0 | MIT | https://github.com/kinetexjs/kinetex */
1
+ /* kinetex v1.2.0 | MIT | https://github.com/kinetexjs/kinetex */
2
2
  "use strict";
3
3
  var kinetex = (() => {
4
4
  var __create = Object.create;
@@ -283,8 +283,11 @@ var kinetex = (() => {
283
283
  function parseIPv4Host(host) {
284
284
  if (host.includes(":") || !/^[0-9a-fA-FxX.]+$/.test(host))
285
285
  return null;
286
- const parts = host.split(".");
287
- if (parts.length > 4)
286
+ const bare = host.endsWith(".") ? host.slice(0, -1) : host;
287
+ if (bare === "")
288
+ return null;
289
+ const parts = bare.split(".");
290
+ if (parts.length > 4 || parts.length < 1)
288
291
  return null;
289
292
  if (parts.length === 1) {
290
293
  const v = parseIPv4Component(parts[0]);
@@ -292,17 +295,21 @@ var kinetex = (() => {
292
295
  return null;
293
296
  return v >>> 0;
294
297
  }
295
- const nums = [];
296
- for (const p of parts) {
297
- const v = parseIPv4Component(p);
298
- const isLast = p === parts[parts.length - 1];
299
- if (v === null || v > (isLast && nums.length === 3 ? 255 : 255))
298
+ const octets = [];
299
+ for (let i = 0; i < parts.length - 1; i++) {
300
+ const v = parseIPv4Component(parts[i]);
301
+ if (v === null || v > 255)
300
302
  return null;
301
- nums.push(v);
303
+ octets.push(v);
304
+ }
305
+ const last = parseIPv4Component(parts[parts.length - 1]);
306
+ const lastCap = 2 ** (8 * (5 - parts.length));
307
+ if (last === null || last >= lastCap)
308
+ return null;
309
+ for (let shift = 8 * (4 - parts.length); shift >= 0; shift -= 8) {
310
+ octets.push(last >>> shift & 255);
302
311
  }
303
- while (nums.length < 4)
304
- nums.push(0);
305
- return (nums[0] << 24 | nums[1] << 16 | nums[2] << 8 | nums[3]) >>> 0;
312
+ return (octets[0] << 24 | octets[1] << 16 | octets[2] << 8 | octets[3]) >>> 0;
306
313
  }
307
314
  function parseIPv6Host(host) {
308
315
  if (!host.includes(":"))
@@ -4913,6 +4920,8 @@ var kinetex = (() => {
4913
4920
  __publicField(this, "sessionUsage", /* @__PURE__ */ new Map());
4914
4921
  __publicField(this, "_strict");
4915
4922
  __publicField(this, "_onDroppedHeader");
4923
+ /** Optional CA bundle for origins with self-signed / private-PKI certificates. */
4924
+ __publicField(this, "_ca");
4916
4925
  /** FIX 11: Configurable connect timeout (replaces hardcoded 30 000 ms) */
4917
4926
  __publicField(this, "_connectTimeoutMs");
4918
4927
  /** FIX 11: Configurable per-request stream timeout (replaces hardcoded 30 000 ms) */
@@ -4924,6 +4933,7 @@ var kinetex = (() => {
4924
4933
  this.maxSessions = options.maxSessions ?? 100;
4925
4934
  this._strict = options.strict ?? false;
4926
4935
  this._onDroppedHeader = options.onDroppedHeader;
4936
+ this._ca = options.ca;
4927
4937
  this._connectTimeoutMs = options.connectTimeoutMs ?? 3e4;
4928
4938
  this._requestTimeoutMs = options.requestTimeoutMs ?? 3e4;
4929
4939
  }
@@ -4973,7 +4983,10 @@ var kinetex = (() => {
4973
4983
  }
4974
4984
  }
4975
4985
  const session = await new Promise((resolve, reject) => {
4976
- const s = http2.connect(origin, { rejectUnauthorized: true });
4986
+ const s = http2.connect(origin, {
4987
+ rejectUnauthorized: true,
4988
+ ...this._ca !== void 0 ? { ca: this._ca } : {}
4989
+ });
4977
4990
  const connectTimeout = setTimeout(() => {
4978
4991
  s.destroy();
4979
4992
  reject(new KinetexError(`HTTP/2 connection to ${origin} timed out`, "ETIMEOUT", {
@@ -5086,30 +5099,28 @@ var kinetex = (() => {
5086
5099
  ":authority": currentUrl.host,
5087
5100
  ...currentReq.headers
5088
5101
  };
5089
- if (this._strict) {
5090
- for (const [hName, hValue] of Object.entries(h2ReqHeaders)) {
5091
- if (hName.startsWith(":"))
5092
- continue;
5093
- const hStr = Array.isArray(hValue) ? hValue.join(", ") : String(hValue);
5094
- let hasForbidden = false;
5095
- for (let ci = 0; ci < hStr.length; ci++) {
5096
- const code = hStr.charCodeAt(ci);
5097
- if (code >= 0 && code <= 8 || code >= 10 && code <= 31 || code === 127) {
5098
- hasForbidden = true;
5099
- break;
5100
- }
5102
+ for (const [hName, hValue] of Object.entries(h2ReqHeaders)) {
5103
+ if (hName.startsWith(":"))
5104
+ continue;
5105
+ const hStr = Array.isArray(hValue) ? hValue.join(", ") : String(hValue);
5106
+ let hasForbidden = false;
5107
+ for (let ci = 0; ci < hStr.length; ci++) {
5108
+ const code = hStr.charCodeAt(ci);
5109
+ if (code >= 0 && code <= 8 || code >= 10 && code <= 31 || code === 127) {
5110
+ hasForbidden = true;
5111
+ break;
5101
5112
  }
5102
- if (hasForbidden) {
5103
- if (this._strict) {
5104
- throw new KinetexError(`Strict mode: header "${hName}" contains forbidden control characters`, "EVALIDATION", { request: currentReq });
5105
- }
5106
- if (this._onDroppedHeader) {
5107
- this._onDroppedHeader(hName, hStr);
5108
- } else if (typeof console !== "undefined") {
5109
- console.warn(`[kinetex] Invalid header dropped (HTTP/2): "${hName}" \u2014 value contains illegal control characters. Pass strictHeaders: true to throw instead.`);
5110
- }
5111
- delete h2ReqHeaders[hName];
5113
+ }
5114
+ if (hasForbidden) {
5115
+ if (this._strict) {
5116
+ throw new KinetexError(`Strict mode: header "${hName}" contains forbidden control characters`, "EVALIDATION", { request: currentReq });
5112
5117
  }
5118
+ if (this._onDroppedHeader) {
5119
+ this._onDroppedHeader(hName, hStr);
5120
+ } else if (typeof console !== "undefined") {
5121
+ console.warn(`[kinetex] Invalid header dropped (HTTP/2): "${hName}" \u2014 value contains illegal control characters. Pass strictHeaders: true to throw instead.`);
5122
+ }
5123
+ delete h2ReqHeaders[hName];
5113
5124
  }
5114
5125
  }
5115
5126
  const endStream = !currentReq.body || currentReq.method === "GET" || currentReq.method === "HEAD";
@@ -9515,7 +9526,8 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
9515
9526
  fetchHeaders["content-type"] = "application/json";
9516
9527
  const controller = new AbortController();
9517
9528
  const timer = config.timeoutMs > 0 ? setTimeout(() => controller.abort(), config.timeoutMs) : null;
9518
- signal?.addEventListener("abort", () => controller.abort(), { once: true });
9529
+ const onExternalAbort = () => controller.abort();
9530
+ signal?.addEventListener("abort", onExternalAbort, { once: true });
9519
9531
  let response;
9520
9532
  try {
9521
9533
  response = await config.fetch(fetchUrl, {
@@ -9534,6 +9546,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
9534
9546
  } finally {
9535
9547
  if (timer)
9536
9548
  clearTimeout(timer);
9549
+ signal?.removeEventListener("abort", onExternalAbort);
9537
9550
  }
9538
9551
  return parseGraphQLResponse(response, req);
9539
9552
  }
@@ -9892,7 +9905,8 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
9892
9905
  delete headers["content-type"];
9893
9906
  const form = buildMultipartBody(req, uploads);
9894
9907
  const controller = new AbortController();
9895
- options.signal?.addEventListener("abort", () => controller.abort(), { once: true });
9908
+ const onExternalAbort = () => controller.abort();
9909
+ options.signal?.addEventListener("abort", onExternalAbort, { once: true });
9896
9910
  this.config.onRequest(req);
9897
9911
  let response;
9898
9912
  try {
@@ -9906,6 +9920,8 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
9906
9920
  const clientErr = new GraphQLClientError(err instanceof Error ? err.message : "Upload network error", "ENETWORK", void 0, req, void 0, err);
9907
9921
  this.config.onError(clientErr, req);
9908
9922
  throw clientErr;
9923
+ } finally {
9924
+ options.signal?.removeEventListener("abort", onExternalAbort);
9909
9925
  }
9910
9926
  const gqlRes = await parseGraphQLResponse(response, req);
9911
9927
  this.config.onResponse(gqlRes, req);
@@ -9936,7 +9952,8 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
9936
9952
  async batch(requests, options = {}) {
9937
9953
  const headers = await buildHeaders(this.config);
9938
9954
  const controller = new AbortController();
9939
- options.signal?.addEventListener("abort", () => controller.abort(), { once: true });
9955
+ const onExternalAbort = () => controller.abort();
9956
+ options.signal?.addEventListener("abort", onExternalAbort, { once: true });
9940
9957
  let response;
9941
9958
  try {
9942
9959
  response = await this.config.fetch(this.config.url, {
@@ -9947,6 +9964,8 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
9947
9964
  });
9948
9965
  } catch (err) {
9949
9966
  throw new GraphQLClientError(err instanceof Error ? err.message : "Batch network error", "ENETWORK", void 0, requests[0], void 0, err);
9967
+ } finally {
9968
+ options.signal?.removeEventListener("abort", onExternalAbort);
9950
9969
  }
9951
9970
  const rawResults = sanitizeParsedJSON(await response.json());
9952
9971
  if (!Array.isArray(rawResults)) {
@@ -11088,11 +11107,12 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11088
11107
  controller.error(new DOMException("Upload aborted", "AbortError"));
11089
11108
  return;
11090
11109
  }
11091
- options.signal?.addEventListener("abort", () => {
11110
+ const onUploadAbort = () => {
11092
11111
  reader.cancel("aborted").catch(() => {
11093
11112
  });
11094
11113
  controller.error(new DOMException("Upload aborted", "AbortError"));
11095
- }, { once: true });
11114
+ };
11115
+ options.signal?.addEventListener("abort", onUploadAbort, { once: true });
11096
11116
  try {
11097
11117
  while (true) {
11098
11118
  const { done, value } = await reader.read();
@@ -11107,6 +11127,8 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11107
11127
  } catch (err) {
11108
11128
  tracker.complete();
11109
11129
  controller.error(err);
11130
+ } finally {
11131
+ options.signal?.removeEventListener("abort", onUploadAbort);
11110
11132
  }
11111
11133
  }
11112
11134
  });
@@ -11197,11 +11219,11 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11197
11219
  });
11198
11220
  throw new DOMException("Stream aborted", "AbortError");
11199
11221
  }
11222
+ const abortHandler = () => {
11223
+ reader.cancel("aborted").catch(() => {
11224
+ });
11225
+ };
11200
11226
  if (options.signal) {
11201
- const abortHandler = () => {
11202
- reader.cancel("aborted").catch(() => {
11203
- });
11204
- };
11205
11227
  options.signal.addEventListener("abort", abortHandler, { once: true });
11206
11228
  }
11207
11229
  try {
@@ -11214,6 +11236,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
11214
11236
  }
11215
11237
  } finally {
11216
11238
  reader.releaseLock();
11239
+ options.signal?.removeEventListener("abort", abortHandler);
11217
11240
  }
11218
11241
  tracker.complete();
11219
11242
  const totalBytes = chunks.reduce((acc, c) => acc + c.byteLength, 0);
@@ -12027,6 +12050,10 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
12027
12050
  __publicField(this, "_openWaiters", []);
12028
12051
  __publicField(this, "_correlations", /* @__PURE__ */ new Map());
12029
12052
  __publicField(this, "_aborted", false);
12053
+ /** Optional external abort signal from config (listener detached on close). */
12054
+ __publicField(this, "_externalSignal", null);
12055
+ /** Bound abort handler for the optional external `cfg.signal` (removed on close). */
12056
+ __publicField(this, "_onExternalAbort", () => this.close(1e3, "AbortSignal aborted"));
12030
12057
  // ── Async iterator ──────────────────────────────────────────────────────
12031
12058
  __publicField(this, "_iterQueue", []);
12032
12059
  __publicField(this, "_iterWaiter", null);
@@ -12090,7 +12117,8 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
12090
12117
  this._rooms.push({ room: r, joinedAt: 0 });
12091
12118
  }
12092
12119
  }
12093
- cfg.signal?.addEventListener("abort", () => this.close(1e3, "AbortSignal aborted"), {
12120
+ this._externalSignal = cfg.signal ?? null;
12121
+ this._externalSignal?.addEventListener("abort", this._onExternalAbort, {
12094
12122
  once: true
12095
12123
  });
12096
12124
  }
@@ -12171,6 +12199,8 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
12171
12199
  */
12172
12200
  close(code = 1e3, reason = "Client closed") {
12173
12201
  this._aborted = true;
12202
+ this._externalSignal?.removeEventListener("abort", this._onExternalAbort);
12203
+ this._externalSignal = null;
12174
12204
  this._stopAll();
12175
12205
  for (const w of this._drainWaiters.splice(0))
12176
12206
  w();
@@ -13310,27 +13340,33 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
13310
13340
  }, ms);
13311
13341
  const existing = ctx.request.signal;
13312
13342
  if (existing) {
13313
- existing.addEventListener("abort", () => {
13343
+ const onExistingAbort = () => {
13314
13344
  clearTimeout(timer);
13315
13345
  controller.abort(existing.reason);
13316
- }, { once: true });
13346
+ };
13347
+ existing.addEventListener("abort", onExistingAbort, { once: true });
13348
+ ctx.store.set(TIMEOUT_SIGNAL_KEY, { existing, onExistingAbort });
13317
13349
  }
13318
13350
  ctx.request = { ...ctx.request, signal: controller.signal };
13319
13351
  ctx.store.set(TIMEOUT_TIMER_KEY, timer);
13320
13352
  };
13321
- const responseInterceptor = (ctx) => {
13353
+ const cleanupTimeout = (ctx) => {
13322
13354
  const timer = ctx.store.get(TIMEOUT_TIMER_KEY);
13323
13355
  if (timer) {
13324
13356
  clearTimeout(timer);
13325
13357
  ctx.store.delete(TIMEOUT_TIMER_KEY);
13326
13358
  }
13359
+ const merged = ctx.store.get(TIMEOUT_SIGNAL_KEY);
13360
+ if (merged) {
13361
+ merged.existing.removeEventListener("abort", merged.onExistingAbort);
13362
+ ctx.store.delete(TIMEOUT_SIGNAL_KEY);
13363
+ }
13364
+ };
13365
+ const responseInterceptor = (ctx) => {
13366
+ cleanupTimeout(ctx);
13327
13367
  };
13328
13368
  const errorInterceptor = (ctx) => {
13329
- const timer = ctx.store.get(TIMEOUT_TIMER_KEY);
13330
- if (timer) {
13331
- clearTimeout(timer);
13332
- ctx.store.delete(TIMEOUT_TIMER_KEY);
13333
- }
13369
+ cleanupTimeout(ctx);
13334
13370
  };
13335
13371
  return {
13336
13372
  id: "timeout",
@@ -13340,6 +13376,7 @@ ${text}`, "ENETWORK", void 0, req, void 0, new Error(`HTTP ${response.status}`))
13340
13376
  };
13341
13377
  }
13342
13378
  var TIMEOUT_TIMER_KEY = /* @__PURE__ */ Symbol("timeoutTimer");
13379
+ var TIMEOUT_SIGNAL_KEY = /* @__PURE__ */ Symbol("timeoutSignal");
13343
13380
  var TimeoutError2 = class extends Error {
13344
13381
  constructor(message, timeoutMs) {
13345
13382
  super(message);