@mosano-product-framework/sdk 0.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.
- package/README.md +827 -0
- package/README.react.md +348 -0
- package/dist/auth/claims-types.d.ts +89 -0
- package/dist/auth/claims.d.ts +125 -0
- package/dist/auth/cross-tab.d.ts +114 -0
- package/dist/auth/errors.d.ts +40 -0
- package/dist/auth/index.d.ts +18 -0
- package/dist/auth/index.js +5 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/oauth-state.d.ts +93 -0
- package/dist/auth/session-manager.d.ts +253 -0
- package/dist/auth/storage.d.ts +36 -0
- package/dist/auth/tenant-directory.d.ts +59 -0
- package/dist/auth/tenant-selection.d.ts +92 -0
- package/dist/chunk-7WAV52EO.js +621 -0
- package/dist/chunk-7WAV52EO.js.map +1 -0
- package/dist/chunk-AJWM5MDZ.js +410 -0
- package/dist/chunk-AJWM5MDZ.js.map +1 -0
- package/dist/chunk-EXPYHNPV.js +212 -0
- package/dist/chunk-EXPYHNPV.js.map +1 -0
- package/dist/chunk-GPWGOYCA.js +85 -0
- package/dist/chunk-GPWGOYCA.js.map +1 -0
- package/dist/chunk-GQJ3QQPH.js +339 -0
- package/dist/chunk-GQJ3QQPH.js.map +1 -0
- package/dist/chunk-K2ELAI2X.js +64 -0
- package/dist/chunk-K2ELAI2X.js.map +1 -0
- package/dist/chunk-LRM6JJ63.js +616 -0
- package/dist/chunk-LRM6JJ63.js.map +1 -0
- package/dist/chunk-XAXFIIRT.js +959 -0
- package/dist/chunk-XAXFIIRT.js.map +1 -0
- package/dist/client/core/client-factory.d.ts +61 -0
- package/dist/client/core/client.d.ts +144 -0
- package/dist/client/core/errors.d.ts +105 -0
- package/dist/client/core/index.d.ts +9 -0
- package/dist/client/core/middleware.d.ts +67 -0
- package/dist/client/core/types.d.ts +99 -0
- package/dist/client/graphql/client.d.ts +66 -0
- package/dist/client/graphql/factory.d.ts +84 -0
- package/dist/client/graphql/operation.d.ts +24 -0
- package/dist/client/graphql/types.d.ts +60 -0
- package/dist/client/graphql/ws-client.d.ts +116 -0
- package/dist/client/index.d.ts +17 -0
- package/dist/client/index.js +227 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/middlewares/admin-auth.d.ts +90 -0
- package/dist/client/middlewares/auth.d.ts +81 -0
- package/dist/client/middlewares/index.d.ts +12 -0
- package/dist/client/middlewares/logging.d.ts +102 -0
- package/dist/client/middlewares/retry.d.ts +138 -0
- package/dist/client/middlewares/tenant.d.ts +60 -0
- package/dist/client/middlewares/turnstile.d.ts +41 -0
- package/dist/client/peer-free.d.ts +25 -0
- package/dist/client/utils/url.d.ts +19 -0
- package/dist/identity/index.d.ts +85 -0
- package/dist/identity/index.js +6 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/identity/types.d.ts +690 -0
- package/dist/identity/v0.d.ts +594 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/react/context.d.ts +47 -0
- package/dist/react/hooks.d.ts +120 -0
- package/dist/react/index.d.ts +19 -0
- package/dist/react/index.js +308 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/provider.d.ts +68 -0
- package/dist/react/store.d.ts +85 -0
- package/dist/storage/index.d.ts +31 -0
- package/dist/storage/index.js +5 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/types.d.ts +107 -0
- package/dist/storage/v0.d.ts +120 -0
- package/package.json +99 -0
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
import { MPFAPIError, MPFAuthError, MPFNetworkError, MPFError, tryDecodeAccessToken } from './chunk-AJWM5MDZ.js';
|
|
2
|
+
|
|
3
|
+
// src/client/core/middleware.ts
|
|
4
|
+
async function executeRequestMiddlewares(middlewares, ctx) {
|
|
5
|
+
let currentCtx = ctx;
|
|
6
|
+
for (const middleware of middlewares) {
|
|
7
|
+
if (middleware.onRequest) {
|
|
8
|
+
currentCtx = await middleware.onRequest(currentCtx);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return currentCtx;
|
|
12
|
+
}
|
|
13
|
+
async function executeResponseMiddlewares(middlewares, ctx) {
|
|
14
|
+
let currentCtx = ctx;
|
|
15
|
+
for (let i = middlewares.length - 1; i >= 0; i--) {
|
|
16
|
+
const middleware = middlewares[i];
|
|
17
|
+
if (middleware.onResponse) {
|
|
18
|
+
currentCtx = await middleware.onResponse(currentCtx);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return currentCtx;
|
|
22
|
+
}
|
|
23
|
+
async function executeErrorMiddlewares(middlewares, error, ctx) {
|
|
24
|
+
let currentError = error;
|
|
25
|
+
for (const middleware of middlewares) {
|
|
26
|
+
if (!middleware.onError) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
await middleware.onError(currentError, ctx);
|
|
31
|
+
} catch (thrown) {
|
|
32
|
+
if (thrown instanceof Error) {
|
|
33
|
+
currentError = thrown;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
throw currentError;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/client/middlewares/retry.ts
|
|
41
|
+
var RETRY_ATTEMPT_KEY = "__retryAttempt";
|
|
42
|
+
var SHOULD_RETRY_KEY = "__shouldRetry";
|
|
43
|
+
var NO_RETRY_KEY = "__noRetry";
|
|
44
|
+
var DEFAULT_RETRY_STATUS_CODES = [408, 429, 500, 502, 503, 504];
|
|
45
|
+
function sleep(ms) {
|
|
46
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
47
|
+
}
|
|
48
|
+
function calculateDelay(attempt, baseDelay, backoffFactor, maxDelay, useJitter) {
|
|
49
|
+
const exponentialDelay = baseDelay * Math.pow(backoffFactor, attempt);
|
|
50
|
+
const cappedDelay = Math.min(exponentialDelay, maxDelay);
|
|
51
|
+
if (useJitter) {
|
|
52
|
+
const jitterFactor = 1 + Math.random() * 0.25;
|
|
53
|
+
return Math.floor(cappedDelay * jitterFactor);
|
|
54
|
+
}
|
|
55
|
+
return Math.floor(cappedDelay);
|
|
56
|
+
}
|
|
57
|
+
function createRetryMiddleware(options) {
|
|
58
|
+
const {
|
|
59
|
+
maxRetries = 3,
|
|
60
|
+
retryDelay = 1e3,
|
|
61
|
+
retryOn = DEFAULT_RETRY_STATUS_CODES,
|
|
62
|
+
backoffFactor = 2,
|
|
63
|
+
maxDelay = 3e4,
|
|
64
|
+
jitter = true,
|
|
65
|
+
onRetry,
|
|
66
|
+
shouldRetry: customShouldRetry
|
|
67
|
+
} = options ?? {};
|
|
68
|
+
function shouldRetry(error, attempt) {
|
|
69
|
+
if (attempt >= maxRetries) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
if (customShouldRetry) {
|
|
73
|
+
return customShouldRetry(error, attempt);
|
|
74
|
+
}
|
|
75
|
+
if (error instanceof MPFError && error.status !== void 0) {
|
|
76
|
+
return retryOn.includes(error.status);
|
|
77
|
+
}
|
|
78
|
+
if (error.name === "TypeError" && error.message.includes("fetch")) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
function getRetryAfterDelay(error) {
|
|
84
|
+
if (error instanceof MPFAPIError && error.details) {
|
|
85
|
+
const details = error.details;
|
|
86
|
+
const retryAfter = details["retryAfter"] || details["retry-after"] || details["Retry-After"];
|
|
87
|
+
if (typeof retryAfter === "number") {
|
|
88
|
+
return retryAfter * 1e3;
|
|
89
|
+
}
|
|
90
|
+
if (typeof retryAfter === "string") {
|
|
91
|
+
const seconds = parseInt(retryAfter, 10);
|
|
92
|
+
if (!isNaN(seconds)) {
|
|
93
|
+
return seconds * 1e3;
|
|
94
|
+
}
|
|
95
|
+
const date = new Date(retryAfter);
|
|
96
|
+
if (!isNaN(date.getTime())) {
|
|
97
|
+
const delay = date.getTime() - Date.now();
|
|
98
|
+
return delay > 0 ? delay : null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
name: "retry",
|
|
106
|
+
onRequest(ctx) {
|
|
107
|
+
if (ctx.metadata[RETRY_ATTEMPT_KEY] === void 0) {
|
|
108
|
+
ctx.metadata[RETRY_ATTEMPT_KEY] = 0;
|
|
109
|
+
}
|
|
110
|
+
return ctx;
|
|
111
|
+
},
|
|
112
|
+
async onError(error, ctx) {
|
|
113
|
+
const currentAttempt = ctx.metadata[RETRY_ATTEMPT_KEY] ?? 0;
|
|
114
|
+
if (!shouldRetry(error, currentAttempt)) {
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
let delay = getRetryAfterDelay(error);
|
|
118
|
+
if (delay === null) {
|
|
119
|
+
delay = calculateDelay(currentAttempt, retryDelay, backoffFactor, maxDelay, jitter);
|
|
120
|
+
}
|
|
121
|
+
delay = Math.min(delay, maxDelay);
|
|
122
|
+
if (onRetry) {
|
|
123
|
+
try {
|
|
124
|
+
await onRetry(currentAttempt + 1, error, delay);
|
|
125
|
+
} catch (callbackError) {
|
|
126
|
+
console.error("[MPF SDK] Error in onRetry callback:", callbackError);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
await sleep(delay);
|
|
130
|
+
ctx.metadata[RETRY_ATTEMPT_KEY] = currentAttempt + 1;
|
|
131
|
+
requestRetry(ctx);
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function shouldRetryRequest(ctx) {
|
|
137
|
+
return ctx.metadata[SHOULD_RETRY_KEY] === true && !isRetryDisabled(ctx);
|
|
138
|
+
}
|
|
139
|
+
function requestRetry(ctx) {
|
|
140
|
+
ctx.metadata[SHOULD_RETRY_KEY] = true;
|
|
141
|
+
}
|
|
142
|
+
function disableRetry(ctx) {
|
|
143
|
+
ctx.metadata[NO_RETRY_KEY] = true;
|
|
144
|
+
}
|
|
145
|
+
function isRetryDisabled(ctx) {
|
|
146
|
+
return ctx.metadata[NO_RETRY_KEY] === true;
|
|
147
|
+
}
|
|
148
|
+
function isReplayableBody(body) {
|
|
149
|
+
if (body === null || body === void 0) {
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
if (typeof ReadableStream !== "undefined" && body instanceof ReadableStream) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
function getRetryAttempt(ctx) {
|
|
158
|
+
return ctx.metadata[RETRY_ATTEMPT_KEY] ?? 0;
|
|
159
|
+
}
|
|
160
|
+
function clearRetryFlag(ctx) {
|
|
161
|
+
delete ctx.metadata[SHOULD_RETRY_KEY];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/client/core/client.ts
|
|
165
|
+
var MAX_REQUEST_ATTEMPTS = 4;
|
|
166
|
+
var MPFClient = class {
|
|
167
|
+
baseUrl;
|
|
168
|
+
defaultHeaders;
|
|
169
|
+
middlewares;
|
|
170
|
+
constructor(config) {
|
|
171
|
+
this.baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
172
|
+
this.defaultHeaders = {
|
|
173
|
+
"Content-Type": "application/json",
|
|
174
|
+
"Accept": "application/json",
|
|
175
|
+
...config.headers
|
|
176
|
+
};
|
|
177
|
+
this.middlewares = [...config.middlewares ?? []];
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Add a middleware to the client
|
|
181
|
+
* Middlewares are executed in the order they are added for requests,
|
|
182
|
+
* and in reverse order for responses.
|
|
183
|
+
*
|
|
184
|
+
* @param middleware - The middleware to add
|
|
185
|
+
* @returns The client instance for chaining
|
|
186
|
+
*/
|
|
187
|
+
use(middleware) {
|
|
188
|
+
this.middlewares.push(middleware);
|
|
189
|
+
return this;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Build a full URL with query parameters
|
|
193
|
+
*
|
|
194
|
+
* @param path - The API path (e.g., "/users" or "users")
|
|
195
|
+
* @param params - Optional query parameters
|
|
196
|
+
* @returns The full URL with query string
|
|
197
|
+
*/
|
|
198
|
+
buildUrl(path, params) {
|
|
199
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
200
|
+
const base = /^https?:\/\//.test(this.baseUrl) ? this.baseUrl : typeof window !== "undefined" ? window.location.origin : "http://localhost";
|
|
201
|
+
const url = new URL(`${this.baseUrl}${normalizedPath}`, base);
|
|
202
|
+
if (params) {
|
|
203
|
+
for (const [key, value] of Object.entries(params)) {
|
|
204
|
+
if (value !== void 0 && value !== null) {
|
|
205
|
+
url.searchParams.append(key, String(value));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return url.toString();
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Merge headers from different sources
|
|
213
|
+
*
|
|
214
|
+
* @param requestHeaders - Headers specific to this request
|
|
215
|
+
* @returns Merged headers object
|
|
216
|
+
*/
|
|
217
|
+
mergeHeaders(requestHeaders) {
|
|
218
|
+
return {
|
|
219
|
+
...this.defaultHeaders,
|
|
220
|
+
...requestHeaders
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Build the headers for a single attempt.
|
|
225
|
+
*
|
|
226
|
+
* Rebuilt per attempt so request middlewares (auth, turnstile, ...) always
|
|
227
|
+
* inject a fresh token instead of replaying a stale one.
|
|
228
|
+
*
|
|
229
|
+
* For a `FormData` body the default JSON `Content-Type` is dropped so that
|
|
230
|
+
* fetch sets `multipart/form-data` with the correct boundary.
|
|
231
|
+
*/
|
|
232
|
+
buildAttemptHeaders(init) {
|
|
233
|
+
const headers = this.mergeHeaders(init.headers);
|
|
234
|
+
if (typeof FormData !== "undefined" && init.body instanceof FormData) {
|
|
235
|
+
delete headers["Content-Type"];
|
|
236
|
+
}
|
|
237
|
+
return headers;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Parse the response body based on content type
|
|
241
|
+
*
|
|
242
|
+
* @param response - The fetch Response object
|
|
243
|
+
* @returns Parsed response data
|
|
244
|
+
*/
|
|
245
|
+
async parseResponse(response) {
|
|
246
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
247
|
+
if (response.status === 204 || response.headers.get("content-length") === "0") {
|
|
248
|
+
return void 0;
|
|
249
|
+
}
|
|
250
|
+
if (contentType.includes("application/json")) {
|
|
251
|
+
const text2 = await response.text();
|
|
252
|
+
if (!text2) {
|
|
253
|
+
return void 0;
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
return JSON.parse(text2);
|
|
257
|
+
} catch {
|
|
258
|
+
throw new MPFAPIError(
|
|
259
|
+
"Failed to parse JSON response",
|
|
260
|
+
"PARSE_ERROR",
|
|
261
|
+
response.status,
|
|
262
|
+
{ text: text2 }
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const text = await response.text();
|
|
267
|
+
return text;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Handle error responses from the API
|
|
271
|
+
*
|
|
272
|
+
* @param response - The fetch Response object
|
|
273
|
+
* @throws MPFAuthError for 401/403, MPFAPIError for other errors
|
|
274
|
+
*/
|
|
275
|
+
async handleErrorResponse(response) {
|
|
276
|
+
let body;
|
|
277
|
+
let errorResponse;
|
|
278
|
+
try {
|
|
279
|
+
const text = await response.text();
|
|
280
|
+
if (text) {
|
|
281
|
+
body = JSON.parse(text);
|
|
282
|
+
errorResponse = body;
|
|
283
|
+
}
|
|
284
|
+
} catch {
|
|
285
|
+
}
|
|
286
|
+
const message = errorResponse?.message ?? errorResponse?.error ?? response.statusText ?? "Request failed";
|
|
287
|
+
const code = errorResponse?.code ?? `HTTP_${response.status}`;
|
|
288
|
+
const details = errorResponse?.details;
|
|
289
|
+
if (response.status === 401) {
|
|
290
|
+
const authType = code.toLowerCase().includes("expired") ? "token_expired" : "unauthorized";
|
|
291
|
+
throw new MPFAuthError(message, authType, 401, details, errorResponse?.code);
|
|
292
|
+
}
|
|
293
|
+
if (response.status === 403) {
|
|
294
|
+
throw new MPFAuthError(message, "forbidden", 403, details, errorResponse?.code);
|
|
295
|
+
}
|
|
296
|
+
throw new MPFAPIError(message, code, response.status, details, body);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Execute an HTTP request with the full middleware chain, retrying when a
|
|
300
|
+
* middleware asks for it.
|
|
301
|
+
*
|
|
302
|
+
* Retry contract:
|
|
303
|
+
* - `metadata` is created once and shared by every attempt. Middlewares keep
|
|
304
|
+
* their per-request state there (retry counter, auth-renew flag); rebuilding
|
|
305
|
+
* it per attempt would reset those counters and loop forever.
|
|
306
|
+
* - `init.headers` are rebuilt per attempt so request middlewares re-inject a
|
|
307
|
+
* fresh token rather than replaying the first attempt's headers.
|
|
308
|
+
* - `MAX_REQUEST_ATTEMPTS` bounds the loop regardless of middleware config.
|
|
309
|
+
* - Bodies that cannot be re-sent (`ReadableStream`) disable retrying.
|
|
310
|
+
*
|
|
311
|
+
* @param path - The API path
|
|
312
|
+
* @param init - Fetch RequestInit options
|
|
313
|
+
* @returns The parsed response data
|
|
314
|
+
* @throws MPFNetworkError for network failures
|
|
315
|
+
* @throws MPFAuthError for 401/403 responses
|
|
316
|
+
* @throws MPFAPIError for other error responses
|
|
317
|
+
*/
|
|
318
|
+
async request(path, init = {}, params, options) {
|
|
319
|
+
const url = this.buildUrl(path, params);
|
|
320
|
+
const metadata = {};
|
|
321
|
+
for (let attempt = 0; attempt < MAX_REQUEST_ATTEMPTS; attempt++) {
|
|
322
|
+
const requestCtx = {
|
|
323
|
+
url,
|
|
324
|
+
init: {
|
|
325
|
+
...init,
|
|
326
|
+
headers: this.buildAttemptHeaders(init)
|
|
327
|
+
},
|
|
328
|
+
metadata,
|
|
329
|
+
options
|
|
330
|
+
};
|
|
331
|
+
if (!isReplayableBody(init.body)) {
|
|
332
|
+
disableRetry(requestCtx);
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
return await this.attempt(requestCtx);
|
|
336
|
+
} catch (error) {
|
|
337
|
+
const isLastAttempt = attempt === MAX_REQUEST_ATTEMPTS - 1;
|
|
338
|
+
if (isLastAttempt || !shouldRetryRequest(requestCtx)) {
|
|
339
|
+
throw error;
|
|
340
|
+
}
|
|
341
|
+
clearRetryFlag(requestCtx);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
throw new MPFNetworkError("Request retry loop exhausted");
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Execute a single request attempt through the middleware chain.
|
|
348
|
+
*
|
|
349
|
+
* On failure the error middleware chain runs before the error propagates, so
|
|
350
|
+
* the caller can inspect `ctx.metadata` for a retry request.
|
|
351
|
+
*/
|
|
352
|
+
async attempt(ctx) {
|
|
353
|
+
let requestCtx = ctx;
|
|
354
|
+
try {
|
|
355
|
+
requestCtx = await executeRequestMiddlewares(this.middlewares, requestCtx);
|
|
356
|
+
let response;
|
|
357
|
+
try {
|
|
358
|
+
response = await fetch(requestCtx.url, requestCtx.init);
|
|
359
|
+
} catch (error) {
|
|
360
|
+
throw new MPFNetworkError(
|
|
361
|
+
error instanceof Error ? error.message : "Network request failed",
|
|
362
|
+
error instanceof Error ? error : void 0
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
if (!response.ok) {
|
|
366
|
+
await this.handleErrorResponse(response);
|
|
367
|
+
}
|
|
368
|
+
const data = await this.parseResponse(response);
|
|
369
|
+
let responseCtx = {
|
|
370
|
+
response,
|
|
371
|
+
data,
|
|
372
|
+
metadata: requestCtx.metadata
|
|
373
|
+
};
|
|
374
|
+
responseCtx = await executeResponseMiddlewares(this.middlewares, responseCtx);
|
|
375
|
+
return responseCtx.data;
|
|
376
|
+
} catch (error) {
|
|
377
|
+
try {
|
|
378
|
+
if (error instanceof Error && this.middlewares.some((m) => m.onError)) {
|
|
379
|
+
await executeErrorMiddlewares(this.middlewares, error, requestCtx);
|
|
380
|
+
}
|
|
381
|
+
throw error;
|
|
382
|
+
} finally {
|
|
383
|
+
if (requestCtx.metadata !== ctx.metadata) {
|
|
384
|
+
Object.assign(ctx.metadata, requestCtx.metadata);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Make a GET request
|
|
391
|
+
*
|
|
392
|
+
* @param path - The API path
|
|
393
|
+
* @param options - Request options
|
|
394
|
+
* @returns The parsed response data
|
|
395
|
+
*/
|
|
396
|
+
async get(path, options = {}) {
|
|
397
|
+
return this.request(path, {
|
|
398
|
+
method: "GET",
|
|
399
|
+
headers: options.headers,
|
|
400
|
+
signal: options.signal
|
|
401
|
+
}, options.params, options);
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Make a POST request
|
|
405
|
+
*
|
|
406
|
+
* @param path - The API path
|
|
407
|
+
* @param body - Request body (will be JSON stringified)
|
|
408
|
+
* @param options - Request options
|
|
409
|
+
* @returns The parsed response data
|
|
410
|
+
*/
|
|
411
|
+
async post(path, body, options = {}) {
|
|
412
|
+
return this.request(path, {
|
|
413
|
+
method: "POST",
|
|
414
|
+
headers: options.headers,
|
|
415
|
+
signal: options.signal,
|
|
416
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
417
|
+
}, options.params, options);
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Make a PUT request
|
|
421
|
+
*
|
|
422
|
+
* @param path - The API path
|
|
423
|
+
* @param body - Request body (will be JSON stringified)
|
|
424
|
+
* @param options - Request options
|
|
425
|
+
* @returns The parsed response data
|
|
426
|
+
*/
|
|
427
|
+
async put(path, body, options = {}) {
|
|
428
|
+
return this.request(path, {
|
|
429
|
+
method: "PUT",
|
|
430
|
+
headers: options.headers,
|
|
431
|
+
signal: options.signal,
|
|
432
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
433
|
+
}, options.params, options);
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Make a PATCH request
|
|
437
|
+
*
|
|
438
|
+
* @param path - The API path
|
|
439
|
+
* @param body - Request body (will be JSON stringified)
|
|
440
|
+
* @param options - Request options
|
|
441
|
+
* @returns The parsed response data
|
|
442
|
+
*/
|
|
443
|
+
async patch(path, body, options = {}) {
|
|
444
|
+
return this.request(path, {
|
|
445
|
+
method: "PATCH",
|
|
446
|
+
headers: options.headers,
|
|
447
|
+
signal: options.signal,
|
|
448
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
449
|
+
}, options.params, options);
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Make a DELETE request
|
|
453
|
+
*
|
|
454
|
+
* @param path - The API path
|
|
455
|
+
* @param body - Optional request body (will be JSON stringified)
|
|
456
|
+
* @param options - Request options
|
|
457
|
+
* @returns The parsed response data
|
|
458
|
+
*/
|
|
459
|
+
async delete(path, body, options = {}) {
|
|
460
|
+
return this.request(path, {
|
|
461
|
+
method: "DELETE",
|
|
462
|
+
headers: options.headers,
|
|
463
|
+
signal: options.signal,
|
|
464
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
465
|
+
}, options.params, options);
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
// src/client/middlewares/auth.ts
|
|
470
|
+
var TOKEN_ATTACHED_KEY = "__authTokenAttached";
|
|
471
|
+
var RENEW_ATTEMPTED_KEY = "__authRenewAttempted";
|
|
472
|
+
function createAuthMiddleware(options) {
|
|
473
|
+
const {
|
|
474
|
+
getAccessToken,
|
|
475
|
+
onTokenExpired,
|
|
476
|
+
onUnauthorized,
|
|
477
|
+
tokenType = "Bearer",
|
|
478
|
+
headerName = "Authorization"
|
|
479
|
+
} = options;
|
|
480
|
+
return {
|
|
481
|
+
name: "auth",
|
|
482
|
+
async onRequest(ctx) {
|
|
483
|
+
if (ctx.options?.skipAuth) {
|
|
484
|
+
return ctx;
|
|
485
|
+
}
|
|
486
|
+
const token = await getAccessToken();
|
|
487
|
+
if (token) {
|
|
488
|
+
const headers = new Headers(ctx.init.headers);
|
|
489
|
+
headers.set(headerName, `${tokenType} ${token}`);
|
|
490
|
+
ctx.init.headers = headers;
|
|
491
|
+
ctx.metadata[TOKEN_ATTACHED_KEY] = true;
|
|
492
|
+
}
|
|
493
|
+
return ctx;
|
|
494
|
+
},
|
|
495
|
+
async onError(error, ctx) {
|
|
496
|
+
const status = error instanceof MPFError ? error.status : void 0;
|
|
497
|
+
if (status !== 401) {
|
|
498
|
+
throw error;
|
|
499
|
+
}
|
|
500
|
+
if (ctx.metadata[TOKEN_ATTACHED_KEY] !== true) {
|
|
501
|
+
throw error;
|
|
502
|
+
}
|
|
503
|
+
if (onUnauthorized && ctx.metadata[RENEW_ATTEMPTED_KEY] !== true) {
|
|
504
|
+
ctx.metadata[RENEW_ATTEMPTED_KEY] = true;
|
|
505
|
+
let renewed = false;
|
|
506
|
+
try {
|
|
507
|
+
renewed = await onUnauthorized();
|
|
508
|
+
} catch (renewError) {
|
|
509
|
+
console.error("[MPF SDK] Error in onUnauthorized callback:", renewError);
|
|
510
|
+
renewed = false;
|
|
511
|
+
}
|
|
512
|
+
if (renewed) {
|
|
513
|
+
requestRetry(ctx);
|
|
514
|
+
throw error;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
if (onTokenExpired) {
|
|
518
|
+
try {
|
|
519
|
+
await onTokenExpired();
|
|
520
|
+
} catch (callbackError) {
|
|
521
|
+
console.error("[MPF SDK] Error in onTokenExpired callback:", callbackError);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
throw error;
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// src/client/middlewares/tenant.ts
|
|
530
|
+
var TENANT_HEADER = "X-MPF-Tenant";
|
|
531
|
+
var TENANT_ROLE_HEADER = "X-MPF-Tenant-Role";
|
|
532
|
+
var UNSCOPED_WARNING = "[MPF SDK] Request sent with no tenant selected, but the access token holds tenant memberships. The server will scope this request to the default (tenantless) role, which usually means empty results or a 403 rather than tenant data. Wire a tenantProvider (createTenantSelection()) into your client and call set(tenantId), or pass `{ tenant: null }` on the request to state explicitly that it is unscoped. This warning is shown once.";
|
|
533
|
+
function createTenantMiddleware(options = {}) {
|
|
534
|
+
const {
|
|
535
|
+
tenantProvider,
|
|
536
|
+
getAccessToken,
|
|
537
|
+
warnOnUnscoped = true,
|
|
538
|
+
logger = console
|
|
539
|
+
} = options;
|
|
540
|
+
let warned = false;
|
|
541
|
+
async function maybeWarnUnscoped() {
|
|
542
|
+
if (warned || !warnOnUnscoped || !getAccessToken) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
let token;
|
|
546
|
+
try {
|
|
547
|
+
token = await getAccessToken();
|
|
548
|
+
} catch {
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
const claims = tryDecodeAccessToken(token);
|
|
552
|
+
if (!claims || claims.tnts.length === 0) {
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
warned = true;
|
|
556
|
+
logger.warn(UNSCOPED_WARNING);
|
|
557
|
+
}
|
|
558
|
+
return {
|
|
559
|
+
name: "tenant",
|
|
560
|
+
async onRequest(ctx) {
|
|
561
|
+
if (ctx.options?.skipTenant) {
|
|
562
|
+
return ctx;
|
|
563
|
+
}
|
|
564
|
+
const perRequest = ctx.options?.tenant;
|
|
565
|
+
const inherit = perRequest === void 0;
|
|
566
|
+
const selection = inherit ? tenantProvider?.get() : void 0;
|
|
567
|
+
const tenant = inherit ? selection?.tenant ?? null : perRequest;
|
|
568
|
+
const role = ctx.options?.tenantRole ?? (inherit ? selection?.role : void 0);
|
|
569
|
+
if (tenant === null || tenant === "") {
|
|
570
|
+
if (inherit) {
|
|
571
|
+
await maybeWarnUnscoped();
|
|
572
|
+
}
|
|
573
|
+
return ctx;
|
|
574
|
+
}
|
|
575
|
+
const headers = new Headers(ctx.init.headers);
|
|
576
|
+
headers.set(TENANT_HEADER, tenant);
|
|
577
|
+
if (role !== void 0 && role !== "") {
|
|
578
|
+
headers.set(TENANT_ROLE_HEADER, role);
|
|
579
|
+
}
|
|
580
|
+
ctx.init.headers = headers;
|
|
581
|
+
return ctx;
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// src/client/core/client-factory.ts
|
|
587
|
+
function attachStandardMiddlewares(client, options) {
|
|
588
|
+
const { tokenProvider, tenantProvider } = options;
|
|
589
|
+
if (tokenProvider) {
|
|
590
|
+
client.use(
|
|
591
|
+
createAuthMiddleware({
|
|
592
|
+
getAccessToken: tokenProvider.getAccessToken,
|
|
593
|
+
onTokenExpired: tokenProvider.onTokenExpired,
|
|
594
|
+
...tokenProvider.onUnauthorized ? { onUnauthorized: tokenProvider.onUnauthorized } : {}
|
|
595
|
+
})
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
if (tenantProvider) {
|
|
599
|
+
client.use(
|
|
600
|
+
createTenantMiddleware({
|
|
601
|
+
tenantProvider,
|
|
602
|
+
...tokenProvider ? { getAccessToken: tokenProvider.getAccessToken } : {}
|
|
603
|
+
})
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
function createHttpClient(options) {
|
|
608
|
+
const { tokenProvider, tenantProvider, ...clientConfig } = options;
|
|
609
|
+
const client = new MPFClient(clientConfig);
|
|
610
|
+
attachStandardMiddlewares(client, { tokenProvider, tenantProvider });
|
|
611
|
+
return client;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
export { MAX_REQUEST_ATTEMPTS, MPFClient, TENANT_HEADER, TENANT_ROLE_HEADER, attachStandardMiddlewares, clearRetryFlag, createAuthMiddleware, createHttpClient, createRetryMiddleware, createTenantMiddleware, disableRetry, executeErrorMiddlewares, executeRequestMiddlewares, executeResponseMiddlewares, getRetryAttempt, isReplayableBody, isRetryDisabled, requestRetry, shouldRetryRequest };
|
|
615
|
+
//# sourceMappingURL=chunk-LRM6JJ63.js.map
|
|
616
|
+
//# sourceMappingURL=chunk-LRM6JJ63.js.map
|