@cloudpdf/engine 3.0.0-next.7 → 3.0.0-next.8

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/index.cjs CHANGED
@@ -810,8 +810,7 @@ var CloudDocumentSecurityService = class {
810
810
  * and `applyCdnAccess` short-circuits to origin; safe to call always.
811
811
  */
812
812
  async postAccess(signal, body) {
813
- const response = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.access, {
814
- docId: this.docId,
813
+ const response = await this.http.postJson(_embedpdf_engine_core_wire.wirePaths.access(this.docId), {
815
814
  layerName: this.layerName,
816
815
  ...body.password ? { password: body.password } : {},
817
816
  mode: body.mode
@@ -1935,7 +1934,7 @@ function fallbackUnknownHead(id) {
1935
1934
  access: {
1936
1935
  required: true,
1937
1936
  reasons: ["permissions-unknown"],
1938
- endpoint: _embedpdf_engine_core_wire.wirePaths.access
1937
+ endpoint: _embedpdf_engine_core_wire.wirePaths.access(id)
1939
1938
  }
1940
1939
  };
1941
1940
  }
@@ -2043,6 +2042,8 @@ var HttpClient = class HttpClient {
2043
2042
  _defineProperty(this, "base", void 0);
2044
2043
  _defineProperty(this, "tokenFn", void 0);
2045
2044
  _defineProperty(this, "fetchFn", void 0);
2045
+ _defineProperty(this, "docAffinityHeader", void 0);
2046
+ _defineProperty(this, "onRetry", void 0);
2046
2047
  _defineProperty(this, "sessionId", void 0);
2047
2048
  _defineProperty(this, "cdnBinding", null);
2048
2049
  this.base = opts.baseUrl.replace(/\/$/, "");
@@ -2050,6 +2051,8 @@ var HttpClient = class HttpClient {
2050
2051
  this.tokenFn = token === void 0 ? null : typeof token === "function" ? token : () => token;
2051
2052
  this.sessionId = opts.sessionId ?? null;
2052
2053
  this.fetchFn = opts.fetch ?? globalThis.fetch.bind(globalThis);
2054
+ this.docAffinityHeader = opts.docAffinityHeader ?? false;
2055
+ this.onRetry = opts.onRetry;
2053
2056
  }
2054
2057
  /** Normalized server base URL (no trailing slash). */
2055
2058
  get baseUrl() {
@@ -2326,7 +2329,7 @@ var HttpClient = class HttpClient {
2326
2329
  });
2327
2330
  }
2328
2331
  async request(path, init) {
2329
- const { url, extraHeader } = this.resolveOutgoing(path);
2332
+ const { url, extraHeader, routedToCdn } = this.resolveOutgoing(path);
2330
2333
  const headers = new Headers(init.headers ?? {});
2331
2334
  if (this.tokenFn) {
2332
2335
  const token = await this.tokenFn();
@@ -2335,11 +2338,32 @@ var HttpClient = class HttpClient {
2335
2338
  if (!headers.has("Accept")) headers.set("Accept", "application/json");
2336
2339
  if (this.sessionId) headers.set("X-Engine-Session-Id", this.sessionId);
2337
2340
  if (extraHeader) headers.set(extraHeader.name, extraHeader.value);
2338
- try {
2339
- return await this.fetchFn(url, {
2341
+ if (this.docAffinityHeader && !routedToCdn) {
2342
+ const doc = /^\/v1\/docs\/([^/]+)\//.exec(path);
2343
+ if (doc) headers.set("X-CloudPDF-Doc", decodeURIComponent(doc[1]));
2344
+ }
2345
+ for (let attempt = 0;; attempt++) {
2346
+ const res = await this.execute(url, {
2340
2347
  ...init,
2341
2348
  headers
2342
2349
  });
2350
+ if (res.status !== 503 || attempt >= HttpClient.MAX_RETRIES || isStreamBody(init.body ?? null)) return res;
2351
+ const code = await peekErrorCode(res);
2352
+ if (code === null || !RETRYABLE_503.has(code)) return res;
2353
+ const hinted = Number(res.headers.get("retry-after"));
2354
+ const waitMs = (Number.isFinite(hinted) ? Math.min(Math.max(hinted, 0) * 1e3, HttpClient.RETRY_AFTER_CAP_MS) : HttpClient.RETRY_AFTER_DEFAULT_MS) * (.9 + .2 * Math.random());
2355
+ this.onRetry?.({
2356
+ path,
2357
+ code,
2358
+ attempt,
2359
+ waitMs
2360
+ });
2361
+ await sleepRacingAbort(waitMs, init.signal ?? null);
2362
+ }
2363
+ }
2364
+ async execute(url, init) {
2365
+ try {
2366
+ return await this.fetchFn(url, init);
2343
2367
  } catch (err) {
2344
2368
  if (init.signal?.aborted) throw new _embedpdf_engine_core_runtime.AbortError(init.signal.reason ?? err);
2345
2369
  if (err?.name === "AbortError") throw new _embedpdf_engine_core_runtime.AbortError(err.message);
@@ -2365,7 +2389,8 @@ var HttpClient = class HttpClient {
2365
2389
  resolveOutgoing(path) {
2366
2390
  if (!this.cdnBinding || !this.cdnBinding.cdn) return {
2367
2391
  url: `${this.baseUrl}${path}`,
2368
- extraHeader: null
2392
+ extraHeader: null,
2393
+ routedToCdn: false
2369
2394
  };
2370
2395
  const applied = (0, _embedpdf_engine_core_wire.applyCdnAccess)({
2371
2396
  path,
@@ -2376,7 +2401,8 @@ var HttpClient = class HttpClient {
2376
2401
  });
2377
2402
  return {
2378
2403
  url: applied.url,
2379
- extraHeader: applied.routedToCdn && applied.authHeader ? applied.authHeader : null
2404
+ extraHeader: applied.routedToCdn && applied.authHeader ? applied.authHeader : null,
2405
+ routedToCdn: applied.routedToCdn
2380
2406
  };
2381
2407
  }
2382
2408
  async throwFromBody(res) {
@@ -2396,6 +2422,9 @@ var HttpClient = class HttpClient {
2396
2422
  throw new _embedpdf_engine_core_runtime.EngineError(code, `HTTP ${res.status}`, { details: { status: res.status } });
2397
2423
  }
2398
2424
  };
2425
+ _defineProperty(HttpClient, "MAX_RETRIES", 2);
2426
+ _defineProperty(HttpClient, "RETRY_AFTER_CAP_MS", 5e3);
2427
+ _defineProperty(HttpClient, "RETRY_AFTER_DEFAULT_MS", 1e3);
2399
2428
  function mapStatusToCode(status) {
2400
2429
  if (status === 401) return _embedpdf_engine_core_runtime.EngineErrorCode.Unauthenticated;
2401
2430
  if (status === 403) return _embedpdf_engine_core_runtime.EngineErrorCode.Forbidden;
@@ -2410,6 +2439,36 @@ function trySerialized(value) {
2410
2439
  const result = _embedpdf_engine_core_wire.EngineErrorPayloadSchema.safeParse(value);
2411
2440
  return result.success ? result.data : null;
2412
2441
  }
2442
+ const RETRYABLE_503 = /* @__PURE__ */ new Set(["EngineBusy", "EngineRestarting"]);
2443
+ function isStreamBody(body) {
2444
+ return typeof ReadableStream !== "undefined" && body instanceof ReadableStream;
2445
+ }
2446
+ /** Read the error code from a 503 body without consuming the response. */
2447
+ async function peekErrorCode(res) {
2448
+ try {
2449
+ const code = (await res.clone().json())?.error?.code;
2450
+ return typeof code === "string" ? code : null;
2451
+ } catch {
2452
+ return null;
2453
+ }
2454
+ }
2455
+ function sleepRacingAbort(ms, signal) {
2456
+ return new Promise((resolve, reject) => {
2457
+ if (signal?.aborted) {
2458
+ reject(new _embedpdf_engine_core_runtime.AbortError(signal.reason ?? "aborted"));
2459
+ return;
2460
+ }
2461
+ const timer = setTimeout(() => {
2462
+ signal?.removeEventListener("abort", onAbort);
2463
+ resolve();
2464
+ }, ms);
2465
+ const onAbort = () => {
2466
+ clearTimeout(timer);
2467
+ reject(new _embedpdf_engine_core_runtime.AbortError(signal?.reason ?? "aborted"));
2468
+ };
2469
+ signal?.addEventListener("abort", onAbort, { once: true });
2470
+ });
2471
+ }
2413
2472
  //#endregion
2414
2473
  //#region src/CloudEngine.ts
2415
2474
  /**