@h-ai/api-client 1.0.0-beta.2 → 1.0.0-beta.20

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.js CHANGED
@@ -8,6 +8,7 @@ import { z } from 'zod';
8
8
  // src/api-client-main.ts
9
9
  var logger = core.logger.child({ module: "api-client", scope: "auth" });
10
10
  var HTTPONLY_COOKIE_SENTINEL = "__httponly_cookie__";
11
+ var cookieTokenStorages = /* @__PURE__ */ new WeakSet();
11
12
  function createMemoryTokenStorage() {
12
13
  let accessToken = null;
13
14
  let refreshToken = null;
@@ -32,7 +33,7 @@ function createMemoryTokenStorage() {
32
33
  }
33
34
  function createHttpOnlyCookieTokenStorage() {
34
35
  let memoryAccessToken = null;
35
- return {
36
+ const storage = {
36
37
  async getAccessToken() {
37
38
  return memoryAccessToken;
38
39
  },
@@ -51,6 +52,8 @@ function createHttpOnlyCookieTokenStorage() {
51
52
  memoryAccessToken = null;
52
53
  }
53
54
  };
55
+ cookieTokenStorages.add(storage);
56
+ return storage;
54
57
  }
55
58
  var RefreshTokenPayloadSchema = z.object({
56
59
  accessToken: z.string().min(1),
@@ -63,6 +66,7 @@ function createTokenManager(storage, refreshEndpointUrl, fetchFn, onRefreshFaile
63
66
  let refreshPromise = null;
64
67
  let refreshController = null;
65
68
  let generation = 0;
69
+ let disposed = false;
66
70
  let sessionInvalid = false;
67
71
  let storageMutation = Promise.resolve();
68
72
  function mutateStorage(operation) {
@@ -91,6 +95,8 @@ function createTokenManager(storage, refreshEndpointUrl, fetchFn, onRefreshFaile
91
95
  });
92
96
  }
93
97
  async function refresh() {
98
+ if (disposed)
99
+ return null;
94
100
  if (refreshPromise)
95
101
  return refreshPromise;
96
102
  const controller = new AbortController();
@@ -153,7 +159,14 @@ function createTokenManager(storage, refreshEndpointUrl, fetchFn, onRefreshFaile
153
159
  return null;
154
160
  }
155
161
  if (!useHttpOnlyCookie && !parsed.data.refreshToken) {
156
- logger.warn("Token refresh returned invalid data", { issues: [{ path: ["refreshToken"], message: "Required" }] });
162
+ logger.warn("Token refresh returned invalid data", {
163
+ issues: [
164
+ {
165
+ path: ["refreshToken"],
166
+ message: "Required"
167
+ }
168
+ ]
169
+ });
157
170
  await failRefresh(refreshGeneration);
158
171
  return null;
159
172
  }
@@ -194,6 +207,13 @@ function createTokenManager(storage, refreshEndpointUrl, fetchFn, onRefreshFaile
194
207
  }
195
208
  return {
196
209
  storage,
210
+ usesCookieCredentials: cookieTokenStorages.has(storage),
211
+ async dispose() {
212
+ disposed = true;
213
+ invalidateRefresh()?.abort();
214
+ callbacks.length = 0;
215
+ await storageMutation;
216
+ },
197
217
  get generation() {
198
218
  return generation;
199
219
  },
@@ -321,7 +341,9 @@ function createIamAuthActions(client) {
321
341
  if (event.persisted || sent)
322
342
  return;
323
343
  sent = true;
324
- void client.iam.auth.closeSession({}, { context: { keepalive: true, autoRefresh: false } }).then((result) => {
344
+ void client.iam.auth.closeSession({}, {
345
+ context: { keepalive: true, autoRefresh: false }
346
+ }).then((result) => {
325
347
  if (!result.success)
326
348
  core.logger.warn("Page exit session revocation failed", { errorCode: result.error.code });
327
349
  }).catch(() => core.logger.warn("Page exit session request did not complete"));
@@ -341,7 +363,22 @@ function createIamAuthActions(client) {
341
363
  publicConfig: () => client.iam.auth.publicConfig(),
342
364
  createCaptcha: (input) => client.iam.auth.createCaptcha(input),
343
365
  login: async (input) => {
344
- const result = input.method === "ldap" ? await client.iam.auth.loginWithLdap({ username: input.identifier, password: input.password, captcha: input.captcha, expectedUserId: input.expectedUserId }) : input.method === "email" || input.method === "sms" ? await client.iam.auth.loginWithOtp({ identifier: input.identifier, code: input.code ?? "", captcha: input.captcha, expectedUserId: input.expectedUserId }) : await client.iam.auth.login({ identifier: input.identifier, password: input.password, captcha: input.captcha, expectedUserId: input.expectedUserId });
366
+ const result = input.method === "ldap" ? await client.iam.auth.loginWithLdap({
367
+ username: input.identifier,
368
+ password: input.password,
369
+ captcha: input.captcha,
370
+ expectedUserId: input.expectedUserId
371
+ }) : input.method === "email" || input.method === "sms" ? await client.iam.auth.loginWithOtp({
372
+ identifier: input.identifier,
373
+ code: input.code ?? "",
374
+ captcha: input.captcha,
375
+ expectedUserId: input.expectedUserId
376
+ }) : await client.iam.auth.login({
377
+ identifier: input.identifier,
378
+ password: input.password,
379
+ captcha: input.captcha,
380
+ expectedUserId: input.expectedUserId
381
+ });
345
382
  return save(result);
346
383
  },
347
384
  register: async (input) => save(await client.iam.auth.register(input)),
@@ -408,12 +445,13 @@ function createApiClient(contract) {
408
445
  });
409
446
  }
410
447
  };
448
+ let closePromise = null;
411
449
  let initPromise = null;
412
450
  const lifecycle = {
413
451
  async init(config) {
414
452
  if (initPromise)
415
453
  return initPromise;
416
- if (state.rawClient) {
454
+ if (state.rawClient || closePromise) {
417
455
  return err(
418
456
  HaiApiClientError.CONFIG_ERROR,
419
457
  apiClientM("apiClient_alreadyInitialized")
@@ -438,7 +476,11 @@ function createApiClient(contract) {
438
476
  state.transport = void 0;
439
477
  return err(
440
478
  HaiApiClientError.CONFIG_ERROR,
441
- apiClientM("apiClient_configError", { params: { error: String(error) } }),
479
+ apiClientM("apiClient_configError", {
480
+ params: {
481
+ error: String(error)
482
+ }
483
+ }),
442
484
  error
443
485
  );
444
486
  }
@@ -450,13 +492,22 @@ function createApiClient(contract) {
450
492
  }
451
493
  },
452
494
  async close() {
495
+ if (closePromise)
496
+ return closePromise;
453
497
  clearIamPageExitBindings(auth);
454
498
  state.observers.clear();
499
+ const manager = state.tokenManager;
455
500
  state.transport?.destroy();
456
501
  state.config = null;
457
502
  state.rawClient = null;
458
503
  state.tokenManager = void 0;
459
504
  state.transport = void 0;
505
+ closePromise = manager?.dispose() ?? Promise.resolve();
506
+ try {
507
+ await closePromise;
508
+ } finally {
509
+ closePromise = null;
510
+ }
460
511
  },
461
512
  get config() {
462
513
  return state.config ? core.sanitize.sanitizeSensitiveFields(state.config) : null;
@@ -512,10 +563,20 @@ function createProcedureProxy(state, path) {
512
563
  });
513
564
  }
514
565
  async function callObservedProcedure(rawClient, path, args, observers) {
515
- const record = { requestId: core.context.get().requestId ?? core.id.uuid(), procedure: path.join("."), timestamp: Date.now(), duration: 0, success: false, attempts: 0 };
566
+ const record = {
567
+ requestId: core.context.get().requestId ?? core.id.uuid(),
568
+ procedure: path.join("."),
569
+ timestamp: Date.now(),
570
+ duration: 0,
571
+ success: false,
572
+ attempts: 0
573
+ };
516
574
  const provided = args[1] && typeof args[1] === "object" ? args[1] : {};
517
575
  const providedContext = provided.context && typeof provided.context === "object" ? provided.context : {};
518
- const options = { ...provided, context: { ...providedContext, [REQUEST_OBSERVATION]: record } };
576
+ const options = {
577
+ ...provided,
578
+ context: { ...providedContext, [REQUEST_OBSERVATION]: record }
579
+ };
519
580
  let result = await callProcedure(rawClient, path, [args[0], options]);
520
581
  record.duration = Math.max(0, Date.now() - record.timestamp);
521
582
  record.success = !(result && typeof result === "object" && "success" in result && result.success === false);
@@ -528,7 +589,16 @@ async function callObservedProcedure(rawClient, path, args, observers) {
528
589
  const safe = core.error.toPublic({ ...error, publicExt: error.ext }, receivedId);
529
590
  if (error.cause && typeof error.cause === "object" && !Array.isArray(error.cause)) {
530
591
  const metadata = {};
531
- for (const key of ["orpcCode", "status", "transportCode", "direction", "stage", "limitBytes", "actualBytes"]) {
592
+ for (const key of [
593
+ "orpcCode",
594
+ "status",
595
+ "transportCode",
596
+ "serverTime",
597
+ "direction",
598
+ "stage",
599
+ "limitBytes",
600
+ "actualBytes"
601
+ ]) {
532
602
  const value = Reflect.get(error.cause, key);
533
603
  if (typeof value === "number" || typeof value === "string")
534
604
  metadata[key] = core.sanitize.sanitizeSensitiveValue(value, key);
@@ -536,7 +606,13 @@ async function callObservedProcedure(rawClient, path, args, observers) {
536
606
  if (Object.keys(metadata).length)
537
607
  safe.cause = metadata;
538
608
  }
539
- core.logger.error("API request failed", { traceId: safe.traceId, requestId: record.requestId, procedure: record.procedure, status: record.status, error: result.error });
609
+ core.logger.error("API request failed", {
610
+ traceId: safe.traceId,
611
+ requestId: record.requestId,
612
+ procedure: record.procedure,
613
+ status: record.status,
614
+ error: result.error
615
+ });
540
616
  result = { ...result, error: safe };
541
617
  }
542
618
  }
@@ -627,7 +703,8 @@ function buildRawClient(contract, config, tokenManager, transport) {
627
703
  const value = await confirm();
628
704
  return typeof value === "string" || value === void 0 ? value : null;
629
705
  } : void 0;
630
- const response = await fetchWithRefresh(request, requestInit, config, options.context.autoRefresh === false ? void 0 : tokenManager, transport, record, reauthorize);
706
+ const authenticatedRequest = tokenManager?.usesCookieCredentials ? new Request(request, { credentials: "include" }) : request;
707
+ const response = await fetchWithRefresh(authenticatedRequest, requestInit, config, options.context.autoRefresh === false ? void 0 : tokenManager, transport, record, reauthorize);
631
708
  if (record) {
632
709
  record.status = response.status;
633
710
  const traceId = response.headers.get("x-trace-id") ?? response.headers.get("x-request-id");
@@ -663,6 +740,7 @@ async function fetchWithRefresh(request, init, config, tokenManager, transport,
663
740
  if (observation)
664
741
  observation.attempts++;
665
742
  const response = await fetchWithTimeout(request, init, config, transport);
743
+ request.signal.throwIfAborted();
666
744
  if (!retryRequest)
667
745
  return response;
668
746
  async function retryAuthorized(accessToken2) {
@@ -692,9 +770,10 @@ async function fetchWithRefresh(request, init, config, tokenManager, transport,
692
770
  const baseUrl = config.baseUrl.replace(TRAILING_SLASHES_REGEX, "");
693
771
  const refreshPath = config.auth?.refreshPath ?? DEFAULT_REFRESH_PATH;
694
772
  const excludedPaths = [...AUTH_ENTRY_PATHS, refreshPath];
695
- if (excludedPaths.some((path) => new URL(`${baseUrl}${path.startsWith("/") ? path : `/${path}`}`).pathname === new URL(request.url).pathname))
773
+ if (excludedPaths.some((path) => new URL(`${baseUrl}${path.startsWith("/") ? path : `/${path}`}`).pathname === new URL(request.url).pathname)) {
696
774
  return response;
697
- const failure = response.status === 401 || response.headers.get("content-type")?.includes("application/json") ? await response.clone().json().catch(() => void 0) : void 0;
775
+ }
776
+ const failure = response.headers.get("content-type")?.includes("application/json") ? await response.clone().json().catch(() => void 0) : void 0;
698
777
  const code = failure && typeof failure === "object" && "success" in failure && failure.success === false && "error" in failure && failure.error && typeof failure.error === "object" && "code" in failure.error ? failure.error.code : void 0;
699
778
  if (code === "hai:iam:027" && reauthorize)
700
779
  return retryAuthorized();
@@ -707,6 +786,7 @@ async function fetchWithRefresh(request, init, config, tokenManager, transport,
707
786
  }
708
787
  if (tokenManager.generation !== identityGeneration)
709
788
  return response;
789
+ request.signal.throwIfAborted();
710
790
  const tokens = await tokenManager.refresh();
711
791
  if (tokenManager.generation !== identityGeneration)
712
792
  return response;
@@ -742,26 +822,39 @@ async function fetchWithTimeout(request, init, config, transport) {
742
822
  const controller = new AbortController();
743
823
  const timeoutId = setTimeout(() => controller.abort(), timeout);
744
824
  const userSignal = request.signal;
745
- if (userSignal) {
746
- if (userSignal.aborted) {
747
- controller.abort();
748
- } else {
749
- userSignal.addEventListener("abort", () => controller.abort(), { once: true });
750
- }
751
- }
825
+ const abort = () => controller.abort(userSignal.reason);
826
+ if (userSignal.aborted)
827
+ abort();
828
+ else
829
+ userSignal.addEventListener("abort", abort, { once: true });
752
830
  try {
753
- return await fetchFn(new Request(request, { signal: controller.signal }), init);
831
+ const response = await fetchFn(new Request(request, { signal: controller.signal }), init);
832
+ const contentType = response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase() ?? "";
833
+ const json = contentType.endsWith("/json") || contentType.endsWith("+json");
834
+ const body = response.body === null ? null : json || !response.ok ? await response.arrayBuffer() : response.body.pipeThrough(new TransformStream(), { signal: userSignal });
835
+ return new Response(body, {
836
+ status: response.status,
837
+ statusText: response.statusText,
838
+ headers: response.headers
839
+ });
754
840
  } finally {
755
841
  clearTimeout(timeoutId);
842
+ userSignal.removeEventListener("abort", abort);
756
843
  }
757
844
  }
758
845
  function mapClientError(error) {
759
846
  const time = error instanceof TransportTimeError ? error : error instanceof Error && error.cause instanceof TransportTimeError ? error.cause : void 0;
760
847
  if (time)
761
- return err(HaiApiClientError.VALIDATION_FAILED, time.message, { transportCode: time.code });
848
+ return err(HaiApiClientError.VALIDATION_FAILED, time.message, { transportCode: time.code, serverTime: time.serverTime });
762
849
  const capacity = error instanceof TransportBodyTooLargeError ? error : error instanceof Error && error.cause instanceof TransportBodyTooLargeError ? error.cause : void 0;
763
- if (capacity)
764
- return err(HaiApiClientError.PAYLOAD_TOO_LARGE, capacity.message, { direction: capacity.direction, stage: capacity.stage, limitBytes: capacity.limitBytes, actualBytes: capacity.actualBytes });
850
+ if (capacity) {
851
+ return err(HaiApiClientError.PAYLOAD_TOO_LARGE, capacity.message, {
852
+ direction: capacity.direction,
853
+ stage: capacity.stage,
854
+ limitBytes: capacity.limitBytes,
855
+ actualBytes: capacity.actualBytes
856
+ });
857
+ }
765
858
  if (error instanceof ORPCError) {
766
859
  const body = error.data && typeof error.data === "object" && "body" in error.data ? error.data.body : void 0;
767
860
  if (body && typeof body === "object" && "success" in body && body.success === false && "error" in body) {