@h-ai/api-client 0.1.0-alpha.72 → 0.1.0-alpha.74
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/README.en.md +18 -0
- package/README.ja.md +18 -0
- package/README.md +18 -0
- package/dist/index.d.ts +467 -3
- package/dist/index.js +87 -15
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { apiContract } from '@h-ai/api-contract';
|
|
1
|
+
import { apiContract, IamSessionErrorCodeSchema } from '@h-ai/api-contract';
|
|
2
2
|
import { core, err, ok } from '@h-ai/core';
|
|
3
3
|
import { TransportTimeError, TransportBodyTooLargeError } from '@h-ai/crypto';
|
|
4
4
|
import { createORPCClient, ORPCError } from '@orpc/client';
|
|
@@ -63,6 +63,7 @@ function createTokenManager(storage, refreshEndpointUrl, fetchFn, onRefreshFaile
|
|
|
63
63
|
let refreshPromise = null;
|
|
64
64
|
let refreshController = null;
|
|
65
65
|
let generation = 0;
|
|
66
|
+
let sessionInvalid = false;
|
|
66
67
|
let storageMutation = Promise.resolve();
|
|
67
68
|
function mutateStorage(operation) {
|
|
68
69
|
const pending = storageMutation.then(operation);
|
|
@@ -83,8 +84,10 @@ function createTokenManager(storage, refreshEndpointUrl, fetchFn, onRefreshFaile
|
|
|
83
84
|
if (refreshGeneration !== generation)
|
|
84
85
|
return;
|
|
85
86
|
await storage.clear();
|
|
86
|
-
if (refreshGeneration === generation)
|
|
87
|
+
if (refreshGeneration === generation) {
|
|
88
|
+
sessionInvalid = true;
|
|
87
89
|
onRefreshFailed?.();
|
|
90
|
+
}
|
|
88
91
|
});
|
|
89
92
|
}
|
|
90
93
|
async function refresh() {
|
|
@@ -111,7 +114,8 @@ function createTokenManager(storage, refreshEndpointUrl, fetchFn, onRefreshFaile
|
|
|
111
114
|
if (refreshGeneration !== generation)
|
|
112
115
|
return null;
|
|
113
116
|
if (!refreshToken) {
|
|
114
|
-
|
|
117
|
+
sessionInvalid = true;
|
|
118
|
+
logger.debug("Token refresh skipped, no refresh token available");
|
|
115
119
|
onRefreshFailed?.();
|
|
116
120
|
return null;
|
|
117
121
|
}
|
|
@@ -171,7 +175,8 @@ function createTokenManager(storage, refreshEndpointUrl, fetchFn, onRefreshFaile
|
|
|
171
175
|
});
|
|
172
176
|
if (!committed || refreshGeneration !== generation)
|
|
173
177
|
return null;
|
|
174
|
-
|
|
178
|
+
sessionInvalid = false;
|
|
179
|
+
logger.debug("Token refreshed successfully");
|
|
175
180
|
for (const cb of callbacks) {
|
|
176
181
|
if (refreshGeneration !== generation)
|
|
177
182
|
return null;
|
|
@@ -189,9 +194,16 @@ function createTokenManager(storage, refreshEndpointUrl, fetchFn, onRefreshFaile
|
|
|
189
194
|
}
|
|
190
195
|
return {
|
|
191
196
|
storage,
|
|
197
|
+
get generation() {
|
|
198
|
+
return generation;
|
|
199
|
+
},
|
|
200
|
+
get sessionInvalid() {
|
|
201
|
+
return sessionInvalid;
|
|
202
|
+
},
|
|
192
203
|
refresh,
|
|
193
204
|
async setTokens(tokens) {
|
|
194
205
|
const previousController = invalidateRefresh();
|
|
206
|
+
sessionInvalid = false;
|
|
195
207
|
const mutationGeneration = generation;
|
|
196
208
|
const pending = mutateStorage(async () => {
|
|
197
209
|
if (mutationGeneration !== generation)
|
|
@@ -286,8 +298,12 @@ var apiClientM = core.i18n.createMessageGetter({
|
|
|
286
298
|
"en-US": en_US_default,
|
|
287
299
|
"ja-JP": ja_JP_default
|
|
288
300
|
});
|
|
289
|
-
|
|
290
|
-
|
|
301
|
+
var pageExitBindings = /* @__PURE__ */ new WeakMap();
|
|
302
|
+
function clearIamPageExitBindings(auth) {
|
|
303
|
+
for (const stop of pageExitBindings.get(auth) ?? [])
|
|
304
|
+
stop();
|
|
305
|
+
pageExitBindings.delete(auth);
|
|
306
|
+
}
|
|
291
307
|
function createIamAuthActions(client) {
|
|
292
308
|
async function save(result) {
|
|
293
309
|
if (result.success)
|
|
@@ -295,15 +311,45 @@ function createIamAuthActions(client) {
|
|
|
295
311
|
return result;
|
|
296
312
|
}
|
|
297
313
|
return {
|
|
314
|
+
bindPageExit(options = {}) {
|
|
315
|
+
if (!options.enabled || typeof window === "undefined")
|
|
316
|
+
return () => {
|
|
317
|
+
};
|
|
318
|
+
const target = window;
|
|
319
|
+
let sent = false;
|
|
320
|
+
const onPageHide = (event) => {
|
|
321
|
+
if (event.persisted || sent)
|
|
322
|
+
return;
|
|
323
|
+
sent = true;
|
|
324
|
+
void client.iam.auth.closeSession({}, { context: { keepalive: true, autoRefresh: false } }).then((result) => {
|
|
325
|
+
if (!result.success)
|
|
326
|
+
core.logger.warn("Page exit session revocation failed", { errorCode: result.error.code });
|
|
327
|
+
}).catch(() => core.logger.warn("Page exit session request did not complete"));
|
|
328
|
+
};
|
|
329
|
+
target.addEventListener("pagehide", onPageHide);
|
|
330
|
+
const bindings = pageExitBindings.get(client.auth) ?? /* @__PURE__ */ new Set();
|
|
331
|
+
const stop = () => {
|
|
332
|
+
target.removeEventListener("pagehide", onPageHide);
|
|
333
|
+
bindings.delete(stop);
|
|
334
|
+
if (!bindings.size)
|
|
335
|
+
pageExitBindings.delete(client.auth);
|
|
336
|
+
};
|
|
337
|
+
bindings.add(stop);
|
|
338
|
+
pageExitBindings.set(client.auth, bindings);
|
|
339
|
+
return stop;
|
|
340
|
+
},
|
|
298
341
|
publicConfig: () => client.iam.auth.publicConfig(),
|
|
342
|
+
createCaptcha: (input) => client.iam.auth.createCaptcha(input),
|
|
299
343
|
login: async (input) => {
|
|
300
|
-
const result = input.method === "ldap" ? await client.iam.auth.loginWithLdap({ username: input.identifier, password: input.password }) : input.method === "email" || input.method === "sms" ? await client.iam.auth.loginWithOtp({ identifier: input.identifier, code: input.code ?? "" }) : await client.iam.auth.login({ identifier: input.identifier, password: input.password });
|
|
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 });
|
|
301
345
|
return save(result);
|
|
302
346
|
},
|
|
303
347
|
register: async (input) => save(await client.iam.auth.register(input)),
|
|
304
|
-
sendOtp: (identifier) => client.iam.auth.sendOtp({ identifier }),
|
|
305
|
-
requestPasswordReset: (identifier) => client.iam.auth.requestPasswordReset({ identifier }),
|
|
306
|
-
confirmPasswordReset: (token, newPassword) => client.iam.auth.confirmPasswordReset({ token, newPassword })
|
|
348
|
+
sendOtp: (identifier, options) => client.iam.auth.sendOtp({ identifier, ...options }),
|
|
349
|
+
requestPasswordReset: (identifier, options) => client.iam.auth.requestPasswordReset({ identifier, ...options }),
|
|
350
|
+
confirmPasswordReset: (token, newPassword) => client.iam.auth.confirmPasswordReset({ token, newPassword }),
|
|
351
|
+
renewPassword: (input) => client.iam.auth.renewPassword(input),
|
|
352
|
+
reauthenticate: (password, operation) => client.iam.auth.reauthenticate({ password, operation })
|
|
307
353
|
};
|
|
308
354
|
}
|
|
309
355
|
var ApiClientErrorInfo = {
|
|
@@ -404,6 +450,7 @@ function createApiClient(contract) {
|
|
|
404
450
|
}
|
|
405
451
|
},
|
|
406
452
|
async close() {
|
|
453
|
+
clearIamPageExitBindings(auth);
|
|
407
454
|
state.observers.clear();
|
|
408
455
|
state.transport?.destroy();
|
|
409
456
|
state.config = null;
|
|
@@ -541,7 +588,7 @@ function buildTokenManager(config, transport) {
|
|
|
541
588
|
new Headers(init?.headers).forEach((value, name) => headers.set(name, value));
|
|
542
589
|
return fetchFn(input, { ...init, headers });
|
|
543
590
|
};
|
|
544
|
-
const manager = createTokenManager(storage, refreshUrl, refreshFetch, config.auth.onRefreshFailed, config.auth.refreshTimeoutMs);
|
|
591
|
+
const manager = createTokenManager(storage, refreshUrl, refreshFetch, config.auth.recoverSession ? void 0 : config.auth.onRefreshFailed, config.auth.refreshTimeoutMs);
|
|
545
592
|
if (config.auth.onTokenRefreshed) {
|
|
546
593
|
manager.onTokenRefreshed(config.auth.onTokenRefreshed);
|
|
547
594
|
}
|
|
@@ -559,7 +606,12 @@ function buildTransportSession(config) {
|
|
|
559
606
|
function buildRawClient(contract, config, tokenManager, transport) {
|
|
560
607
|
const link = new OpenAPILink(contract, {
|
|
561
608
|
url: config.baseUrl,
|
|
562
|
-
headers: async (options) =>
|
|
609
|
+
headers: async (options) => {
|
|
610
|
+
const headers = await buildHeaders(config, tokenManager, getObservation(options.context)?.requestId);
|
|
611
|
+
if (typeof options.context.reauthentication === "string")
|
|
612
|
+
headers["x-hai-reauthentication"] = options.context.reauthentication;
|
|
613
|
+
return headers;
|
|
614
|
+
},
|
|
563
615
|
fetch: async (request, init, options) => {
|
|
564
616
|
const record = getObservation(options.context);
|
|
565
617
|
if (record) {
|
|
@@ -569,7 +621,8 @@ function buildRawClient(contract, config, tokenManager, transport) {
|
|
|
569
621
|
url.hash = "";
|
|
570
622
|
record.url = core.sanitize.sanitizeUrl(url.toString());
|
|
571
623
|
}
|
|
572
|
-
const
|
|
624
|
+
const requestInit = options.context.keepalive === true ? { ...init, keepalive: true } : init;
|
|
625
|
+
const response = await fetchWithRefresh(request, requestInit, config, options.context.autoRefresh === false ? void 0 : tokenManager, transport, record);
|
|
573
626
|
if (record) {
|
|
574
627
|
record.status = response.status;
|
|
575
628
|
const traceId = response.headers.get("x-trace-id") ?? response.headers.get("x-request-id");
|
|
@@ -588,6 +641,7 @@ function getObservation(context) {
|
|
|
588
641
|
async function buildHeaders(config, tokenManager, requestId) {
|
|
589
642
|
const configured = typeof config.headers === "function" ? await config.headers() : config.headers ?? {};
|
|
590
643
|
const headers = new Headers(configured);
|
|
644
|
+
headers.set("x-hai-terminal", config.auth?.terminalType ?? "web");
|
|
591
645
|
if (!headers.has("accept-language"))
|
|
592
646
|
headers.set("accept-language", core.i18n.getGlobalLocale());
|
|
593
647
|
const accessToken = await tokenManager?.storage.getAccessToken();
|
|
@@ -600,6 +654,7 @@ async function buildHeaders(config, tokenManager, requestId) {
|
|
|
600
654
|
}
|
|
601
655
|
async function fetchWithRefresh(request, init, config, tokenManager, transport, observation) {
|
|
602
656
|
const retryRequest = tokenManager ? request.clone() : void 0;
|
|
657
|
+
const identityGeneration = tokenManager?.generation;
|
|
603
658
|
if (observation)
|
|
604
659
|
observation.attempts++;
|
|
605
660
|
const response = await fetchWithTimeout(request, init, config, transport);
|
|
@@ -612,11 +667,28 @@ async function fetchWithRefresh(request, init, config, tokenManager, transport,
|
|
|
612
667
|
return response;
|
|
613
668
|
if (response.status !== 401 && !await isUnauthorizedResult(response))
|
|
614
669
|
return response;
|
|
670
|
+
const failure = await response.clone().json().catch(() => void 0);
|
|
671
|
+
if (failure && typeof failure === "object" && "error" in failure && failure.error && typeof failure.error === "object" && "code" in failure.error && typeof failure.error.code === "string" && failure.error.code.startsWith("hai:iam:") && !IamSessionErrorCodeSchema.safeParse(failure.error.code).success) {
|
|
672
|
+
return response;
|
|
673
|
+
}
|
|
674
|
+
if (tokenManager.generation !== identityGeneration)
|
|
675
|
+
return response;
|
|
615
676
|
const tokens = await tokenManager.refresh();
|
|
616
|
-
if (
|
|
677
|
+
if (tokenManager.generation !== identityGeneration)
|
|
678
|
+
return response;
|
|
679
|
+
let accessToken = tokens?.accessToken;
|
|
680
|
+
if (!accessToken && config.auth?.recoverSession && tokenManager.sessionInvalid) {
|
|
681
|
+
if (await config.auth.recoverSession()) {
|
|
682
|
+
const restoredGeneration = tokenManager.generation;
|
|
683
|
+
accessToken = await tokenManager.storage.getAccessToken() ?? void 0;
|
|
684
|
+
if (tokenManager.generation !== restoredGeneration)
|
|
685
|
+
return response;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (!accessToken)
|
|
617
689
|
return response;
|
|
618
690
|
const headers = new Headers(retryRequest.headers);
|
|
619
|
-
headers.set("authorization", `Bearer ${
|
|
691
|
+
headers.set("authorization", `Bearer ${accessToken}`);
|
|
620
692
|
if (observation)
|
|
621
693
|
observation.attempts++;
|
|
622
694
|
return fetchWithTimeout(new Request(retryRequest, { headers }), init, config, transport);
|