@h-ai/api-client 0.1.0-alpha.67 → 0.1.0-alpha.69
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 +39 -3
- package/README.ja.md +39 -3
- package/README.md +39 -3
- package/dist/index.d.ts +564 -180
- package/dist/index.js +183 -55
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { apiContract } from '@h-ai/api-contract';
|
|
2
2
|
import { core, err, ok } from '@h-ai/core';
|
|
3
|
+
import { TransportTimeError, TransportBodyTooLargeError } from '@h-ai/crypto';
|
|
3
4
|
import { createORPCClient, ORPCError } from '@orpc/client';
|
|
4
5
|
import { OpenAPILink } from '@orpc/openapi-client/fetch';
|
|
5
6
|
import { z } from 'zod';
|
|
@@ -7,29 +8,6 @@ import { z } from 'zod';
|
|
|
7
8
|
// src/api-client-main.ts
|
|
8
9
|
var logger = core.logger.child({ module: "api-client", scope: "auth" });
|
|
9
10
|
var HTTPONLY_COOKIE_SENTINEL = "__httponly_cookie__";
|
|
10
|
-
var LS_ACCESS_KEY = "hai_access_token";
|
|
11
|
-
var LS_REFRESH_KEY = "hai_refresh_token";
|
|
12
|
-
function createLocalStorageTokenStorage() {
|
|
13
|
-
logger.warn("apiClient.tokenStorage.localStorage: storing tokens in localStorage exposes them to XSS. Prefer httpOnly cookies for production.");
|
|
14
|
-
return {
|
|
15
|
-
async getAccessToken() {
|
|
16
|
-
return globalThis.localStorage?.getItem(LS_ACCESS_KEY) ?? null;
|
|
17
|
-
},
|
|
18
|
-
async setAccessToken(token) {
|
|
19
|
-
globalThis.localStorage?.setItem(LS_ACCESS_KEY, token);
|
|
20
|
-
},
|
|
21
|
-
async getRefreshToken() {
|
|
22
|
-
return globalThis.localStorage?.getItem(LS_REFRESH_KEY) ?? null;
|
|
23
|
-
},
|
|
24
|
-
async setRefreshToken(token) {
|
|
25
|
-
globalThis.localStorage?.setItem(LS_REFRESH_KEY, token);
|
|
26
|
-
},
|
|
27
|
-
async clear() {
|
|
28
|
-
globalThis.localStorage?.removeItem(LS_ACCESS_KEY);
|
|
29
|
-
globalThis.localStorage?.removeItem(LS_REFRESH_KEY);
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
11
|
function createMemoryTokenStorage() {
|
|
34
12
|
let accessToken = null;
|
|
35
13
|
let refreshToken = null;
|
|
@@ -83,25 +61,61 @@ var RefreshTokenPayloadSchema = z.object({
|
|
|
83
61
|
function createTokenManager(storage, refreshEndpointUrl, fetchFn, onRefreshFailed, refreshTimeoutMs = 1e4) {
|
|
84
62
|
const callbacks = [];
|
|
85
63
|
let refreshPromise = null;
|
|
64
|
+
let refreshController = null;
|
|
65
|
+
let generation = 0;
|
|
66
|
+
let storageMutation = Promise.resolve();
|
|
67
|
+
function mutateStorage(operation) {
|
|
68
|
+
const pending = storageMutation.then(operation);
|
|
69
|
+
storageMutation = pending.then(() => {
|
|
70
|
+
}, () => {
|
|
71
|
+
});
|
|
72
|
+
return pending;
|
|
73
|
+
}
|
|
74
|
+
function invalidateRefresh() {
|
|
75
|
+
const previousController = refreshController;
|
|
76
|
+
generation++;
|
|
77
|
+
refreshController = null;
|
|
78
|
+
refreshPromise = null;
|
|
79
|
+
return previousController;
|
|
80
|
+
}
|
|
81
|
+
async function failRefresh(refreshGeneration) {
|
|
82
|
+
await mutateStorage(async () => {
|
|
83
|
+
if (refreshGeneration !== generation)
|
|
84
|
+
return;
|
|
85
|
+
await storage.clear();
|
|
86
|
+
if (refreshGeneration === generation)
|
|
87
|
+
onRefreshFailed?.();
|
|
88
|
+
});
|
|
89
|
+
}
|
|
86
90
|
async function refresh() {
|
|
87
91
|
if (refreshPromise)
|
|
88
92
|
return refreshPromise;
|
|
89
|
-
|
|
93
|
+
const controller = new AbortController();
|
|
94
|
+
const pending = doRefresh(generation, controller);
|
|
95
|
+
refreshController = controller;
|
|
96
|
+
refreshPromise = pending;
|
|
90
97
|
try {
|
|
91
|
-
return await
|
|
98
|
+
return await pending;
|
|
92
99
|
} finally {
|
|
93
|
-
refreshPromise
|
|
100
|
+
if (refreshPromise === pending) {
|
|
101
|
+
refreshPromise = null;
|
|
102
|
+
refreshController = null;
|
|
103
|
+
}
|
|
94
104
|
}
|
|
95
105
|
}
|
|
96
|
-
async function doRefresh() {
|
|
106
|
+
async function doRefresh(refreshGeneration, controller) {
|
|
107
|
+
await storageMutation;
|
|
108
|
+
if (refreshGeneration !== generation)
|
|
109
|
+
return null;
|
|
97
110
|
const refreshToken = await storage.getRefreshToken();
|
|
111
|
+
if (refreshGeneration !== generation)
|
|
112
|
+
return null;
|
|
98
113
|
if (!refreshToken) {
|
|
99
114
|
logger.warn("Token refresh skipped, no refresh token available");
|
|
100
115
|
onRefreshFailed?.();
|
|
101
116
|
return null;
|
|
102
117
|
}
|
|
103
118
|
logger.debug("Refreshing token");
|
|
104
|
-
const controller = new AbortController();
|
|
105
119
|
const timeoutId = setTimeout(() => controller.abort(), refreshTimeoutMs);
|
|
106
120
|
try {
|
|
107
121
|
const useHttpOnlyCookie = refreshToken === HTTPONLY_COOKIE_SENTINEL;
|
|
@@ -115,27 +129,28 @@ function createTokenManager(storage, refreshEndpointUrl, fetchFn, onRefreshFaile
|
|
|
115
129
|
requestInit.headers = { "Content-Type": "application/json" };
|
|
116
130
|
}
|
|
117
131
|
const response = await fetchFn(refreshEndpointUrl, requestInit);
|
|
132
|
+
if (refreshGeneration !== generation)
|
|
133
|
+
return null;
|
|
118
134
|
if (!response.ok) {
|
|
119
135
|
if (response.status >= 400 && response.status < 500) {
|
|
120
|
-
await
|
|
121
|
-
onRefreshFailed?.();
|
|
136
|
+
await failRefresh(refreshGeneration);
|
|
122
137
|
} else {
|
|
123
138
|
logger.warn("Token refresh transient failure, keeping tokens", { status: response.status });
|
|
124
139
|
}
|
|
125
140
|
return null;
|
|
126
141
|
}
|
|
127
142
|
const body = await response.json();
|
|
143
|
+
if (refreshGeneration !== generation)
|
|
144
|
+
return null;
|
|
128
145
|
const parsed = RefreshTokenPayloadSchema.safeParse(readTokenPayload(body.data));
|
|
129
146
|
if (!parsed.success) {
|
|
130
147
|
logger.warn("Token refresh returned invalid data", { issues: parsed.error.issues });
|
|
131
|
-
await
|
|
132
|
-
onRefreshFailed?.();
|
|
148
|
+
await failRefresh(refreshGeneration);
|
|
133
149
|
return null;
|
|
134
150
|
}
|
|
135
151
|
if (!useHttpOnlyCookie && !parsed.data.refreshToken) {
|
|
136
152
|
logger.warn("Token refresh returned invalid data", { issues: [{ path: ["refreshToken"], message: "Required" }] });
|
|
137
|
-
await
|
|
138
|
-
onRefreshFailed?.();
|
|
153
|
+
await failRefresh(refreshGeneration);
|
|
139
154
|
return null;
|
|
140
155
|
}
|
|
141
156
|
const refreshTokenForStorage = parsed.data.refreshToken ?? HTTPONLY_COOKIE_SENTINEL;
|
|
@@ -145,12 +160,27 @@ function createTokenManager(storage, refreshEndpointUrl, fetchFn, onRefreshFaile
|
|
|
145
160
|
expiresIn: parsed.data.expiresIn ?? 3600,
|
|
146
161
|
tokenType: "Bearer"
|
|
147
162
|
};
|
|
148
|
-
await
|
|
149
|
-
|
|
163
|
+
const committed = await mutateStorage(async () => {
|
|
164
|
+
if (refreshGeneration !== generation)
|
|
165
|
+
return false;
|
|
166
|
+
await storage.setAccessToken(tokens.accessToken);
|
|
167
|
+
if (refreshGeneration !== generation)
|
|
168
|
+
return false;
|
|
169
|
+
await storage.setRefreshToken(refreshTokenForStorage);
|
|
170
|
+
return refreshGeneration === generation;
|
|
171
|
+
});
|
|
172
|
+
if (!committed || refreshGeneration !== generation)
|
|
173
|
+
return null;
|
|
150
174
|
logger.info("Token refreshed successfully");
|
|
151
|
-
for (const cb of callbacks)
|
|
152
|
-
|
|
175
|
+
for (const cb of callbacks) {
|
|
176
|
+
if (refreshGeneration !== generation)
|
|
177
|
+
return null;
|
|
178
|
+
cb(tokens);
|
|
179
|
+
}
|
|
180
|
+
return refreshGeneration === generation ? tokens : null;
|
|
153
181
|
} catch (error) {
|
|
182
|
+
if (refreshGeneration !== generation)
|
|
183
|
+
return null;
|
|
154
184
|
logger.error("Token refresh failed (transient)", { error });
|
|
155
185
|
return null;
|
|
156
186
|
} finally {
|
|
@@ -161,12 +191,26 @@ function createTokenManager(storage, refreshEndpointUrl, fetchFn, onRefreshFaile
|
|
|
161
191
|
storage,
|
|
162
192
|
refresh,
|
|
163
193
|
async setTokens(tokens) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
194
|
+
const previousController = invalidateRefresh();
|
|
195
|
+
const mutationGeneration = generation;
|
|
196
|
+
const pending = mutateStorage(async () => {
|
|
197
|
+
if (mutationGeneration !== generation)
|
|
198
|
+
return;
|
|
199
|
+
await storage.clear();
|
|
200
|
+
if (mutationGeneration !== generation)
|
|
201
|
+
return;
|
|
202
|
+
await storage.setAccessToken(tokens.accessToken);
|
|
203
|
+
if (tokens.refreshToken && mutationGeneration === generation)
|
|
204
|
+
await storage.setRefreshToken(tokens.refreshToken);
|
|
205
|
+
});
|
|
206
|
+
previousController?.abort();
|
|
207
|
+
await pending;
|
|
167
208
|
},
|
|
168
209
|
async clear() {
|
|
169
|
-
|
|
210
|
+
const previousController = invalidateRefresh();
|
|
211
|
+
const pending = mutateStorage(() => storage.clear());
|
|
212
|
+
previousController?.abort();
|
|
213
|
+
await pending;
|
|
170
214
|
},
|
|
171
215
|
onTokenRefreshed(callback) {
|
|
172
216
|
callbacks.push(callback);
|
|
@@ -202,6 +246,23 @@ var en_US_default = {
|
|
|
202
246
|
apiClient_unknown: "Unknown error"
|
|
203
247
|
};
|
|
204
248
|
|
|
249
|
+
// messages/ja-JP.json
|
|
250
|
+
var ja_JP_default = {
|
|
251
|
+
apiClient_networkError: "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u30EA\u30AF\u30A8\u30B9\u30C8\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
252
|
+
apiClient_timeout: "\u30EA\u30AF\u30A8\u30B9\u30C8\u304C\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8\u3057\u307E\u3057\u305F",
|
|
253
|
+
apiClient_serverError: "\u30B5\u30FC\u30D0\u30FC\u30A8\u30E9\u30FC\uFF08{status}\uFF09",
|
|
254
|
+
apiClient_unauthorized: "\u8A8D\u8A3C\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u518D\u5EA6\u30ED\u30B0\u30A4\u30F3\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
255
|
+
apiClient_forbidden: "\u30A2\u30AF\u30BB\u30B9\u304C\u62D2\u5426\u3055\u308C\u307E\u3057\u305F",
|
|
256
|
+
apiClient_notFound: "\u30EA\u30BD\u30FC\u30B9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093",
|
|
257
|
+
apiClient_validationFailed: "\u30EA\u30AF\u30A8\u30B9\u30C8\u306E\u691C\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
258
|
+
apiClient_tokenRefreshFailed: "\u30C8\u30FC\u30AF\u30F3\u306E\u66F4\u65B0\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u518D\u5EA6\u30ED\u30B0\u30A4\u30F3\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
259
|
+
apiClient_notInitialized: "API\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u304C\u521D\u671F\u5316\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u5148\u306BapiClient.init()\u3092\u547C\u3073\u51FA\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
260
|
+
apiClient_alreadyInitialized: "API\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u306F\u521D\u671F\u5316\u6E08\u307F\u3067\u3059\u3002init()\u306E\u524D\u306BapiClient.close()\u3092\u547C\u3073\u51FA\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
261
|
+
apiClient_configError: "API\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8\u306E\u8A2D\u5B9A\u30A8\u30E9\u30FC: {error}",
|
|
262
|
+
apiClient_responseValidationFailed: "\u30EC\u30B9\u30DD\u30F3\u30B9\u306E\u691C\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
263
|
+
apiClient_unknown: "\u4E0D\u660E\u306A\u30A8\u30E9\u30FC"
|
|
264
|
+
};
|
|
265
|
+
|
|
205
266
|
// messages/zh-CN.json
|
|
206
267
|
var zh_CN_default = {
|
|
207
268
|
apiClient_networkError: "\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25",
|
|
@@ -222,7 +283,8 @@ var zh_CN_default = {
|
|
|
222
283
|
// src/api-client-i18n.ts
|
|
223
284
|
var apiClientM = core.i18n.createMessageGetter({
|
|
224
285
|
"zh-CN": zh_CN_default,
|
|
225
|
-
"en-US": en_US_default
|
|
286
|
+
"en-US": en_US_default,
|
|
287
|
+
"ja-JP": ja_JP_default
|
|
226
288
|
});
|
|
227
289
|
|
|
228
290
|
// src/api-client-iam.ts
|
|
@@ -235,7 +297,7 @@ function createIamAuthActions(client) {
|
|
|
235
297
|
return {
|
|
236
298
|
publicConfig: () => client.iam.auth.publicConfig(),
|
|
237
299
|
login: async (input) => {
|
|
238
|
-
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
|
|
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 });
|
|
239
301
|
return save(result);
|
|
240
302
|
},
|
|
241
303
|
register: async (input) => save(await client.iam.auth.register(input)),
|
|
@@ -253,6 +315,7 @@ var ApiClientErrorInfo = {
|
|
|
253
315
|
NOT_FOUND: "006:404",
|
|
254
316
|
VALIDATION_FAILED: "007:400",
|
|
255
317
|
TOKEN_REFRESH_FAILED: "008:401",
|
|
318
|
+
PAYLOAD_TOO_LARGE: "020:413",
|
|
256
319
|
NOT_INITIALIZED: "010:500",
|
|
257
320
|
CONFIG_ERROR: "011:500",
|
|
258
321
|
UNKNOWN: "099:500"
|
|
@@ -272,11 +335,10 @@ var AUTH_ENTRY_PATHS = [
|
|
|
272
335
|
apiContract.pathOf(apiContract.iam.auth.register)
|
|
273
336
|
];
|
|
274
337
|
var TRAILING_SLASHES_REGEX = /\/+$/;
|
|
338
|
+
var REQUEST_OBSERVATION = /* @__PURE__ */ Symbol("hai.api-client.request-observation");
|
|
275
339
|
var tokenStorage = {
|
|
276
340
|
/** 内存存储——SSR / Node 测试或一次性会话使用。 */
|
|
277
341
|
memory: createMemoryTokenStorage,
|
|
278
|
-
/** 浏览器 `localStorage` 存储——单页应用便捷选项;注意 XSS 风险。 */
|
|
279
|
-
localStorage: createLocalStorageTokenStorage,
|
|
280
342
|
/** httpOnly Cookie 存储——浏览器场景推荐方案。 */
|
|
281
343
|
httpOnlyCookie: createHttpOnlyCookieTokenStorage
|
|
282
344
|
};
|
|
@@ -285,7 +347,8 @@ function createApiClient(contract) {
|
|
|
285
347
|
config: null,
|
|
286
348
|
rawClient: null,
|
|
287
349
|
tokenManager: void 0,
|
|
288
|
-
transport: void 0
|
|
350
|
+
transport: void 0,
|
|
351
|
+
observers: /* @__PURE__ */ new Set()
|
|
289
352
|
};
|
|
290
353
|
const auth = {
|
|
291
354
|
async setTokens(tokens) {
|
|
@@ -341,6 +404,7 @@ function createApiClient(contract) {
|
|
|
341
404
|
}
|
|
342
405
|
},
|
|
343
406
|
async close() {
|
|
407
|
+
state.observers.clear();
|
|
344
408
|
state.transport?.destroy();
|
|
345
409
|
state.config = null;
|
|
346
410
|
state.rawClient = null;
|
|
@@ -353,7 +417,13 @@ function createApiClient(contract) {
|
|
|
353
417
|
get isInitialized() {
|
|
354
418
|
return state.rawClient !== null;
|
|
355
419
|
},
|
|
356
|
-
auth
|
|
420
|
+
auth,
|
|
421
|
+
observeRequests(observer) {
|
|
422
|
+
state.observers.add(observer);
|
|
423
|
+
return () => {
|
|
424
|
+
state.observers.delete(observer);
|
|
425
|
+
};
|
|
426
|
+
}
|
|
357
427
|
};
|
|
358
428
|
return new Proxy({}, {
|
|
359
429
|
get(_target, property) {
|
|
@@ -390,10 +460,31 @@ function createProcedureProxy(state, path) {
|
|
|
390
460
|
return createProcedureProxy(state, [...path, property]);
|
|
391
461
|
},
|
|
392
462
|
apply(_t, _thisArg, args) {
|
|
393
|
-
return
|
|
463
|
+
return callObservedProcedure(state.rawClient, path, args, state.observers);
|
|
394
464
|
}
|
|
395
465
|
});
|
|
396
466
|
}
|
|
467
|
+
async function callObservedProcedure(rawClient, path, args, observers) {
|
|
468
|
+
const record = { requestId: core.context.get().requestId ?? core.id.uuid(), procedure: path.join("."), timestamp: Date.now(), duration: 0, success: false, attempts: 0 };
|
|
469
|
+
const provided = args[1] && typeof args[1] === "object" ? args[1] : {};
|
|
470
|
+
const providedContext = provided.context && typeof provided.context === "object" ? provided.context : {};
|
|
471
|
+
const options = { ...provided, context: { ...providedContext, [REQUEST_OBSERVATION]: record } };
|
|
472
|
+
const result = await callProcedure(rawClient, path, [args[0], options]);
|
|
473
|
+
record.duration = Math.max(0, Date.now() - record.timestamp);
|
|
474
|
+
record.success = !(result && typeof result === "object" && "success" in result && result.success === false);
|
|
475
|
+
if (result && typeof result === "object" && "error" in result && result.error && typeof result.error === "object" && "code" in result.error)
|
|
476
|
+
record.errorCode = String(result.error.code);
|
|
477
|
+
for (const observer of observers ?? []) {
|
|
478
|
+
try {
|
|
479
|
+
const observed = observer(Object.freeze({ ...record }));
|
|
480
|
+
if (observed instanceof Promise)
|
|
481
|
+
void observed.catch(() => {
|
|
482
|
+
});
|
|
483
|
+
} catch {
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return result;
|
|
487
|
+
}
|
|
397
488
|
async function callProcedure(rawClient, path, args) {
|
|
398
489
|
if (!rawClient) {
|
|
399
490
|
return err(HaiApiClientError.NOT_INITIALIZED, apiClientM("apiClient_notInitialized"));
|
|
@@ -425,6 +516,8 @@ function buildTokenManager(config, transport) {
|
|
|
425
516
|
const refreshFetch = async (input, init) => {
|
|
426
517
|
const configured = typeof config.headers === "function" ? await config.headers() : config.headers ?? {};
|
|
427
518
|
const headers = new Headers(configured);
|
|
519
|
+
if (!headers.has("accept-language"))
|
|
520
|
+
headers.set("accept-language", core.i18n.getGlobalLocale());
|
|
428
521
|
headers.set("x-request-client", config.clientName ?? DEFAULT_CLIENT_NAME);
|
|
429
522
|
new Headers(init?.headers).forEach((value, name) => headers.set(name, value));
|
|
430
523
|
return fetchFn(input, { ...init, headers });
|
|
@@ -442,27 +535,50 @@ function buildTransportSession(config) {
|
|
|
442
535
|
const baseUrl = config.baseUrl.replace(TRAILING_SLASHES_REGEX, "");
|
|
443
536
|
const path = config.transport.keyExchangePath ?? "/_hai/key-exchange";
|
|
444
537
|
const keyExchangeUrl = `${baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
445
|
-
return config.transport.crypto.transport.createClient({ keyExchangeUrl, fetch: baseFetch });
|
|
538
|
+
return config.transport.crypto.transport.createClient({ keyExchangeUrl, fetch: baseFetch, maxPlaintextBytes: config.transport.maxPlaintextBytes });
|
|
446
539
|
}
|
|
447
540
|
function buildRawClient(contract, config, tokenManager, transport) {
|
|
448
541
|
const link = new OpenAPILink(contract, {
|
|
449
542
|
url: config.baseUrl,
|
|
450
|
-
headers: async () => buildHeaders(config, tokenManager),
|
|
451
|
-
fetch: async (request, init) =>
|
|
543
|
+
headers: async (options) => buildHeaders(config, tokenManager, getObservation(options.context)?.requestId),
|
|
544
|
+
fetch: async (request, init, options) => {
|
|
545
|
+
const record = getObservation(options.context);
|
|
546
|
+
if (record) {
|
|
547
|
+
record.method = request.method;
|
|
548
|
+
const url = new URL(request.url);
|
|
549
|
+
url.search = "";
|
|
550
|
+
url.hash = "";
|
|
551
|
+
record.url = core.sanitize.sanitizeUrl(url.toString());
|
|
552
|
+
}
|
|
553
|
+
const response = await fetchWithRefresh(request, init, config, tokenManager, transport, record);
|
|
554
|
+
if (record)
|
|
555
|
+
record.status = response.status;
|
|
556
|
+
return response;
|
|
557
|
+
}
|
|
452
558
|
});
|
|
453
559
|
return createORPCClient(link);
|
|
454
560
|
}
|
|
455
|
-
|
|
561
|
+
function getObservation(context) {
|
|
562
|
+
const record = context[REQUEST_OBSERVATION];
|
|
563
|
+
return record && typeof record === "object" && "requestId" in record ? record : void 0;
|
|
564
|
+
}
|
|
565
|
+
async function buildHeaders(config, tokenManager, requestId) {
|
|
456
566
|
const configured = typeof config.headers === "function" ? await config.headers() : config.headers ?? {};
|
|
567
|
+
const headers = new Headers(configured);
|
|
568
|
+
if (!headers.has("accept-language"))
|
|
569
|
+
headers.set("accept-language", core.i18n.getGlobalLocale());
|
|
457
570
|
const accessToken = await tokenManager?.storage.getAccessToken();
|
|
458
571
|
return {
|
|
459
|
-
...
|
|
572
|
+
...Object.fromEntries(headers),
|
|
460
573
|
"x-request-client": config.clientName ?? DEFAULT_CLIENT_NAME,
|
|
574
|
+
"x-request-id": requestId ?? core.context.get().requestId ?? core.id.uuid(),
|
|
461
575
|
...accessToken ? { authorization: `Bearer ${accessToken}` } : {}
|
|
462
576
|
};
|
|
463
577
|
}
|
|
464
|
-
async function fetchWithRefresh(request, init, config, tokenManager, transport) {
|
|
578
|
+
async function fetchWithRefresh(request, init, config, tokenManager, transport, observation) {
|
|
465
579
|
const retryRequest = tokenManager ? request.clone() : void 0;
|
|
580
|
+
if (observation)
|
|
581
|
+
observation.attempts++;
|
|
466
582
|
const response = await fetchWithTimeout(request, init, config, transport);
|
|
467
583
|
if (!tokenManager || !retryRequest)
|
|
468
584
|
return response;
|
|
@@ -478,6 +594,8 @@ async function fetchWithRefresh(request, init, config, tokenManager, transport)
|
|
|
478
594
|
return response;
|
|
479
595
|
const headers = new Headers(retryRequest.headers);
|
|
480
596
|
headers.set("authorization", `Bearer ${tokens.accessToken}`);
|
|
597
|
+
if (observation)
|
|
598
|
+
observation.attempts++;
|
|
481
599
|
return fetchWithTimeout(new Request(retryRequest, { headers }), init, config, transport);
|
|
482
600
|
}
|
|
483
601
|
async function isUnauthorizedResult(response) {
|
|
@@ -513,8 +631,18 @@ async function fetchWithTimeout(request, init, config, transport) {
|
|
|
513
631
|
}
|
|
514
632
|
}
|
|
515
633
|
function mapClientError(error) {
|
|
634
|
+
const time = error instanceof TransportTimeError ? error : error instanceof Error && error.cause instanceof TransportTimeError ? error.cause : void 0;
|
|
635
|
+
if (time)
|
|
636
|
+
return err(HaiApiClientError.VALIDATION_FAILED, time.message, { transportCode: time.code });
|
|
637
|
+
const capacity = error instanceof TransportBodyTooLargeError ? error : error instanceof Error && error.cause instanceof TransportBodyTooLargeError ? error.cause : void 0;
|
|
638
|
+
if (capacity)
|
|
639
|
+
return err(HaiApiClientError.PAYLOAD_TOO_LARGE, capacity.message, { direction: capacity.direction, stage: capacity.stage, limitBytes: capacity.limitBytes, actualBytes: capacity.actualBytes });
|
|
516
640
|
if (error instanceof ORPCError) {
|
|
517
641
|
const detail = { orpcCode: error.code, status: error.status, data: error.data };
|
|
642
|
+
if (error.status === 413)
|
|
643
|
+
return err(HaiApiClientError.PAYLOAD_TOO_LARGE, error.message, detail);
|
|
644
|
+
if (error.status === 400 || error.status === 422)
|
|
645
|
+
return err(HaiApiClientError.VALIDATION_FAILED, error.message, detail);
|
|
518
646
|
if (error.status === 401)
|
|
519
647
|
return err(HaiApiClientError.UNAUTHORIZED, error.message, detail);
|
|
520
648
|
if (error.status === 403)
|