@mitralab.io/platform-sdk 1.0.8 → 1.1.0-beta.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/CHANGELOG.md +49 -0
- package/LICENSE +21 -0
- package/README.md +251 -108
- package/dist/index.cjs +1835 -0
- package/dist/index.d.cts +966 -0
- package/dist/index.d.ts +409 -102
- package/dist/index.js +1238 -160
- package/package.json +29 -11
- package/dist/index.d.mts +0 -659
- package/dist/index.mjs +0 -706
package/dist/index.mjs
DELETED
|
@@ -1,706 +0,0 @@
|
|
|
1
|
-
// src/client.ts
|
|
2
|
-
import { encodePathSegment, expectObject } from "@mitralab.io/sdk-core";
|
|
3
|
-
|
|
4
|
-
// src/utils/http-client.ts
|
|
5
|
-
var bearerCredentialPattern = /(Bearer\s+)\S+/gi;
|
|
6
|
-
function redactText(value, currentToken) {
|
|
7
|
-
const withoutBearerCredentials = value.replace(bearerCredentialPattern, "$1[REDACTED]");
|
|
8
|
-
return currentToken ? withoutBearerCredentials.split(currentToken).join("[REDACTED]") : withoutBearerCredentials;
|
|
9
|
-
}
|
|
10
|
-
function redactDetails(value, currentToken) {
|
|
11
|
-
if (typeof value === "string") return redactText(value, currentToken);
|
|
12
|
-
if (Array.isArray(value)) return value.map((item) => redactDetails(item, currentToken));
|
|
13
|
-
if (value && typeof value === "object") {
|
|
14
|
-
return Object.fromEntries(
|
|
15
|
-
Object.entries(value).map(([key, entry]) => [
|
|
16
|
-
redactText(key, currentToken),
|
|
17
|
-
redactDetails(entry, currentToken)
|
|
18
|
-
])
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
return value;
|
|
22
|
-
}
|
|
23
|
-
function asErrorPayload(value) {
|
|
24
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
25
|
-
return value;
|
|
26
|
-
}
|
|
27
|
-
function optionalString(value) {
|
|
28
|
-
return typeof value === "string" ? value : void 0;
|
|
29
|
-
}
|
|
30
|
-
var HttpClient = class {
|
|
31
|
-
baseUrl;
|
|
32
|
-
tokenGetter;
|
|
33
|
-
onUnauthorized;
|
|
34
|
-
onError;
|
|
35
|
-
defaultHeaders;
|
|
36
|
-
constructor(config) {
|
|
37
|
-
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
38
|
-
this.tokenGetter = config.getToken ?? (() => null);
|
|
39
|
-
this.onUnauthorized = config.onUnauthorized;
|
|
40
|
-
this.onError = config.onError;
|
|
41
|
-
this.defaultHeaders = config.defaultHeaders ?? {};
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Returns the current authentication token.
|
|
45
|
-
* @returns The JWT token if authenticated, null otherwise
|
|
46
|
-
*/
|
|
47
|
-
getToken() {
|
|
48
|
-
return this.tokenGetter();
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Makes an HTTP request with automatic JSON handling and authentication.
|
|
52
|
-
*
|
|
53
|
-
* @param path - API endpoint path (e.g., '/users')
|
|
54
|
-
* @param options - Request options including method, body, headers, and params
|
|
55
|
-
* @returns Promise resolving to the parsed JSON response
|
|
56
|
-
* @throws {MitraApiError} When the API returns an error response
|
|
57
|
-
*
|
|
58
|
-
* @example
|
|
59
|
-
* ```typescript
|
|
60
|
-
* const result = await client.request<User>('/users/123', {
|
|
61
|
-
* method: 'PUT',
|
|
62
|
-
* body: { name: 'Updated Name' },
|
|
63
|
-
* });
|
|
64
|
-
* ```
|
|
65
|
-
*/
|
|
66
|
-
async request(path, options = {}) {
|
|
67
|
-
const { method = "GET", body, headers = {}, params, isRetry } = options;
|
|
68
|
-
let url = `${this.baseUrl}${path}`;
|
|
69
|
-
if (params) {
|
|
70
|
-
const searchParams = new URLSearchParams();
|
|
71
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
72
|
-
if (value !== void 0) {
|
|
73
|
-
searchParams.append(key, String(value));
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
const queryString = searchParams.toString();
|
|
77
|
-
if (queryString) {
|
|
78
|
-
url += `?${queryString}`;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
const requestHeaders = {
|
|
82
|
-
"Content-Type": "application/json",
|
|
83
|
-
...this.defaultHeaders,
|
|
84
|
-
...headers
|
|
85
|
-
};
|
|
86
|
-
const token = this.tokenGetter();
|
|
87
|
-
if (token) {
|
|
88
|
-
requestHeaders["Authorization"] = `Bearer ${token}`;
|
|
89
|
-
}
|
|
90
|
-
const response = await fetch(url, {
|
|
91
|
-
method,
|
|
92
|
-
headers: requestHeaders,
|
|
93
|
-
body: body ? JSON.stringify(body) : void 0,
|
|
94
|
-
redirect: "manual"
|
|
95
|
-
});
|
|
96
|
-
if (response.redirected || response.type === "opaqueredirect") {
|
|
97
|
-
const error = new MitraApiError(
|
|
98
|
-
"Redirected responses are not allowed",
|
|
99
|
-
response.status,
|
|
100
|
-
"REDIRECT_NOT_ALLOWED"
|
|
101
|
-
);
|
|
102
|
-
this.onError?.(error);
|
|
103
|
-
throw error;
|
|
104
|
-
}
|
|
105
|
-
if (!response.ok) {
|
|
106
|
-
if (response.status === 401 && !isRetry && this.onUnauthorized) {
|
|
107
|
-
const refreshed = await this.onUnauthorized();
|
|
108
|
-
if (refreshed) {
|
|
109
|
-
return this.request(path, { ...options, isRetry: true });
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
const errorBody = await response.json().catch(() => ({}));
|
|
113
|
-
const errorPayload = asErrorPayload(errorBody);
|
|
114
|
-
const rawMessage = optionalString(errorPayload.message);
|
|
115
|
-
const rawCode = optionalString(errorPayload.error_code);
|
|
116
|
-
const error = new MitraApiError(
|
|
117
|
-
redactText(rawMessage || `Request failed with status ${response.status}`, token),
|
|
118
|
-
response.status,
|
|
119
|
-
rawCode === void 0 ? void 0 : redactText(rawCode, token),
|
|
120
|
-
redactDetails(errorBody, token)
|
|
121
|
-
);
|
|
122
|
-
this.onError?.(error);
|
|
123
|
-
throw error;
|
|
124
|
-
}
|
|
125
|
-
if (response.status === 204) {
|
|
126
|
-
return void 0;
|
|
127
|
-
}
|
|
128
|
-
return response.json();
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Makes a GET request.
|
|
132
|
-
*
|
|
133
|
-
* @param path - API endpoint path
|
|
134
|
-
* @param params - Optional query parameters
|
|
135
|
-
* @returns Promise resolving to the parsed JSON response
|
|
136
|
-
*
|
|
137
|
-
* @example
|
|
138
|
-
* ```typescript
|
|
139
|
-
* const users = await client.get<User[]>('/users', { limit: 10 });
|
|
140
|
-
* ```
|
|
141
|
-
*/
|
|
142
|
-
get(path, params) {
|
|
143
|
-
return this.request(path, { method: "GET", params });
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Makes a POST request.
|
|
147
|
-
*
|
|
148
|
-
* @param path - API endpoint path
|
|
149
|
-
* @param body - Request body (will be JSON stringified)
|
|
150
|
-
* @returns Promise resolving to the parsed JSON response
|
|
151
|
-
*
|
|
152
|
-
* @example
|
|
153
|
-
* ```typescript
|
|
154
|
-
* const user = await client.post<User>('/users', { name: 'John', email: 'john@example.com' });
|
|
155
|
-
* ```
|
|
156
|
-
*/
|
|
157
|
-
post(path, body) {
|
|
158
|
-
return this.request(path, { method: "POST", body });
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Makes a PUT request.
|
|
162
|
-
*
|
|
163
|
-
* @param path - API endpoint path
|
|
164
|
-
* @param body - Request body (will be JSON stringified)
|
|
165
|
-
* @returns Promise resolving to the parsed JSON response
|
|
166
|
-
*
|
|
167
|
-
* @example
|
|
168
|
-
* ```typescript
|
|
169
|
-
* const user = await client.put<User>('/users/123', { name: 'Updated Name' });
|
|
170
|
-
* ```
|
|
171
|
-
*/
|
|
172
|
-
put(path, body) {
|
|
173
|
-
return this.request(path, { method: "PUT", body });
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Makes a DELETE request.
|
|
177
|
-
*
|
|
178
|
-
* @param path - API endpoint path
|
|
179
|
-
* @param params - Optional query parameters
|
|
180
|
-
* @returns Promise resolving to the parsed JSON response (or undefined for 204 responses)
|
|
181
|
-
*
|
|
182
|
-
* @example
|
|
183
|
-
* ```typescript
|
|
184
|
-
* await client.delete('/users/123');
|
|
185
|
-
* ```
|
|
186
|
-
*/
|
|
187
|
-
delete(path, params) {
|
|
188
|
-
return this.request(path, { method: "DELETE", params });
|
|
189
|
-
}
|
|
190
|
-
};
|
|
191
|
-
var MitraApiError = class extends Error {
|
|
192
|
-
constructor(message, status, code, details) {
|
|
193
|
-
super(message);
|
|
194
|
-
this.status = status;
|
|
195
|
-
this.code = code;
|
|
196
|
-
this.details = details;
|
|
197
|
-
this.name = "MitraApiError";
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
// src/core-errors.ts
|
|
202
|
-
var coreErrors = {
|
|
203
|
-
configuration: (message) => new MitraApiError(message, 0, "INVALID_CONFIGURATION"),
|
|
204
|
-
invalidResponse: (message) => new MitraApiError(message, 200, "INVALID_RESPONSE")
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
// src/modules/auth.ts
|
|
208
|
-
import { createAuthModule } from "@mitralab.io/sdk-core";
|
|
209
|
-
var AuthModule = class {
|
|
210
|
-
appId;
|
|
211
|
-
_currentUser = null;
|
|
212
|
-
#accessToken = null;
|
|
213
|
-
#refreshToken = null;
|
|
214
|
-
refreshPromise = null;
|
|
215
|
-
listeners = /* @__PURE__ */ new Set();
|
|
216
|
-
storageKey;
|
|
217
|
-
publicClient;
|
|
218
|
-
authedClient;
|
|
219
|
-
currentUserApi;
|
|
220
|
-
constructor(appId, iamBaseUrl) {
|
|
221
|
-
this.appId = appId;
|
|
222
|
-
this.storageKey = `mitra_auth_${appId}`;
|
|
223
|
-
this.publicClient = new HttpClient({ baseUrl: iamBaseUrl, getToken: () => null });
|
|
224
|
-
this.authedClient = new HttpClient({ baseUrl: iamBaseUrl, getToken: () => this.#accessToken });
|
|
225
|
-
this.currentUserApi = createAuthModule(this.authedClient, coreErrors);
|
|
226
|
-
this.loadFromStorage();
|
|
227
|
-
}
|
|
228
|
-
/** The currently authenticated user, or null. */
|
|
229
|
-
get currentUser() {
|
|
230
|
-
return this._currentUser;
|
|
231
|
-
}
|
|
232
|
-
/** The current JWT access token, or null. */
|
|
233
|
-
get accessToken() {
|
|
234
|
-
return this.#accessToken;
|
|
235
|
-
}
|
|
236
|
-
/** Whether a user is currently authenticated (local check, not server-validated). */
|
|
237
|
-
get isAuthenticated() {
|
|
238
|
-
return this._currentUser !== null && this.#accessToken !== null;
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Signs in a user with email and password.
|
|
242
|
-
*
|
|
243
|
-
* On success, stores access token, refresh token, and user data.
|
|
244
|
-
* Subsequent API requests use the token automatically.
|
|
245
|
-
*
|
|
246
|
-
* @param credentials - Email and password.
|
|
247
|
-
* @returns The authenticated user.
|
|
248
|
-
* @throws {MitraApiError} On invalid credentials (401).
|
|
249
|
-
*
|
|
250
|
-
* @example
|
|
251
|
-
* ```typescript
|
|
252
|
-
* const user = await mitra.auth.signIn({
|
|
253
|
-
* email: 'user@example.com',
|
|
254
|
-
* password: 'password123',
|
|
255
|
-
* });
|
|
256
|
-
* ```
|
|
257
|
-
*/
|
|
258
|
-
async signIn(credentials) {
|
|
259
|
-
const tokenResponse = await this.publicClient.post(
|
|
260
|
-
"/api/v1/auth/login",
|
|
261
|
-
{ ...credentials, appId: this.appId }
|
|
262
|
-
);
|
|
263
|
-
this.#accessToken = tokenResponse.accessToken;
|
|
264
|
-
this.#refreshToken = tokenResponse.refreshToken;
|
|
265
|
-
const user = await this.getCurrentUser();
|
|
266
|
-
this.setAuthState(user, tokenResponse.accessToken, tokenResponse.refreshToken);
|
|
267
|
-
return user;
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Registers a new user and signs them in automatically.
|
|
271
|
-
*
|
|
272
|
-
* @param data - Email, password, and optional name.
|
|
273
|
-
* @returns The newly created and authenticated user.
|
|
274
|
-
* @throws {MitraApiError} On duplicate email (409) or validation error (400).
|
|
275
|
-
*
|
|
276
|
-
* @example
|
|
277
|
-
* ```typescript
|
|
278
|
-
* const user = await mitra.auth.signUp({
|
|
279
|
-
* email: 'new@example.com',
|
|
280
|
-
* password: 'securepassword',
|
|
281
|
-
* name: 'Jane Doe',
|
|
282
|
-
* });
|
|
283
|
-
* ```
|
|
284
|
-
*/
|
|
285
|
-
async signUp(data) {
|
|
286
|
-
await this.publicClient.post("/api/v1/auth/register", {
|
|
287
|
-
...data,
|
|
288
|
-
appId: this.appId
|
|
289
|
-
});
|
|
290
|
-
return this.signIn({ email: data.email, password: data.password });
|
|
291
|
-
}
|
|
292
|
-
/**
|
|
293
|
-
* Signs out the current user, clearing all auth state and localStorage.
|
|
294
|
-
*
|
|
295
|
-
* @param redirectUrl - Optional URL to navigate to after sign-out.
|
|
296
|
-
*
|
|
297
|
-
* @example
|
|
298
|
-
* ```typescript
|
|
299
|
-
* mitra.auth.signOut();
|
|
300
|
-
* mitra.auth.signOut('/login');
|
|
301
|
-
* ```
|
|
302
|
-
*/
|
|
303
|
-
signOut(redirectUrl) {
|
|
304
|
-
this.clearAuthState();
|
|
305
|
-
if (globalThis.window !== void 0 && redirectUrl) {
|
|
306
|
-
globalThis.window.location.href = redirectUrl;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
/**
|
|
310
|
-
* Refreshes the session using the stored refresh token.
|
|
311
|
-
*
|
|
312
|
-
* Called automatically by the SDK on 401 responses. Can also be called
|
|
313
|
-
* manually. Multiple concurrent calls are deduplicated (only one refresh
|
|
314
|
-
* request is made).
|
|
315
|
-
*
|
|
316
|
-
* @returns `true` if refresh succeeded, `false` otherwise.
|
|
317
|
-
*
|
|
318
|
-
* @example
|
|
319
|
-
* ```typescript
|
|
320
|
-
* const ok = await mitra.auth.refreshSession();
|
|
321
|
-
* if (!ok) mitra.auth.redirectToLogin();
|
|
322
|
-
* ```
|
|
323
|
-
*/
|
|
324
|
-
async refreshSession() {
|
|
325
|
-
if (!this.#refreshToken) return false;
|
|
326
|
-
if (this.refreshPromise) return this.refreshPromise;
|
|
327
|
-
this.refreshPromise = this.doRefresh();
|
|
328
|
-
try {
|
|
329
|
-
return await this.refreshPromise;
|
|
330
|
-
} finally {
|
|
331
|
-
this.refreshPromise = null;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
/**
|
|
335
|
-
* Fetches the current user from the server and updates local state.
|
|
336
|
-
*
|
|
337
|
-
* Only clears auth state on 401 (expired/invalid token).
|
|
338
|
-
* Transient errors (500, network) return null without clearing the session.
|
|
339
|
-
*
|
|
340
|
-
* @returns The user if authenticated, `null` otherwise.
|
|
341
|
-
*
|
|
342
|
-
* @example
|
|
343
|
-
* ```typescript
|
|
344
|
-
* const user = await mitra.auth.me();
|
|
345
|
-
* if (!user) console.log('Not authenticated');
|
|
346
|
-
* ```
|
|
347
|
-
*/
|
|
348
|
-
async me() {
|
|
349
|
-
if (!this.#accessToken) return null;
|
|
350
|
-
try {
|
|
351
|
-
const user = await this.getCurrentUser();
|
|
352
|
-
this._currentUser = user;
|
|
353
|
-
this.saveToStorage();
|
|
354
|
-
this.notifyListeners();
|
|
355
|
-
return user;
|
|
356
|
-
} catch (error) {
|
|
357
|
-
if (error instanceof MitraApiError && error.status === 401) {
|
|
358
|
-
this.clearAuthState();
|
|
359
|
-
}
|
|
360
|
-
return null;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
/**
|
|
364
|
-
* Validates the current session with the server.
|
|
365
|
-
*
|
|
366
|
-
* @returns `true` if the session is valid, `false` otherwise.
|
|
367
|
-
*
|
|
368
|
-
* @example
|
|
369
|
-
* ```typescript
|
|
370
|
-
* const valid = await mitra.auth.checkAuth();
|
|
371
|
-
* if (!valid) mitra.auth.redirectToLogin();
|
|
372
|
-
* ```
|
|
373
|
-
*/
|
|
374
|
-
async checkAuth() {
|
|
375
|
-
return await this.me() !== null;
|
|
376
|
-
}
|
|
377
|
-
/**
|
|
378
|
-
* Sets the access token manually (e.g., from SSO/OAuth callback).
|
|
379
|
-
*
|
|
380
|
-
* Call `me()` afterwards to fetch the associated user data.
|
|
381
|
-
*
|
|
382
|
-
* @param token - JWT access token.
|
|
383
|
-
* @param saveToStorage - Whether to persist to localStorage (default: true).
|
|
384
|
-
*
|
|
385
|
-
* @example
|
|
386
|
-
* ```typescript
|
|
387
|
-
* mitra.auth.setToken(tokenFromCallback);
|
|
388
|
-
* await mitra.auth.me();
|
|
389
|
-
* ```
|
|
390
|
-
*/
|
|
391
|
-
setToken(token, saveToStorage = true) {
|
|
392
|
-
this.#accessToken = token;
|
|
393
|
-
if (saveToStorage) {
|
|
394
|
-
this.saveToStorage();
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
/**
|
|
398
|
-
* Redirects to `/login?returnUrl=...` for unauthenticated users.
|
|
399
|
-
*
|
|
400
|
-
* @param returnUrl - URL to return to after login (default: '/').
|
|
401
|
-
*
|
|
402
|
-
* @example
|
|
403
|
-
* ```typescript
|
|
404
|
-
* if (!mitra.auth.isAuthenticated) {
|
|
405
|
-
* mitra.auth.redirectToLogin(window.location.pathname);
|
|
406
|
-
* }
|
|
407
|
-
* ```
|
|
408
|
-
*/
|
|
409
|
-
redirectToLogin(returnUrl = "/") {
|
|
410
|
-
if (globalThis.window === void 0) return;
|
|
411
|
-
globalThis.window.location.href = `/login?returnUrl=${encodeURIComponent(returnUrl)}`;
|
|
412
|
-
}
|
|
413
|
-
/**
|
|
414
|
-
* Registers a callback for auth state changes.
|
|
415
|
-
*
|
|
416
|
-
* Called immediately with the current state, then on every sign-in/sign-out.
|
|
417
|
-
*
|
|
418
|
-
* @param callback - Receives the User on login, null on logout.
|
|
419
|
-
* @returns Unsubscribe function.
|
|
420
|
-
*
|
|
421
|
-
* @example
|
|
422
|
-
* ```typescript
|
|
423
|
-
* useEffect(() => {
|
|
424
|
-
* const unsub = mitra.auth.onAuthStateChange((user) => {
|
|
425
|
-
* setUser(user);
|
|
426
|
-
* setLoading(false);
|
|
427
|
-
* });
|
|
428
|
-
* return unsub;
|
|
429
|
-
* }, []);
|
|
430
|
-
* ```
|
|
431
|
-
*/
|
|
432
|
-
onAuthStateChange(callback) {
|
|
433
|
-
this.listeners.add(callback);
|
|
434
|
-
callback(this._currentUser);
|
|
435
|
-
return () => {
|
|
436
|
-
this.listeners.delete(callback);
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
async doRefresh() {
|
|
440
|
-
try {
|
|
441
|
-
const tokenResponse = await this.publicClient.post(
|
|
442
|
-
"/api/v1/auth/refresh-token",
|
|
443
|
-
{ refreshToken: this.#refreshToken }
|
|
444
|
-
);
|
|
445
|
-
this.#accessToken = tokenResponse.accessToken;
|
|
446
|
-
this.#refreshToken = tokenResponse.refreshToken;
|
|
447
|
-
const user = await this.getCurrentUser();
|
|
448
|
-
this.setAuthState(user, tokenResponse.accessToken, tokenResponse.refreshToken);
|
|
449
|
-
return true;
|
|
450
|
-
} catch {
|
|
451
|
-
this.clearAuthState();
|
|
452
|
-
return false;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
setAuthState(user, token, refreshToken) {
|
|
456
|
-
this._currentUser = user;
|
|
457
|
-
this.#accessToken = token;
|
|
458
|
-
this.#refreshToken = refreshToken;
|
|
459
|
-
this.saveToStorage();
|
|
460
|
-
this.notifyListeners();
|
|
461
|
-
}
|
|
462
|
-
async getCurrentUser() {
|
|
463
|
-
const user = await this.currentUserApi.me();
|
|
464
|
-
return { ...user, tenantId: user.tenant.id };
|
|
465
|
-
}
|
|
466
|
-
clearAuthState() {
|
|
467
|
-
this._currentUser = null;
|
|
468
|
-
this.#accessToken = null;
|
|
469
|
-
this.#refreshToken = null;
|
|
470
|
-
this.removeFromStorage();
|
|
471
|
-
this.notifyListeners();
|
|
472
|
-
}
|
|
473
|
-
notifyListeners() {
|
|
474
|
-
this.listeners.forEach((callback) => {
|
|
475
|
-
try {
|
|
476
|
-
callback(this._currentUser);
|
|
477
|
-
} catch (error) {
|
|
478
|
-
console.error("Auth state change listener error:", error);
|
|
479
|
-
}
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
saveToStorage() {
|
|
483
|
-
if (typeof localStorage === "undefined") return;
|
|
484
|
-
try {
|
|
485
|
-
localStorage.setItem(
|
|
486
|
-
this.storageKey,
|
|
487
|
-
JSON.stringify({
|
|
488
|
-
user: this._currentUser,
|
|
489
|
-
token: this.#accessToken,
|
|
490
|
-
refreshToken: this.#refreshToken
|
|
491
|
-
})
|
|
492
|
-
);
|
|
493
|
-
} catch {
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
loadFromStorage() {
|
|
497
|
-
if (typeof localStorage === "undefined") return;
|
|
498
|
-
try {
|
|
499
|
-
const stored = localStorage.getItem(this.storageKey);
|
|
500
|
-
if (stored) {
|
|
501
|
-
const { user, token, refreshToken } = JSON.parse(stored);
|
|
502
|
-
this._currentUser = user;
|
|
503
|
-
this.#accessToken = token;
|
|
504
|
-
this.#refreshToken = refreshToken ?? null;
|
|
505
|
-
}
|
|
506
|
-
} catch {
|
|
507
|
-
this.removeFromStorage();
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
removeFromStorage() {
|
|
511
|
-
if (typeof localStorage === "undefined") return;
|
|
512
|
-
try {
|
|
513
|
-
localStorage.removeItem(this.storageKey);
|
|
514
|
-
} catch {
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
};
|
|
518
|
-
|
|
519
|
-
// src/modules/entities.ts
|
|
520
|
-
import {
|
|
521
|
-
createEntitiesModule
|
|
522
|
-
} from "@mitralab.io/sdk-core";
|
|
523
|
-
var EntitiesModule = class _EntitiesModule {
|
|
524
|
-
constructor(httpClient, dataSourceId) {
|
|
525
|
-
this.httpClient = httpClient;
|
|
526
|
-
void dataSourceId;
|
|
527
|
-
this.core = createEntitiesModule(httpClient, coreErrors);
|
|
528
|
-
}
|
|
529
|
-
core;
|
|
530
|
-
static createProxy(httpClient, dataSourceId) {
|
|
531
|
-
const instance = new _EntitiesModule(httpClient, dataSourceId);
|
|
532
|
-
return new Proxy(instance, {
|
|
533
|
-
get(target, property, receiver) {
|
|
534
|
-
if (typeof property !== "string" || property in target) {
|
|
535
|
-
return Reflect.get(target, property, receiver);
|
|
536
|
-
}
|
|
537
|
-
return target.getTable(property);
|
|
538
|
-
}
|
|
539
|
-
});
|
|
540
|
-
}
|
|
541
|
-
/**
|
|
542
|
-
* Preserved for Platform SDK 1.x compatibility.
|
|
543
|
-
* Records now resolve the app from authenticated context instead of a data source path.
|
|
544
|
-
*/
|
|
545
|
-
setDataSourceId(dataSourceId) {
|
|
546
|
-
void dataSourceId;
|
|
547
|
-
this.core = createEntitiesModule(this.httpClient, coreErrors);
|
|
548
|
-
}
|
|
549
|
-
getTable(tableName) {
|
|
550
|
-
return this.core.getTable(tableName);
|
|
551
|
-
}
|
|
552
|
-
};
|
|
553
|
-
|
|
554
|
-
// src/modules/functions.ts
|
|
555
|
-
import {
|
|
556
|
-
createFunctionsModule
|
|
557
|
-
} from "@mitralab.io/sdk-core";
|
|
558
|
-
var FunctionsModule = class {
|
|
559
|
-
core;
|
|
560
|
-
constructor(httpClient) {
|
|
561
|
-
this.core = createFunctionsModule(httpClient, { emptyInput: "omit-body" }, coreErrors);
|
|
562
|
-
}
|
|
563
|
-
/**
|
|
564
|
-
* Executes a Function using the Platform SDK 1.x server-default invocation semantics.
|
|
565
|
-
* The runtime SDK uses an explicit invocation header instead.
|
|
566
|
-
*/
|
|
567
|
-
async execute(functionId, input) {
|
|
568
|
-
const execution = await this.core.execute(functionId, input);
|
|
569
|
-
if (execution.input === null) {
|
|
570
|
-
throw coreErrors.invalidResponse(
|
|
571
|
-
"Function execution response has an invalid input field"
|
|
572
|
-
);
|
|
573
|
-
}
|
|
574
|
-
return { ...execution, input: execution.input };
|
|
575
|
-
}
|
|
576
|
-
};
|
|
577
|
-
|
|
578
|
-
// src/modules/integration.ts
|
|
579
|
-
import {
|
|
580
|
-
createIntegrationModule
|
|
581
|
-
} from "@mitralab.io/sdk-core";
|
|
582
|
-
var IntegrationModule = class {
|
|
583
|
-
core;
|
|
584
|
-
constructor(httpClient) {
|
|
585
|
-
this.core = createIntegrationModule(httpClient, coreErrors);
|
|
586
|
-
}
|
|
587
|
-
executeResource(resourceId, params) {
|
|
588
|
-
return this.core.executeResource(resourceId, params);
|
|
589
|
-
}
|
|
590
|
-
execute(configId, request) {
|
|
591
|
-
return this.core.execute(configId, request);
|
|
592
|
-
}
|
|
593
|
-
};
|
|
594
|
-
|
|
595
|
-
// src/modules/queries.ts
|
|
596
|
-
import {
|
|
597
|
-
createQueriesModule
|
|
598
|
-
} from "@mitralab.io/sdk-core";
|
|
599
|
-
var QueriesModule = class {
|
|
600
|
-
dataSourceId = "";
|
|
601
|
-
core;
|
|
602
|
-
constructor(httpClient) {
|
|
603
|
-
this.core = createQueriesModule(httpClient, () => this.dataSourceId, coreErrors);
|
|
604
|
-
}
|
|
605
|
-
/** Called by `client.init()` to set the app's resolved data source. */
|
|
606
|
-
setDataSourceId(dataSourceId) {
|
|
607
|
-
this.dataSourceId = dataSourceId;
|
|
608
|
-
}
|
|
609
|
-
async execute(id, parameters) {
|
|
610
|
-
const result = await this.core.execute(id, parameters);
|
|
611
|
-
return { ...result, affectedRows: result.affectedRows ?? null };
|
|
612
|
-
}
|
|
613
|
-
};
|
|
614
|
-
|
|
615
|
-
// src/client.ts
|
|
616
|
-
function expectAppInfoResponse(value) {
|
|
617
|
-
const response = expectObject(
|
|
618
|
-
value,
|
|
619
|
-
"App info response",
|
|
620
|
-
coreErrors
|
|
621
|
-
);
|
|
622
|
-
if (typeof response.dataSourceId !== "string" || !response.dataSourceId.trim()) {
|
|
623
|
-
throw coreErrors.invalidResponse(
|
|
624
|
-
"App info response has an invalid dataSourceId field"
|
|
625
|
-
);
|
|
626
|
-
}
|
|
627
|
-
if (typeof response.allowSignup !== "boolean") {
|
|
628
|
-
throw coreErrors.invalidResponse(
|
|
629
|
-
"App info response has an invalid allowSignup field"
|
|
630
|
-
);
|
|
631
|
-
}
|
|
632
|
-
return {
|
|
633
|
-
dataSourceId: response.dataSourceId,
|
|
634
|
-
allowSignup: response.allowSignup
|
|
635
|
-
};
|
|
636
|
-
}
|
|
637
|
-
function createClient(config) {
|
|
638
|
-
const { appId, apiUrl, onError } = config;
|
|
639
|
-
const iamUrl = `${apiUrl}/iam`;
|
|
640
|
-
const dataManagerUrl = `${apiUrl}/data-manager`;
|
|
641
|
-
const functionsUrl = `${apiUrl}/functions`;
|
|
642
|
-
const integrationUrl = `${apiUrl}/integration`;
|
|
643
|
-
const codeStudioUrl = `${apiUrl}/code-studio`;
|
|
644
|
-
const authModule = new AuthModule(appId, iamUrl);
|
|
645
|
-
const onUnauthorized = () => authModule.refreshSession();
|
|
646
|
-
const defaultHeaders = { "X-App-Id": appId };
|
|
647
|
-
const httpClient = new HttpClient({
|
|
648
|
-
baseUrl: dataManagerUrl,
|
|
649
|
-
getToken: () => authModule.accessToken,
|
|
650
|
-
onUnauthorized,
|
|
651
|
-
onError,
|
|
652
|
-
defaultHeaders
|
|
653
|
-
});
|
|
654
|
-
const entitiesModule = EntitiesModule.createProxy(httpClient, "");
|
|
655
|
-
const functionsHttpClient = new HttpClient({
|
|
656
|
-
baseUrl: functionsUrl,
|
|
657
|
-
getToken: () => authModule.accessToken,
|
|
658
|
-
onUnauthorized,
|
|
659
|
-
onError,
|
|
660
|
-
defaultHeaders
|
|
661
|
-
});
|
|
662
|
-
const functionsModule = new FunctionsModule(functionsHttpClient);
|
|
663
|
-
const integrationHttpClient = new HttpClient({
|
|
664
|
-
baseUrl: integrationUrl,
|
|
665
|
-
getToken: () => authModule.accessToken,
|
|
666
|
-
onUnauthorized,
|
|
667
|
-
onError,
|
|
668
|
-
defaultHeaders
|
|
669
|
-
});
|
|
670
|
-
const integrationModule = new IntegrationModule(integrationHttpClient);
|
|
671
|
-
const queriesModule = new QueriesModule(httpClient);
|
|
672
|
-
let initialized = false;
|
|
673
|
-
let allowSignup = true;
|
|
674
|
-
async function init() {
|
|
675
|
-
if (initialized) return;
|
|
676
|
-
const publicClient = new HttpClient({
|
|
677
|
-
baseUrl: codeStudioUrl,
|
|
678
|
-
getToken: () => null
|
|
679
|
-
});
|
|
680
|
-
const appInfo = expectAppInfoResponse(
|
|
681
|
-
await publicClient.get(
|
|
682
|
-
`/api/v1/apps/${encodePathSegment(appId, "appId", coreErrors)}/info`
|
|
683
|
-
)
|
|
684
|
-
);
|
|
685
|
-
entitiesModule.setDataSourceId(appInfo.dataSourceId);
|
|
686
|
-
queriesModule.setDataSourceId(appInfo.dataSourceId);
|
|
687
|
-
allowSignup = appInfo.allowSignup;
|
|
688
|
-
initialized = true;
|
|
689
|
-
}
|
|
690
|
-
return {
|
|
691
|
-
init,
|
|
692
|
-
auth: authModule,
|
|
693
|
-
entities: entitiesModule,
|
|
694
|
-
functions: functionsModule,
|
|
695
|
-
integration: integrationModule,
|
|
696
|
-
queries: queriesModule,
|
|
697
|
-
get allowSignup() {
|
|
698
|
-
return allowSignup;
|
|
699
|
-
},
|
|
700
|
-
config
|
|
701
|
-
};
|
|
702
|
-
}
|
|
703
|
-
export {
|
|
704
|
-
MitraApiError,
|
|
705
|
-
createClient
|
|
706
|
-
};
|