@insforge/sdk 1.4.2 → 1.4.4
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 +141 -135
- package/SDK-REFERENCE.md +146 -168
- package/dist/{client-BR9o-WUm.d.ts → client-BZyLCjO1.d.mts} +62 -122
- package/dist/{client-C-qBRoea.d.mts → client-Dh7GOydb.d.ts} +62 -122
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +585 -428
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +588 -428
- package/dist/index.mjs.map +1 -1
- package/dist/{middleware-Tu_RlUAt.d.ts → middleware-CicBLgnO.d.ts} +1 -1
- package/dist/{middleware-K59XjpUX.d.mts → middleware-o3zPqPvY.d.mts} +1 -1
- package/dist/ssr/middleware.d.mts +2 -2
- package/dist/ssr/middleware.d.ts +2 -2
- package/dist/ssr/middleware.js +71 -52
- package/dist/ssr/middleware.js.map +1 -1
- package/dist/ssr/middleware.mjs +71 -52
- package/dist/ssr/middleware.mjs.map +1 -1
- package/dist/ssr.d.mts +4 -4
- package/dist/ssr.d.ts +4 -4
- package/dist/ssr.js +680 -544
- package/dist/ssr.js.map +1 -1
- package/dist/ssr.mjs +684 -545
- package/dist/ssr.mjs.map +1 -1
- package/dist/{types-Dk-44JJf.d.mts → types-MKmYAYeg.d.mts} +12 -0
- package/dist/{types-Dk-44JJf.d.ts → types-MKmYAYeg.d.ts} +12 -0
- package/package.json +12 -5
package/dist/index.js
CHANGED
|
@@ -32,6 +32,7 @@ var src_exports = {};
|
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
AI: () => AI,
|
|
34
34
|
Auth: () => Auth,
|
|
35
|
+
AuthChangeEvent: () => AuthChangeEvent,
|
|
35
36
|
Database: () => Database,
|
|
36
37
|
Emails: () => Emails,
|
|
37
38
|
Functions: () => Functions,
|
|
@@ -43,7 +44,6 @@ __export(src_exports, {
|
|
|
43
44
|
Realtime: () => Realtime,
|
|
44
45
|
Storage: () => Storage,
|
|
45
46
|
StorageBucket: () => StorageBucket,
|
|
46
|
-
TokenManager: () => TokenManager,
|
|
47
47
|
createAdminClient: () => createAdminClient,
|
|
48
48
|
createClient: () => createClient,
|
|
49
49
|
default: () => src_default
|
|
@@ -97,7 +97,9 @@ function redactHeaders(headers) {
|
|
|
97
97
|
return redacted;
|
|
98
98
|
}
|
|
99
99
|
function sanitizeBody(body) {
|
|
100
|
-
if (body === null || body === void 0)
|
|
100
|
+
if (body === null || body === void 0) {
|
|
101
|
+
return body;
|
|
102
|
+
}
|
|
101
103
|
if (typeof body === "string") {
|
|
102
104
|
try {
|
|
103
105
|
const parsed = JSON.parse(body);
|
|
@@ -106,7 +108,9 @@ function sanitizeBody(body) {
|
|
|
106
108
|
return body;
|
|
107
109
|
}
|
|
108
110
|
}
|
|
109
|
-
if (Array.isArray(body))
|
|
111
|
+
if (Array.isArray(body)) {
|
|
112
|
+
return body.map(sanitizeBody);
|
|
113
|
+
}
|
|
110
114
|
if (typeof body === "object") {
|
|
111
115
|
const sanitized = {};
|
|
112
116
|
for (const [key, value] of Object.entries(body)) {
|
|
@@ -121,7 +125,9 @@ function sanitizeBody(body) {
|
|
|
121
125
|
return body;
|
|
122
126
|
}
|
|
123
127
|
function formatBody(body) {
|
|
124
|
-
if (body === void 0 || body === null)
|
|
128
|
+
if (body === void 0 || body === null) {
|
|
129
|
+
return "";
|
|
130
|
+
}
|
|
125
131
|
if (typeof body === "string") {
|
|
126
132
|
try {
|
|
127
133
|
return JSON.stringify(JSON.parse(body), null, 2);
|
|
@@ -158,7 +164,9 @@ var Logger = class {
|
|
|
158
164
|
* @param args - Additional arguments to pass to the log function
|
|
159
165
|
*/
|
|
160
166
|
log(message, ...args) {
|
|
161
|
-
if (!this.enabled)
|
|
167
|
+
if (!this.enabled) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
162
170
|
const formatted = `[InsForge Debug] ${message}`;
|
|
163
171
|
if (this.customLog) {
|
|
164
172
|
this.customLog(formatted, ...args);
|
|
@@ -172,7 +180,9 @@ var Logger = class {
|
|
|
172
180
|
* @param args - Additional arguments to pass to the log function
|
|
173
181
|
*/
|
|
174
182
|
warn(message, ...args) {
|
|
175
|
-
if (!this.enabled)
|
|
183
|
+
if (!this.enabled) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
176
186
|
const formatted = `[InsForge Debug] ${message}`;
|
|
177
187
|
if (this.customLog) {
|
|
178
188
|
this.customLog(formatted, ...args);
|
|
@@ -186,7 +196,9 @@ var Logger = class {
|
|
|
186
196
|
* @param args - Additional arguments to pass to the log function
|
|
187
197
|
*/
|
|
188
198
|
error(message, ...args) {
|
|
189
|
-
if (!this.enabled)
|
|
199
|
+
if (!this.enabled) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
190
202
|
const formatted = `[InsForge Debug] ${message}`;
|
|
191
203
|
if (this.customLog) {
|
|
192
204
|
this.customLog(formatted, ...args);
|
|
@@ -203,10 +215,10 @@ var Logger = class {
|
|
|
203
215
|
* @param body - Request body (sensitive fields will be masked)
|
|
204
216
|
*/
|
|
205
217
|
logRequest(method, url, headers, body) {
|
|
206
|
-
if (!this.enabled)
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
];
|
|
218
|
+
if (!this.enabled) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const parts = [`\u2192 ${method} ${url}`];
|
|
210
222
|
if (headers && Object.keys(headers).length > 0) {
|
|
211
223
|
parts.push(` Headers: ${JSON.stringify(redactHeaders(headers))}`);
|
|
212
224
|
}
|
|
@@ -227,10 +239,10 @@ var Logger = class {
|
|
|
227
239
|
* @param body - Response body (sensitive fields will be masked, large bodies truncated)
|
|
228
240
|
*/
|
|
229
241
|
logResponse(method, url, status, durationMs, body) {
|
|
230
|
-
if (!this.enabled)
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
];
|
|
242
|
+
if (!this.enabled) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const parts = [`\u2190 ${method} ${url} ${status} (${durationMs}ms)`];
|
|
234
246
|
const formattedBody = formatBody(sanitizeBody(body));
|
|
235
247
|
if (formattedBody) {
|
|
236
248
|
const truncated = formattedBody.length > 1e3 ? formattedBody.slice(0, 1e3) + "... [truncated]" : formattedBody;
|
|
@@ -245,21 +257,34 @@ var Logger = class {
|
|
|
245
257
|
};
|
|
246
258
|
|
|
247
259
|
// src/lib/token-manager.ts
|
|
260
|
+
var AuthChangeEvent = {
|
|
261
|
+
SIGNED_IN: "signedIn",
|
|
262
|
+
SIGNED_OUT: "signedOut",
|
|
263
|
+
TOKEN_REFRESHED: "tokenRefreshed"
|
|
264
|
+
};
|
|
248
265
|
var CSRF_TOKEN_COOKIE = "insforge_csrf_token";
|
|
249
266
|
function getCsrfToken() {
|
|
250
|
-
if (typeof document === "undefined")
|
|
267
|
+
if (typeof document === "undefined") {
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
251
270
|
const match = document.cookie.split(";").find((c) => c.trim().startsWith(`${CSRF_TOKEN_COOKIE}=`));
|
|
252
|
-
if (!match)
|
|
271
|
+
if (!match) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
253
274
|
return match.split("=")[1] || null;
|
|
254
275
|
}
|
|
255
276
|
function setCsrfToken(token) {
|
|
256
|
-
if (typeof document === "undefined")
|
|
277
|
+
if (typeof document === "undefined") {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
257
280
|
const maxAge = 7 * 24 * 60 * 60;
|
|
258
281
|
const secure = typeof window !== "undefined" && window.location.protocol === "https:" ? "; Secure" : "";
|
|
259
282
|
document.cookie = `${CSRF_TOKEN_COOKIE}=${encodeURIComponent(token)}; path=/; max-age=${maxAge}; SameSite=Lax${secure}`;
|
|
260
283
|
}
|
|
261
284
|
function clearCsrfToken() {
|
|
262
|
-
if (typeof document === "undefined")
|
|
285
|
+
if (typeof document === "undefined") {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
263
288
|
const secure = typeof window !== "undefined" && window.location.protocol === "https:" ? "; Secure" : "";
|
|
264
289
|
document.cookie = `${CSRF_TOKEN_COOKIE}=; path=/; max-age=0; SameSite=Lax${secure}`;
|
|
265
290
|
}
|
|
@@ -268,25 +293,26 @@ var TokenManager = class {
|
|
|
268
293
|
// In-memory storage
|
|
269
294
|
this.accessToken = null;
|
|
270
295
|
this.user = null;
|
|
271
|
-
|
|
272
|
-
this.onTokenChange = null;
|
|
296
|
+
this.authStateChangeCallbacks = /* @__PURE__ */ new Map();
|
|
273
297
|
}
|
|
274
298
|
/**
|
|
275
299
|
* Save session in memory
|
|
276
300
|
*/
|
|
277
|
-
saveSession(session) {
|
|
301
|
+
saveSession(session, event = AuthChangeEvent.SIGNED_IN) {
|
|
278
302
|
const tokenChanged = session.accessToken !== this.accessToken;
|
|
279
303
|
this.accessToken = session.accessToken;
|
|
280
304
|
this.user = session.user;
|
|
281
|
-
if (tokenChanged
|
|
282
|
-
this.
|
|
305
|
+
if (tokenChanged) {
|
|
306
|
+
this.notifyAuthStateChange(event);
|
|
283
307
|
}
|
|
284
308
|
}
|
|
285
309
|
/**
|
|
286
310
|
* Get current session
|
|
287
311
|
*/
|
|
288
312
|
getSession() {
|
|
289
|
-
if (!this.accessToken || !this.user)
|
|
313
|
+
if (!this.accessToken || !this.user) {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
290
316
|
return {
|
|
291
317
|
accessToken: this.accessToken,
|
|
292
318
|
user: this.user
|
|
@@ -301,11 +327,11 @@ var TokenManager = class {
|
|
|
301
327
|
/**
|
|
302
328
|
* Set access token
|
|
303
329
|
*/
|
|
304
|
-
setAccessToken(token) {
|
|
330
|
+
setAccessToken(token, event = AuthChangeEvent.SIGNED_IN) {
|
|
305
331
|
const tokenChanged = token !== this.accessToken;
|
|
306
332
|
this.accessToken = token;
|
|
307
|
-
if (tokenChanged
|
|
308
|
-
this.
|
|
333
|
+
if (tokenChanged) {
|
|
334
|
+
this.notifyAuthStateChange(event);
|
|
309
335
|
}
|
|
310
336
|
}
|
|
311
337
|
/**
|
|
@@ -327,22 +353,74 @@ var TokenManager = class {
|
|
|
327
353
|
const hadToken = this.accessToken !== null;
|
|
328
354
|
this.accessToken = null;
|
|
329
355
|
this.user = null;
|
|
330
|
-
if (hadToken
|
|
331
|
-
this.
|
|
356
|
+
if (hadToken) {
|
|
357
|
+
this.notifyAuthStateChange(AuthChangeEvent.SIGNED_OUT);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
onAuthStateChange(callback) {
|
|
361
|
+
const id = Symbol("auth-state-change");
|
|
362
|
+
this.authStateChangeCallbacks.set(id, callback);
|
|
363
|
+
return () => this.authStateChangeCallbacks.delete(id);
|
|
364
|
+
}
|
|
365
|
+
notifyAuthStateChange(event) {
|
|
366
|
+
for (const callback of this.authStateChangeCallbacks.values()) {
|
|
367
|
+
try {
|
|
368
|
+
callback(event);
|
|
369
|
+
} catch (error) {
|
|
370
|
+
console.error("Error in auth state change callback:", error);
|
|
371
|
+
}
|
|
332
372
|
}
|
|
333
373
|
}
|
|
334
374
|
};
|
|
335
375
|
|
|
376
|
+
// src/lib/jwt.ts
|
|
377
|
+
function decodeBase64Url(input) {
|
|
378
|
+
const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
379
|
+
const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
|
|
380
|
+
const binary = atob(padded);
|
|
381
|
+
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
382
|
+
return new TextDecoder().decode(bytes);
|
|
383
|
+
}
|
|
384
|
+
function getJwtExpiration(token) {
|
|
385
|
+
if (!token) {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
const [, payload] = token.split(".");
|
|
389
|
+
if (!payload) {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
try {
|
|
393
|
+
const parsed = JSON.parse(decodeBase64Url(payload));
|
|
394
|
+
if (typeof parsed.exp !== "number" || !Number.isFinite(parsed.exp)) {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
return new Date(parsed.exp * 1e3);
|
|
398
|
+
} catch {
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function isJwtExpiredOrExpiring(token, leewaySeconds = 60) {
|
|
403
|
+
if (!token) {
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
const expires = getJwtExpiration(token);
|
|
407
|
+
if (!expires) {
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
return expires.getTime() <= Date.now() + leewaySeconds * 1e3;
|
|
411
|
+
}
|
|
412
|
+
|
|
336
413
|
// src/lib/http-client.ts
|
|
337
414
|
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([500, 502, 503, 504]);
|
|
338
415
|
var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
339
|
-
var REFRESHABLE_AUTH_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
340
|
-
"AUTH_UNAUTHORIZED",
|
|
341
|
-
"PGRST301"
|
|
342
|
-
]);
|
|
416
|
+
var REFRESHABLE_AUTH_ERROR_CODES = /* @__PURE__ */ new Set(["AUTH_UNAUTHORIZED", "PGRST301"]);
|
|
343
417
|
function serializeBody(method, body, headers) {
|
|
344
|
-
if (body === void 0)
|
|
345
|
-
|
|
418
|
+
if (body === void 0) {
|
|
419
|
+
return void 0;
|
|
420
|
+
}
|
|
421
|
+
if (method === "GET" || method === "HEAD") {
|
|
422
|
+
return void 0;
|
|
423
|
+
}
|
|
346
424
|
if (typeof FormData !== "undefined" && body instanceof FormData) {
|
|
347
425
|
return body;
|
|
348
426
|
}
|
|
@@ -350,7 +428,9 @@ function serializeBody(method, body, headers) {
|
|
|
350
428
|
return JSON.stringify(body);
|
|
351
429
|
}
|
|
352
430
|
async function parseResponse(response) {
|
|
353
|
-
if (response.status === 204)
|
|
431
|
+
if (response.status === 204) {
|
|
432
|
+
return void 0;
|
|
433
|
+
}
|
|
354
434
|
let data;
|
|
355
435
|
const contentType = response.headers.get("content-type");
|
|
356
436
|
try {
|
|
@@ -452,31 +532,24 @@ var HttpClient = class {
|
|
|
452
532
|
return statusCode === 401 && REFRESHABLE_AUTH_ERROR_CODES.has(errorCode ?? "") && !this.config.isServerMode && !this.config.accessToken && !this.config.edgeFunctionToken && !options.skipAuthRefresh && authToken !== null;
|
|
453
533
|
}
|
|
454
534
|
async fetchWithRetry(args) {
|
|
455
|
-
const {
|
|
456
|
-
method,
|
|
457
|
-
url,
|
|
458
|
-
headers,
|
|
459
|
-
body,
|
|
460
|
-
fetchOptions,
|
|
461
|
-
callerSignal,
|
|
462
|
-
maxAttempts
|
|
463
|
-
} = args;
|
|
535
|
+
const { method, url, headers, body, fetchOptions, callerSignal, maxAttempts } = args;
|
|
464
536
|
let lastError;
|
|
465
537
|
for (let attempt = 0; attempt <= maxAttempts; attempt++) {
|
|
466
538
|
if (attempt > 0) {
|
|
467
539
|
const delay = this.computeRetryDelay(attempt);
|
|
468
|
-
this.logger.warn(
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
540
|
+
this.logger.warn(`Retry ${attempt}/${maxAttempts} for ${method} ${url} in ${delay}ms`);
|
|
541
|
+
if (callerSignal?.aborted) {
|
|
542
|
+
throw callerSignal.reason;
|
|
543
|
+
}
|
|
472
544
|
await new Promise((resolve, reject) => {
|
|
473
545
|
const onAbort = () => {
|
|
474
546
|
clearTimeout(timer2);
|
|
475
547
|
reject(callerSignal.reason);
|
|
476
548
|
};
|
|
477
549
|
const timer2 = setTimeout(() => {
|
|
478
|
-
if (callerSignal)
|
|
550
|
+
if (callerSignal) {
|
|
479
551
|
callerSignal.removeEventListener("abort", onAbort);
|
|
552
|
+
}
|
|
480
553
|
resolve();
|
|
481
554
|
}, delay);
|
|
482
555
|
if (callerSignal) {
|
|
@@ -518,7 +591,9 @@ var HttpClient = class {
|
|
|
518
591
|
...controller ? { signal: controller.signal } : {}
|
|
519
592
|
});
|
|
520
593
|
if (this.isRetryableStatus(response.status) && attempt < maxAttempts) {
|
|
521
|
-
if (timer !== void 0)
|
|
594
|
+
if (timer !== void 0) {
|
|
595
|
+
clearTimeout(timer);
|
|
596
|
+
}
|
|
522
597
|
await response.body?.cancel();
|
|
523
598
|
lastError = new InsForgeError(
|
|
524
599
|
`Server error: ${response.status} ${response.statusText}`,
|
|
@@ -527,10 +602,14 @@ var HttpClient = class {
|
|
|
527
602
|
);
|
|
528
603
|
continue;
|
|
529
604
|
}
|
|
530
|
-
if (timer !== void 0)
|
|
605
|
+
if (timer !== void 0) {
|
|
606
|
+
clearTimeout(timer);
|
|
607
|
+
}
|
|
531
608
|
return response;
|
|
532
609
|
} catch (err) {
|
|
533
|
-
if (timer !== void 0)
|
|
610
|
+
if (timer !== void 0) {
|
|
611
|
+
clearTimeout(timer);
|
|
612
|
+
}
|
|
534
613
|
if (err?.name === "AbortError") {
|
|
535
614
|
if (controller && controller.signal.aborted && this.timeout > 0 && !callerSignal?.aborted) {
|
|
536
615
|
throw new InsForgeError(
|
|
@@ -552,11 +631,7 @@ var HttpClient = class {
|
|
|
552
631
|
);
|
|
553
632
|
}
|
|
554
633
|
}
|
|
555
|
-
throw lastError || new InsForgeError(
|
|
556
|
-
"Request failed after all retry attempts",
|
|
557
|
-
0,
|
|
558
|
-
"NETWORK_ERROR"
|
|
559
|
-
);
|
|
634
|
+
throw lastError || new InsForgeError("Request failed after all retry attempts", 0, "NETWORK_ERROR");
|
|
560
635
|
}
|
|
561
636
|
/**
|
|
562
637
|
* Performs an HTTP request with automatic retry and timeout handling.
|
|
@@ -636,31 +711,15 @@ var HttpClient = class {
|
|
|
636
711
|
}
|
|
637
712
|
throw err;
|
|
638
713
|
}
|
|
639
|
-
this.logger.logResponse(
|
|
640
|
-
method,
|
|
641
|
-
url,
|
|
642
|
-
response.status,
|
|
643
|
-
Date.now() - startTime,
|
|
644
|
-
data
|
|
645
|
-
);
|
|
714
|
+
this.logger.logResponse(method, url, response.status, Date.now() - startTime, data);
|
|
646
715
|
return data;
|
|
647
716
|
}
|
|
648
717
|
async request(method, path, options = {}) {
|
|
649
718
|
const tokenUsed = this.userToken;
|
|
650
719
|
try {
|
|
651
|
-
return await this.handleRequest(
|
|
652
|
-
method,
|
|
653
|
-
path,
|
|
654
|
-
{ ...options },
|
|
655
|
-
tokenUsed
|
|
656
|
-
);
|
|
720
|
+
return await this.handleRequest(method, path, { ...options }, tokenUsed);
|
|
657
721
|
} catch (error) {
|
|
658
|
-
if (!(error instanceof InsForgeError) || !this.shouldRefreshAccessToken(
|
|
659
|
-
error.statusCode,
|
|
660
|
-
error.error,
|
|
661
|
-
tokenUsed,
|
|
662
|
-
options
|
|
663
|
-
)) {
|
|
722
|
+
if (!(error instanceof InsForgeError) || !this.shouldRefreshAccessToken(error.statusCode, error.error, tokenUsed, options)) {
|
|
664
723
|
throw error;
|
|
665
724
|
}
|
|
666
725
|
if (tokenUsed !== this.userToken) {
|
|
@@ -745,12 +804,7 @@ var HttpClient = class {
|
|
|
745
804
|
callerSignal,
|
|
746
805
|
maxAttempts
|
|
747
806
|
});
|
|
748
|
-
this.logger.logResponse(
|
|
749
|
-
method,
|
|
750
|
-
url,
|
|
751
|
-
response.status,
|
|
752
|
-
Date.now() - startTime
|
|
753
|
-
);
|
|
807
|
+
this.logger.logResponse(method, url, response.status, Date.now() - startTime);
|
|
754
808
|
let errorCode = null;
|
|
755
809
|
if (response.status === 401) {
|
|
756
810
|
try {
|
|
@@ -764,12 +818,7 @@ var HttpClient = class {
|
|
|
764
818
|
} catch {
|
|
765
819
|
}
|
|
766
820
|
}
|
|
767
|
-
if (!this.shouldRefreshAccessToken(
|
|
768
|
-
response.status,
|
|
769
|
-
errorCode,
|
|
770
|
-
tokenUsed,
|
|
771
|
-
options
|
|
772
|
-
)) {
|
|
821
|
+
if (!this.shouldRefreshAccessToken(response.status, errorCode, tokenUsed, options)) {
|
|
773
822
|
return response;
|
|
774
823
|
}
|
|
775
824
|
if (tokenUsed !== this.userToken) {
|
|
@@ -863,10 +912,30 @@ var HttpClient = class {
|
|
|
863
912
|
})();
|
|
864
913
|
return this.refreshPromise;
|
|
865
914
|
}
|
|
915
|
+
/** Returns a token safe to use for a new connection handshake. */
|
|
916
|
+
async getValidAccessToken(leewaySeconds = 60) {
|
|
917
|
+
const accessToken = this.tokenManager.getAccessToken() ?? this.userToken;
|
|
918
|
+
if (!accessToken || !isJwtExpiredOrExpiring(accessToken, leewaySeconds)) {
|
|
919
|
+
return accessToken;
|
|
920
|
+
}
|
|
921
|
+
const canRefresh = !this.config.isServerMode && !this.config.accessToken && !this.config.edgeFunctionToken && this.userToken !== null;
|
|
922
|
+
if (!canRefresh) {
|
|
923
|
+
return accessToken;
|
|
924
|
+
}
|
|
925
|
+
try {
|
|
926
|
+
const refreshed = await this.refreshAndSaveSession();
|
|
927
|
+
return refreshed.accessToken;
|
|
928
|
+
} catch (error) {
|
|
929
|
+
if (error instanceof InsForgeError && (error.statusCode === 401 || error.statusCode === 403) && this.userToken === accessToken) {
|
|
930
|
+
this.clearAuthSession();
|
|
931
|
+
}
|
|
932
|
+
throw error;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
866
935
|
async refreshAndSaveSession() {
|
|
867
936
|
const newTokenData = await this.refreshAccessToken();
|
|
868
937
|
this.setAuthToken(newTokenData.accessToken);
|
|
869
|
-
this.tokenManager.saveSession(newTokenData);
|
|
938
|
+
this.tokenManager.saveSession(newTokenData, AuthChangeEvent.TOKEN_REFRESHED);
|
|
870
939
|
if (newTokenData.csrfToken) {
|
|
871
940
|
setCsrfToken(newTokenData.csrfToken);
|
|
872
941
|
}
|
|
@@ -962,11 +1031,15 @@ var Auth = class {
|
|
|
962
1031
|
isServerMode() {
|
|
963
1032
|
return !!this.options.isServerMode;
|
|
964
1033
|
}
|
|
1034
|
+
/** Subscribe to SDK authentication state changes. */
|
|
1035
|
+
onAuthStateChange(callback) {
|
|
1036
|
+
return this.tokenManager.onAuthStateChange(callback);
|
|
1037
|
+
}
|
|
965
1038
|
/**
|
|
966
1039
|
* Save session from API response
|
|
967
1040
|
* Handles token storage, CSRF token, and HTTP auth header
|
|
968
1041
|
*/
|
|
969
|
-
saveSessionFromResponse(response) {
|
|
1042
|
+
saveSessionFromResponse(response, event = AuthChangeEvent.SIGNED_IN) {
|
|
970
1043
|
if (!response.accessToken || !response.user) {
|
|
971
1044
|
return false;
|
|
972
1045
|
}
|
|
@@ -978,7 +1051,7 @@ var Auth = class {
|
|
|
978
1051
|
setCsrfToken(response.csrfToken);
|
|
979
1052
|
}
|
|
980
1053
|
if (!this.isServerMode()) {
|
|
981
|
-
this.tokenManager.saveSession(session);
|
|
1054
|
+
this.tokenManager.saveSession(session, event);
|
|
982
1055
|
}
|
|
983
1056
|
this.http.setAuthToken(response.accessToken);
|
|
984
1057
|
this.http.setRefreshToken(response.refreshToken ?? null);
|
|
@@ -992,7 +1065,9 @@ var Auth = class {
|
|
|
992
1065
|
* Supports PKCE flow (insforge_code)
|
|
993
1066
|
*/
|
|
994
1067
|
async detectAuthCallback() {
|
|
995
|
-
if (this.isServerMode() || typeof window === "undefined")
|
|
1068
|
+
if (this.isServerMode() || typeof window === "undefined") {
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
996
1071
|
try {
|
|
997
1072
|
const params = new URLSearchParams(window.location.search);
|
|
998
1073
|
const error = params.get("error");
|
|
@@ -1054,10 +1129,16 @@ var Auth = class {
|
|
|
1054
1129
|
async signOut() {
|
|
1055
1130
|
try {
|
|
1056
1131
|
try {
|
|
1132
|
+
const serverMode = this.isServerMode();
|
|
1133
|
+
const csrfToken = !serverMode ? getCsrfToken() : null;
|
|
1057
1134
|
await this.http.post(
|
|
1058
|
-
|
|
1135
|
+
serverMode ? "/api/auth/logout?client_type=mobile" : "/api/auth/logout",
|
|
1059
1136
|
void 0,
|
|
1060
|
-
{
|
|
1137
|
+
{
|
|
1138
|
+
credentials: "include",
|
|
1139
|
+
skipAuthRefresh: true,
|
|
1140
|
+
...csrfToken ? { headers: { "X-CSRF-Token": csrfToken } } : {}
|
|
1141
|
+
}
|
|
1061
1142
|
);
|
|
1062
1143
|
} catch {
|
|
1063
1144
|
}
|
|
@@ -1094,11 +1175,7 @@ var Auth = class {
|
|
|
1094
1175
|
if (!signInOptions || !signInOptions.redirectTo) {
|
|
1095
1176
|
return {
|
|
1096
1177
|
data: {},
|
|
1097
|
-
error: new InsForgeError(
|
|
1098
|
-
"Redirect URI is required",
|
|
1099
|
-
400,
|
|
1100
|
-
import_shared_schemas.ERROR_CODES.INVALID_INPUT
|
|
1101
|
-
)
|
|
1178
|
+
error: new InsForgeError("Redirect URI is required", 400, import_shared_schemas.ERROR_CODES.INVALID_INPUT)
|
|
1102
1179
|
};
|
|
1103
1180
|
}
|
|
1104
1181
|
const { provider } = signInOptions;
|
|
@@ -1173,10 +1250,7 @@ var Auth = class {
|
|
|
1173
1250
|
error: null
|
|
1174
1251
|
};
|
|
1175
1252
|
} catch (error) {
|
|
1176
|
-
return wrapError(
|
|
1177
|
-
error,
|
|
1178
|
-
"An unexpected error occurred during OAuth code exchange"
|
|
1179
|
-
);
|
|
1253
|
+
return wrapError(error, "An unexpected error occurred during OAuth code exchange");
|
|
1180
1254
|
}
|
|
1181
1255
|
}
|
|
1182
1256
|
/**
|
|
@@ -1203,10 +1277,7 @@ var Auth = class {
|
|
|
1203
1277
|
error: null
|
|
1204
1278
|
};
|
|
1205
1279
|
} catch (error) {
|
|
1206
|
-
return wrapError(
|
|
1207
|
-
error,
|
|
1208
|
-
"An unexpected error occurred during ID token sign in"
|
|
1209
|
-
);
|
|
1280
|
+
return wrapError(error, "An unexpected error occurred during ID token sign in");
|
|
1210
1281
|
}
|
|
1211
1282
|
}
|
|
1212
1283
|
// ============================================================================
|
|
@@ -1247,14 +1318,11 @@ var Auth = class {
|
|
|
1247
1318
|
}
|
|
1248
1319
|
);
|
|
1249
1320
|
if (response.accessToken) {
|
|
1250
|
-
this.saveSessionFromResponse(response);
|
|
1321
|
+
this.saveSessionFromResponse(response, AuthChangeEvent.TOKEN_REFRESHED);
|
|
1251
1322
|
}
|
|
1252
1323
|
return { data: response, error: null };
|
|
1253
1324
|
} catch (error) {
|
|
1254
|
-
return wrapError(
|
|
1255
|
-
error,
|
|
1256
|
-
"An unexpected error occurred during session refresh"
|
|
1257
|
-
);
|
|
1325
|
+
return wrapError(error, "An unexpected error occurred during session refresh");
|
|
1258
1326
|
}
|
|
1259
1327
|
}
|
|
1260
1328
|
/**
|
|
@@ -1265,11 +1333,11 @@ var Auth = class {
|
|
|
1265
1333
|
try {
|
|
1266
1334
|
if (this.isServerMode()) {
|
|
1267
1335
|
const accessToken = this.tokenManager.getAccessToken();
|
|
1268
|
-
if (!accessToken)
|
|
1336
|
+
if (!accessToken) {
|
|
1337
|
+
return { data: { user: null }, error: null };
|
|
1338
|
+
}
|
|
1269
1339
|
this.http.setAuthToken(accessToken);
|
|
1270
|
-
const response = await this.http.get(
|
|
1271
|
-
"/api/auth/sessions/current"
|
|
1272
|
-
);
|
|
1340
|
+
const response = await this.http.get("/api/auth/sessions/current");
|
|
1273
1341
|
const user = response.user ?? null;
|
|
1274
1342
|
return { data: { user }, error: null };
|
|
1275
1343
|
}
|
|
@@ -1307,25 +1375,17 @@ var Auth = class {
|
|
|
1307
1375
|
// ============================================================================
|
|
1308
1376
|
async getProfile(userId) {
|
|
1309
1377
|
try {
|
|
1310
|
-
const response = await this.http.get(
|
|
1311
|
-
`/api/auth/profiles/${userId}`
|
|
1312
|
-
);
|
|
1378
|
+
const response = await this.http.get(`/api/auth/profiles/${userId}`);
|
|
1313
1379
|
return { data: response, error: null };
|
|
1314
1380
|
} catch (error) {
|
|
1315
|
-
return wrapError(
|
|
1316
|
-
error,
|
|
1317
|
-
"An unexpected error occurred while fetching user profile"
|
|
1318
|
-
);
|
|
1381
|
+
return wrapError(error, "An unexpected error occurred while fetching user profile");
|
|
1319
1382
|
}
|
|
1320
1383
|
}
|
|
1321
1384
|
async setProfile(profile) {
|
|
1322
1385
|
try {
|
|
1323
|
-
const response = await this.http.patch(
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
profile
|
|
1327
|
-
}
|
|
1328
|
-
);
|
|
1386
|
+
const response = await this.http.patch("/api/auth/profiles/current", {
|
|
1387
|
+
profile
|
|
1388
|
+
});
|
|
1329
1389
|
const currentUser = this.tokenManager.getUser();
|
|
1330
1390
|
if (!this.isServerMode() && currentUser && response.profile !== void 0) {
|
|
1331
1391
|
this.tokenManager.setUser({
|
|
@@ -1335,10 +1395,7 @@ var Auth = class {
|
|
|
1335
1395
|
}
|
|
1336
1396
|
return { data: response, error: null };
|
|
1337
1397
|
} catch (error) {
|
|
1338
|
-
return wrapError(
|
|
1339
|
-
error,
|
|
1340
|
-
"An unexpected error occurred while updating user profile"
|
|
1341
|
-
);
|
|
1398
|
+
return wrapError(error, "An unexpected error occurred while updating user profile");
|
|
1342
1399
|
}
|
|
1343
1400
|
}
|
|
1344
1401
|
// ============================================================================
|
|
@@ -1351,10 +1408,7 @@ var Auth = class {
|
|
|
1351
1408
|
});
|
|
1352
1409
|
return { data: response, error: null };
|
|
1353
1410
|
} catch (error) {
|
|
1354
|
-
return wrapError(
|
|
1355
|
-
error,
|
|
1356
|
-
"An unexpected error occurred while sending verification email"
|
|
1357
|
-
);
|
|
1411
|
+
return wrapError(error, "An unexpected error occurred while sending verification email");
|
|
1358
1412
|
}
|
|
1359
1413
|
}
|
|
1360
1414
|
async verifyEmail(request) {
|
|
@@ -1370,10 +1424,7 @@ var Auth = class {
|
|
|
1370
1424
|
}
|
|
1371
1425
|
return { data: response, error: null };
|
|
1372
1426
|
} catch (error) {
|
|
1373
|
-
return wrapError(
|
|
1374
|
-
error,
|
|
1375
|
-
"An unexpected error occurred while verifying email"
|
|
1376
|
-
);
|
|
1427
|
+
return wrapError(error, "An unexpected error occurred while verifying email");
|
|
1377
1428
|
}
|
|
1378
1429
|
}
|
|
1379
1430
|
// ============================================================================
|
|
@@ -1386,10 +1437,7 @@ var Auth = class {
|
|
|
1386
1437
|
});
|
|
1387
1438
|
return { data: response, error: null };
|
|
1388
1439
|
} catch (error) {
|
|
1389
|
-
return wrapError(
|
|
1390
|
-
error,
|
|
1391
|
-
"An unexpected error occurred while sending password reset email"
|
|
1392
|
-
);
|
|
1440
|
+
return wrapError(error, "An unexpected error occurred while sending password reset email");
|
|
1393
1441
|
}
|
|
1394
1442
|
}
|
|
1395
1443
|
async exchangeResetPasswordToken(request) {
|
|
@@ -1401,10 +1449,7 @@ var Auth = class {
|
|
|
1401
1449
|
);
|
|
1402
1450
|
return { data: response, error: null };
|
|
1403
1451
|
} catch (error) {
|
|
1404
|
-
return wrapError(
|
|
1405
|
-
error,
|
|
1406
|
-
"An unexpected error occurred while verifying reset code"
|
|
1407
|
-
);
|
|
1452
|
+
return wrapError(error, "An unexpected error occurred while verifying reset code");
|
|
1408
1453
|
}
|
|
1409
1454
|
}
|
|
1410
1455
|
async resetPassword(request) {
|
|
@@ -1416,10 +1461,7 @@ var Auth = class {
|
|
|
1416
1461
|
);
|
|
1417
1462
|
return { data: response, error: null };
|
|
1418
1463
|
} catch (error) {
|
|
1419
|
-
return wrapError(
|
|
1420
|
-
error,
|
|
1421
|
-
"An unexpected error occurred while resetting password"
|
|
1422
|
-
);
|
|
1464
|
+
return wrapError(error, "An unexpected error occurred while resetting password");
|
|
1423
1465
|
}
|
|
1424
1466
|
}
|
|
1425
1467
|
// ============================================================================
|
|
@@ -1427,16 +1469,12 @@ var Auth = class {
|
|
|
1427
1469
|
// ============================================================================
|
|
1428
1470
|
async getPublicAuthConfig() {
|
|
1429
1471
|
try {
|
|
1430
|
-
const response = await this.http.get(
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
);
|
|
1472
|
+
const response = await this.http.get("/api/auth/public-config", {
|
|
1473
|
+
skipAuthRefresh: true
|
|
1474
|
+
});
|
|
1434
1475
|
return { data: response, error: null };
|
|
1435
1476
|
} catch (error) {
|
|
1436
|
-
return wrapError(
|
|
1437
|
-
error,
|
|
1438
|
-
"An unexpected error occurred while fetching auth configuration"
|
|
1439
|
-
);
|
|
1477
|
+
return wrapError(error, "An unexpected error occurred while fetching auth configuration");
|
|
1440
1478
|
}
|
|
1441
1479
|
}
|
|
1442
1480
|
};
|
|
@@ -1463,12 +1501,30 @@ function createInsForgePostgrestFetch(httpClient) {
|
|
|
1463
1501
|
};
|
|
1464
1502
|
}
|
|
1465
1503
|
var Database = class {
|
|
1466
|
-
constructor(httpClient) {
|
|
1504
|
+
constructor(httpClient, defaultSchema) {
|
|
1467
1505
|
this.postgrest = new import_postgrest_js.PostgrestClient("http://dummy", {
|
|
1468
1506
|
fetch: createInsForgePostgrestFetch(httpClient),
|
|
1469
|
-
headers: {}
|
|
1507
|
+
headers: {},
|
|
1508
|
+
...defaultSchema ? { schema: defaultSchema } : {}
|
|
1470
1509
|
});
|
|
1471
1510
|
}
|
|
1511
|
+
/**
|
|
1512
|
+
* Select a non-default Postgres schema for the chained query. Maps to
|
|
1513
|
+
* PostgREST's `Accept-Profile` (reads) / `Content-Profile` (writes) header.
|
|
1514
|
+
* The schema must be exposed by the backend.
|
|
1515
|
+
*
|
|
1516
|
+
* @example
|
|
1517
|
+
* const { data } = await client.database
|
|
1518
|
+
* .schema('analytics')
|
|
1519
|
+
* .from('events')
|
|
1520
|
+
* .select('*');
|
|
1521
|
+
*
|
|
1522
|
+
* @example
|
|
1523
|
+
* await client.database.schema('analytics').rpc('rollup', { day: '2026-01-01' });
|
|
1524
|
+
*/
|
|
1525
|
+
schema(schemaName) {
|
|
1526
|
+
return this.postgrest.schema(schemaName);
|
|
1527
|
+
}
|
|
1472
1528
|
/**
|
|
1473
1529
|
* Create a query builder for a table
|
|
1474
1530
|
*
|
|
@@ -1576,11 +1632,7 @@ var StorageBucket = class {
|
|
|
1576
1632
|
} catch (error) {
|
|
1577
1633
|
return {
|
|
1578
1634
|
data: null,
|
|
1579
|
-
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
1580
|
-
"Upload failed",
|
|
1581
|
-
500,
|
|
1582
|
-
"STORAGE_ERROR"
|
|
1583
|
-
)
|
|
1635
|
+
error: error instanceof InsForgeError ? error : new InsForgeError("Upload failed", 500, "STORAGE_ERROR")
|
|
1584
1636
|
};
|
|
1585
1637
|
}
|
|
1586
1638
|
}
|
|
@@ -1626,11 +1678,7 @@ var StorageBucket = class {
|
|
|
1626
1678
|
} catch (error) {
|
|
1627
1679
|
return {
|
|
1628
1680
|
data: null,
|
|
1629
|
-
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
1630
|
-
"Upload failed",
|
|
1631
|
-
500,
|
|
1632
|
-
"STORAGE_ERROR"
|
|
1633
|
-
)
|
|
1681
|
+
error: error instanceof InsForgeError ? error : new InsForgeError("Upload failed", 500, "STORAGE_ERROR")
|
|
1634
1682
|
};
|
|
1635
1683
|
}
|
|
1636
1684
|
}
|
|
@@ -1658,13 +1706,10 @@ var StorageBucket = class {
|
|
|
1658
1706
|
);
|
|
1659
1707
|
}
|
|
1660
1708
|
if (strategy.confirmRequired && strategy.confirmUrl) {
|
|
1661
|
-
const confirmResponse = await this.http.post(
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
contentType: file.type || "application/octet-stream"
|
|
1666
|
-
}
|
|
1667
|
-
);
|
|
1709
|
+
const confirmResponse = await this.http.post(strategy.confirmUrl, {
|
|
1710
|
+
size: file.size,
|
|
1711
|
+
contentType: file.type || "application/octet-stream"
|
|
1712
|
+
});
|
|
1668
1713
|
return { data: confirmResponse, error: null };
|
|
1669
1714
|
}
|
|
1670
1715
|
return {
|
|
@@ -1679,11 +1724,7 @@ var StorageBucket = class {
|
|
|
1679
1724
|
error: null
|
|
1680
1725
|
};
|
|
1681
1726
|
} catch (error) {
|
|
1682
|
-
throw error instanceof InsForgeError ? error : new InsForgeError(
|
|
1683
|
-
"Presigned upload failed",
|
|
1684
|
-
500,
|
|
1685
|
-
"STORAGE_ERROR"
|
|
1686
|
-
);
|
|
1727
|
+
throw error instanceof InsForgeError ? error : new InsForgeError("Presigned upload failed", 500, "STORAGE_ERROR");
|
|
1687
1728
|
}
|
|
1688
1729
|
}
|
|
1689
1730
|
/**
|
|
@@ -1737,11 +1778,7 @@ var StorageBucket = class {
|
|
|
1737
1778
|
} catch (error) {
|
|
1738
1779
|
return {
|
|
1739
1780
|
data: null,
|
|
1740
|
-
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
1741
|
-
"Download failed",
|
|
1742
|
-
500,
|
|
1743
|
-
"STORAGE_ERROR"
|
|
1744
|
-
)
|
|
1781
|
+
error: error instanceof InsForgeError ? error : new InsForgeError("Download failed", 500, "STORAGE_ERROR")
|
|
1745
1782
|
};
|
|
1746
1783
|
}
|
|
1747
1784
|
}
|
|
@@ -1776,7 +1813,9 @@ var StorageBucket = class {
|
|
|
1776
1813
|
} catch (error) {
|
|
1777
1814
|
const status = error instanceof InsForgeError ? error.statusCode : void 0;
|
|
1778
1815
|
const isMissingRoute = (status === 404 || status === 405) && !(error instanceof InsForgeError && error.error === "STORAGE_NOT_FOUND");
|
|
1779
|
-
if (!isMissingRoute)
|
|
1816
|
+
if (!isMissingRoute) {
|
|
1817
|
+
throw error;
|
|
1818
|
+
}
|
|
1780
1819
|
return await this.http.post(
|
|
1781
1820
|
`/api/storage/buckets/${this.bucketName}/objects/${encoded}/download-strategy`,
|
|
1782
1821
|
{ expiresIn }
|
|
@@ -1852,10 +1891,18 @@ var StorageBucket = class {
|
|
|
1852
1891
|
async list(options) {
|
|
1853
1892
|
try {
|
|
1854
1893
|
const params = {};
|
|
1855
|
-
if (options?.prefix)
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
if (options?.
|
|
1894
|
+
if (options?.prefix) {
|
|
1895
|
+
params.prefix = options.prefix;
|
|
1896
|
+
}
|
|
1897
|
+
if (options?.search) {
|
|
1898
|
+
params.search = options.search;
|
|
1899
|
+
}
|
|
1900
|
+
if (options?.limit) {
|
|
1901
|
+
params.limit = options.limit.toString();
|
|
1902
|
+
}
|
|
1903
|
+
if (options?.offset) {
|
|
1904
|
+
params.offset = options.offset.toString();
|
|
1905
|
+
}
|
|
1859
1906
|
const response = await this.http.get(
|
|
1860
1907
|
`/api/storage/buckets/${this.bucketName}/objects`,
|
|
1861
1908
|
{ params }
|
|
@@ -1864,11 +1911,7 @@ var StorageBucket = class {
|
|
|
1864
1911
|
} catch (error) {
|
|
1865
1912
|
return {
|
|
1866
1913
|
data: null,
|
|
1867
|
-
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
1868
|
-
"List failed",
|
|
1869
|
-
500,
|
|
1870
|
-
"STORAGE_ERROR"
|
|
1871
|
-
)
|
|
1914
|
+
error: error instanceof InsForgeError ? error : new InsForgeError("List failed", 500, "STORAGE_ERROR")
|
|
1872
1915
|
};
|
|
1873
1916
|
}
|
|
1874
1917
|
}
|
|
@@ -1885,11 +1928,7 @@ var StorageBucket = class {
|
|
|
1885
1928
|
} catch (error) {
|
|
1886
1929
|
return {
|
|
1887
1930
|
data: null,
|
|
1888
|
-
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
1889
|
-
"Delete failed",
|
|
1890
|
-
500,
|
|
1891
|
-
"STORAGE_ERROR"
|
|
1892
|
-
)
|
|
1931
|
+
error: error instanceof InsForgeError ? error : new InsForgeError("Delete failed", 500, "STORAGE_ERROR")
|
|
1893
1932
|
};
|
|
1894
1933
|
}
|
|
1895
1934
|
}
|
|
@@ -2011,14 +2050,11 @@ var ChatCompletions = class {
|
|
|
2011
2050
|
if (params.stream) {
|
|
2012
2051
|
const headers = this.http.getHeaders();
|
|
2013
2052
|
headers["Content-Type"] = "application/json";
|
|
2014
|
-
const response2 = await this.http.fetch(
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
body: JSON.stringify(backendParams)
|
|
2020
|
-
}
|
|
2021
|
-
);
|
|
2053
|
+
const response2 = await this.http.fetch(`${this.http.baseUrl}/api/ai/chat/completion`, {
|
|
2054
|
+
method: "POST",
|
|
2055
|
+
headers,
|
|
2056
|
+
body: JSON.stringify(backendParams)
|
|
2057
|
+
});
|
|
2022
2058
|
if (!response2.ok) {
|
|
2023
2059
|
const error = await response2.json();
|
|
2024
2060
|
throw new Error(error.error || "Stream request failed");
|
|
@@ -2066,7 +2102,9 @@ var ChatCompletions = class {
|
|
|
2066
2102
|
try {
|
|
2067
2103
|
while (true) {
|
|
2068
2104
|
const { done, value } = await reader.read();
|
|
2069
|
-
if (done)
|
|
2105
|
+
if (done) {
|
|
2106
|
+
break;
|
|
2107
|
+
}
|
|
2070
2108
|
buffer += decoder.decode(value, { stream: true });
|
|
2071
2109
|
const lines = buffer.split("\n");
|
|
2072
2110
|
buffer = lines.pop() || "";
|
|
@@ -2114,7 +2152,7 @@ var ChatCompletions = class {
|
|
|
2114
2152
|
reader.releaseLock();
|
|
2115
2153
|
return;
|
|
2116
2154
|
}
|
|
2117
|
-
} catch
|
|
2155
|
+
} catch {
|
|
2118
2156
|
console.warn("Failed to parse SSE data:", dataStr);
|
|
2119
2157
|
}
|
|
2120
2158
|
}
|
|
@@ -2167,10 +2205,7 @@ var Embeddings = class {
|
|
|
2167
2205
|
* ```
|
|
2168
2206
|
*/
|
|
2169
2207
|
async create(params) {
|
|
2170
|
-
const response = await this.http.post(
|
|
2171
|
-
"/api/ai/embeddings",
|
|
2172
|
-
params
|
|
2173
|
-
);
|
|
2208
|
+
const response = await this.http.post("/api/ai/embeddings", params);
|
|
2174
2209
|
return {
|
|
2175
2210
|
object: response.object,
|
|
2176
2211
|
data: response.data,
|
|
@@ -2256,7 +2291,9 @@ var Functions = class _Functions {
|
|
|
2256
2291
|
static deriveSubhostingUrl(baseUrl) {
|
|
2257
2292
|
try {
|
|
2258
2293
|
const { hostname } = new URL(baseUrl);
|
|
2259
|
-
if (!hostname.endsWith(".insforge.app"))
|
|
2294
|
+
if (!hostname.endsWith(".insforge.app")) {
|
|
2295
|
+
return void 0;
|
|
2296
|
+
}
|
|
2260
2297
|
const appKey = hostname.split(".")[0];
|
|
2261
2298
|
return `https://${appKey}.functions.insforge.app`;
|
|
2262
2299
|
} catch {
|
|
@@ -2302,7 +2339,9 @@ var Functions = class _Functions {
|
|
|
2302
2339
|
const data = await parseResponse(res);
|
|
2303
2340
|
return { data, error: null };
|
|
2304
2341
|
} catch (error) {
|
|
2305
|
-
if (error instanceof Error && error.name === "AbortError")
|
|
2342
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2343
|
+
throw error;
|
|
2344
|
+
}
|
|
2306
2345
|
return {
|
|
2307
2346
|
data: null,
|
|
2308
2347
|
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
@@ -2321,7 +2360,9 @@ var Functions = class _Functions {
|
|
|
2321
2360
|
});
|
|
2322
2361
|
return { data, error: null };
|
|
2323
2362
|
} catch (error) {
|
|
2324
|
-
if (error instanceof Error && error.name === "AbortError")
|
|
2363
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2364
|
+
throw error;
|
|
2365
|
+
}
|
|
2325
2366
|
if (error instanceof InsForgeError && error.statusCode === 404) {
|
|
2326
2367
|
} else {
|
|
2327
2368
|
return {
|
|
@@ -2340,7 +2381,9 @@ var Functions = class _Functions {
|
|
|
2340
2381
|
const data = await this.http.request(method, path, { body, headers });
|
|
2341
2382
|
return { data, error: null };
|
|
2342
2383
|
} catch (error) {
|
|
2343
|
-
if (error instanceof Error && error.name === "AbortError")
|
|
2384
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2385
|
+
throw error;
|
|
2386
|
+
}
|
|
2344
2387
|
return {
|
|
2345
2388
|
data: null,
|
|
2346
2389
|
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
@@ -2355,32 +2398,37 @@ var Functions = class _Functions {
|
|
|
2355
2398
|
|
|
2356
2399
|
// src/modules/realtime.ts
|
|
2357
2400
|
var CONNECT_TIMEOUT = 1e4;
|
|
2401
|
+
var SUBSCRIBE_TIMEOUT = 1e4;
|
|
2358
2402
|
var Realtime = class {
|
|
2359
|
-
constructor(baseUrl, tokenManager, anonKey) {
|
|
2360
|
-
this.socket = null;
|
|
2361
|
-
this.connectPromise = null;
|
|
2362
|
-
this.subscribedChannels = /* @__PURE__ */ new Set();
|
|
2363
|
-
this.eventListeners = /* @__PURE__ */ new Map();
|
|
2403
|
+
constructor(baseUrl, tokenManager, anonKey, getValidAccessToken = async () => tokenManager.getAccessToken()) {
|
|
2364
2404
|
this.baseUrl = baseUrl;
|
|
2365
2405
|
this.tokenManager = tokenManager;
|
|
2366
2406
|
this.anonKey = anonKey;
|
|
2367
|
-
this.
|
|
2407
|
+
this.getValidAccessToken = getValidAccessToken;
|
|
2408
|
+
this.socket = null;
|
|
2409
|
+
this.connectPromise = null;
|
|
2410
|
+
this.connectionAttempt = null;
|
|
2411
|
+
this.nextConnectionAttemptId = 0;
|
|
2412
|
+
this.subscriptions = /* @__PURE__ */ new Map();
|
|
2413
|
+
this.eventListeners = /* @__PURE__ */ new Map();
|
|
2414
|
+
this.tokenManager.onAuthStateChange((event) => {
|
|
2415
|
+
if (event !== AuthChangeEvent.TOKEN_REFRESHED) {
|
|
2416
|
+
this.reconnectForAuthChange();
|
|
2417
|
+
}
|
|
2418
|
+
});
|
|
2368
2419
|
}
|
|
2369
2420
|
notifyListeners(event, payload) {
|
|
2370
|
-
const
|
|
2371
|
-
if (!listeners) return;
|
|
2372
|
-
for (const cb of listeners) {
|
|
2421
|
+
for (const callback of this.eventListeners.get(event) ?? []) {
|
|
2373
2422
|
try {
|
|
2374
|
-
|
|
2375
|
-
} catch (
|
|
2376
|
-
console.error(`Error in ${event} callback:`,
|
|
2423
|
+
callback(payload);
|
|
2424
|
+
} catch (error) {
|
|
2425
|
+
console.error(`Error in ${event} callback:`, error);
|
|
2377
2426
|
}
|
|
2378
2427
|
}
|
|
2379
2428
|
}
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
*/
|
|
2429
|
+
async getHandshakeToken() {
|
|
2430
|
+
return await this.getValidAccessToken() ?? this.anonKey ?? null;
|
|
2431
|
+
}
|
|
2384
2432
|
connect() {
|
|
2385
2433
|
if (this.socket?.connected) {
|
|
2386
2434
|
return Promise.resolve();
|
|
@@ -2388,210 +2436,324 @@ var Realtime = class {
|
|
|
2388
2436
|
if (this.connectPromise) {
|
|
2389
2437
|
return this.connectPromise;
|
|
2390
2438
|
}
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
}
|
|
2407
|
-
};
|
|
2408
|
-
timeoutId = setTimeout(() => {
|
|
2409
|
-
if (initialConnection) {
|
|
2410
|
-
initialConnection = false;
|
|
2411
|
-
this.connectPromise = null;
|
|
2412
|
-
this.socket?.disconnect();
|
|
2413
|
-
this.socket = null;
|
|
2414
|
-
reject(new Error(`Connection timeout after ${CONNECT_TIMEOUT}ms`));
|
|
2415
|
-
}
|
|
2416
|
-
}, CONNECT_TIMEOUT);
|
|
2417
|
-
this.socket.on("connect", () => {
|
|
2418
|
-
cleanup();
|
|
2419
|
-
for (const channel of this.subscribedChannels) {
|
|
2420
|
-
this.socket.emit("realtime:subscribe", { channel });
|
|
2421
|
-
}
|
|
2422
|
-
this.notifyListeners("connect");
|
|
2423
|
-
if (initialConnection) {
|
|
2424
|
-
initialConnection = false;
|
|
2425
|
-
this.connectPromise = null;
|
|
2426
|
-
resolve();
|
|
2427
|
-
}
|
|
2428
|
-
});
|
|
2429
|
-
this.socket.on("connect_error", (error) => {
|
|
2430
|
-
cleanup();
|
|
2431
|
-
this.notifyListeners("connect_error", error);
|
|
2432
|
-
if (initialConnection) {
|
|
2433
|
-
initialConnection = false;
|
|
2434
|
-
this.connectPromise = null;
|
|
2435
|
-
reject(error);
|
|
2436
|
-
}
|
|
2437
|
-
});
|
|
2438
|
-
this.socket.on("disconnect", (reason) => {
|
|
2439
|
-
this.notifyListeners("disconnect", reason);
|
|
2440
|
-
});
|
|
2441
|
-
this.socket.on("realtime:error", (error) => {
|
|
2442
|
-
this.notifyListeners("error", error);
|
|
2443
|
-
});
|
|
2444
|
-
this.socket.onAny((event, message) => {
|
|
2445
|
-
if (event === "realtime:error") return;
|
|
2446
|
-
this.notifyListeners(event, message);
|
|
2447
|
-
});
|
|
2439
|
+
const attemptId = ++this.nextConnectionAttemptId;
|
|
2440
|
+
const connection = (async () => {
|
|
2441
|
+
const { io } = await import("socket.io-client");
|
|
2442
|
+
if (attemptId !== this.nextConnectionAttemptId) {
|
|
2443
|
+
throw new Error("Connection cancelled");
|
|
2444
|
+
}
|
|
2445
|
+
await new Promise((resolve, reject) => {
|
|
2446
|
+
const socket = io(this.baseUrl, {
|
|
2447
|
+
transports: ["websocket"],
|
|
2448
|
+
auth: (callback) => {
|
|
2449
|
+
void this.getHandshakeToken().then(
|
|
2450
|
+
(token) => callback(token ? { token } : {}),
|
|
2451
|
+
() => callback({})
|
|
2452
|
+
);
|
|
2453
|
+
}
|
|
2448
2454
|
});
|
|
2449
|
-
|
|
2455
|
+
this.socket = socket;
|
|
2456
|
+
let initialConnection = true;
|
|
2457
|
+
let timeoutId = null;
|
|
2458
|
+
const clearConnectTimeout = () => {
|
|
2459
|
+
if (timeoutId) {
|
|
2460
|
+
clearTimeout(timeoutId);
|
|
2461
|
+
timeoutId = null;
|
|
2462
|
+
}
|
|
2463
|
+
};
|
|
2464
|
+
const dispose = () => {
|
|
2465
|
+
clearConnectTimeout();
|
|
2466
|
+
socket.off("connect", onConnect);
|
|
2467
|
+
socket.off("connect_error", onConnectError);
|
|
2468
|
+
socket.off("disconnect", onDisconnect);
|
|
2469
|
+
socket.off("realtime:error", onRealtimeError);
|
|
2470
|
+
socket.offAny(onAny);
|
|
2471
|
+
socket.disconnect();
|
|
2472
|
+
if (this.socket === socket) {
|
|
2473
|
+
this.socket = null;
|
|
2474
|
+
}
|
|
2475
|
+
if (this.connectionAttempt?.id === attemptId) {
|
|
2476
|
+
this.connectionAttempt = null;
|
|
2477
|
+
}
|
|
2478
|
+
};
|
|
2479
|
+
const fail = (error) => {
|
|
2480
|
+
if (!initialConnection) {
|
|
2481
|
+
return;
|
|
2482
|
+
}
|
|
2483
|
+
initialConnection = false;
|
|
2484
|
+
dispose();
|
|
2485
|
+
reject(error);
|
|
2486
|
+
};
|
|
2487
|
+
const onConnect = () => {
|
|
2488
|
+
if (this.socket !== socket) {
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
clearConnectTimeout();
|
|
2492
|
+
this.resubscribeChannels();
|
|
2493
|
+
this.notifyListeners("connect");
|
|
2494
|
+
if (initialConnection) {
|
|
2495
|
+
initialConnection = false;
|
|
2496
|
+
if (this.connectionAttempt?.id === attemptId) {
|
|
2497
|
+
this.connectionAttempt = null;
|
|
2498
|
+
}
|
|
2499
|
+
resolve();
|
|
2500
|
+
}
|
|
2501
|
+
};
|
|
2502
|
+
const onConnectError = (error) => {
|
|
2503
|
+
clearConnectTimeout();
|
|
2504
|
+
this.notifyListeners("connect_error", error);
|
|
2505
|
+
if (initialConnection) {
|
|
2506
|
+
fail(error);
|
|
2507
|
+
}
|
|
2508
|
+
};
|
|
2509
|
+
const onDisconnect = (reason) => {
|
|
2510
|
+
this.handleDisconnect(reason);
|
|
2511
|
+
};
|
|
2512
|
+
const onRealtimeError = (error) => {
|
|
2513
|
+
this.notifyListeners("error", error);
|
|
2514
|
+
};
|
|
2515
|
+
const onAny = (event, message) => {
|
|
2516
|
+
if (event === "realtime:error") {
|
|
2517
|
+
return;
|
|
2518
|
+
}
|
|
2519
|
+
this.applyPresenceEvent(event, message);
|
|
2520
|
+
this.notifyListeners(event, message);
|
|
2521
|
+
};
|
|
2522
|
+
this.connectionAttempt = { id: attemptId, socket, cancel: fail };
|
|
2523
|
+
socket.on("connect", onConnect);
|
|
2524
|
+
socket.on("connect_error", onConnectError);
|
|
2525
|
+
socket.on("disconnect", onDisconnect);
|
|
2526
|
+
socket.on("realtime:error", onRealtimeError);
|
|
2527
|
+
socket.onAny(onAny);
|
|
2528
|
+
timeoutId = setTimeout(
|
|
2529
|
+
() => fail(new Error(`Connection timeout after ${CONNECT_TIMEOUT}ms`)),
|
|
2530
|
+
CONNECT_TIMEOUT
|
|
2531
|
+
);
|
|
2532
|
+
});
|
|
2533
|
+
})();
|
|
2534
|
+
const trackedConnection = connection.finally(() => {
|
|
2535
|
+
if (this.connectPromise === trackedConnection) {
|
|
2450
2536
|
this.connectPromise = null;
|
|
2451
|
-
throw error;
|
|
2452
2537
|
}
|
|
2453
|
-
})
|
|
2454
|
-
|
|
2538
|
+
});
|
|
2539
|
+
this.connectPromise = trackedConnection;
|
|
2540
|
+
return trackedConnection;
|
|
2455
2541
|
}
|
|
2456
|
-
/**
|
|
2457
|
-
* Disconnect from the realtime server
|
|
2458
|
-
*/
|
|
2459
2542
|
disconnect() {
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2543
|
+
this.nextConnectionAttemptId++;
|
|
2544
|
+
this.connectionAttempt?.cancel(new Error("Disconnected"));
|
|
2545
|
+
this.socket?.disconnect();
|
|
2546
|
+
this.socket = null;
|
|
2547
|
+
this.connectPromise = null;
|
|
2548
|
+
for (const subscription of this.subscriptions.values()) {
|
|
2549
|
+
this.settleSubscription(
|
|
2550
|
+
subscription,
|
|
2551
|
+
{
|
|
2552
|
+
ok: false,
|
|
2553
|
+
channel: subscription.channel,
|
|
2554
|
+
error: { code: "DISCONNECTED", message: "Disconnected" }
|
|
2555
|
+
},
|
|
2556
|
+
false
|
|
2557
|
+
);
|
|
2463
2558
|
}
|
|
2464
|
-
this.
|
|
2559
|
+
this.subscriptions.clear();
|
|
2465
2560
|
}
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
onTokenChange() {
|
|
2472
|
-
const token = this.tokenManager.getAccessToken() ?? this.anonKey;
|
|
2473
|
-
if (this.socket) {
|
|
2474
|
-
this.socket.auth = token ? { token } : {};
|
|
2561
|
+
reconnectForAuthChange() {
|
|
2562
|
+
for (const subscription of this.subscriptions.values()) {
|
|
2563
|
+
if (subscription.status === "rejected") {
|
|
2564
|
+
subscription.status = "pending";
|
|
2565
|
+
}
|
|
2475
2566
|
}
|
|
2476
|
-
if (this.socket
|
|
2477
|
-
|
|
2478
|
-
|
|
2567
|
+
if (!this.socket) {
|
|
2568
|
+
return;
|
|
2569
|
+
}
|
|
2570
|
+
this.socket.disconnect();
|
|
2571
|
+
this.socket.connect();
|
|
2572
|
+
}
|
|
2573
|
+
handleDisconnect(reason) {
|
|
2574
|
+
for (const subscription of this.subscriptions.values()) {
|
|
2575
|
+
if (subscription.status === "rejected") {
|
|
2576
|
+
continue;
|
|
2577
|
+
}
|
|
2578
|
+
subscription.status = "pending";
|
|
2579
|
+
this.settleSubscription(
|
|
2580
|
+
subscription,
|
|
2581
|
+
{
|
|
2582
|
+
ok: false,
|
|
2583
|
+
channel: subscription.channel,
|
|
2584
|
+
error: { code: "DISCONNECTED", message: "Connection lost before subscription completed" }
|
|
2585
|
+
},
|
|
2586
|
+
true
|
|
2587
|
+
);
|
|
2588
|
+
}
|
|
2589
|
+
this.notifyListeners("disconnect", reason);
|
|
2590
|
+
}
|
|
2591
|
+
resubscribeChannels() {
|
|
2592
|
+
for (const [channel, subscription] of this.subscriptions) {
|
|
2593
|
+
if (subscription.status === "pending") {
|
|
2594
|
+
this.requestSubscription(channel, subscription);
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
requestSubscription(channel, subscription) {
|
|
2599
|
+
if (subscription.pending) {
|
|
2600
|
+
return subscription.pending;
|
|
2601
|
+
}
|
|
2602
|
+
const socket = this.socket;
|
|
2603
|
+
if (!socket?.connected) {
|
|
2604
|
+
return Promise.resolve({
|
|
2605
|
+
ok: false,
|
|
2606
|
+
channel,
|
|
2607
|
+
error: { code: "CONNECTION_FAILED", message: "Not connected to realtime server" }
|
|
2608
|
+
});
|
|
2609
|
+
}
|
|
2610
|
+
subscription.status = "pending";
|
|
2611
|
+
const epoch = ++subscription.epoch;
|
|
2612
|
+
let timeoutId;
|
|
2613
|
+
subscription.pending = new Promise((resolve) => {
|
|
2614
|
+
subscription.settlePending = (response) => {
|
|
2615
|
+
if (timeoutId) {
|
|
2616
|
+
clearTimeout(timeoutId);
|
|
2617
|
+
}
|
|
2618
|
+
subscription.pending = void 0;
|
|
2619
|
+
subscription.settlePending = void 0;
|
|
2620
|
+
resolve(response);
|
|
2621
|
+
};
|
|
2622
|
+
timeoutId = setTimeout(() => {
|
|
2623
|
+
if (this.subscriptions.get(channel) === subscription && subscription.epoch === epoch) {
|
|
2624
|
+
this.settleSubscription(
|
|
2625
|
+
subscription,
|
|
2626
|
+
{
|
|
2627
|
+
ok: false,
|
|
2628
|
+
channel,
|
|
2629
|
+
error: {
|
|
2630
|
+
code: "SUBSCRIBE_TIMEOUT",
|
|
2631
|
+
message: "Subscription acknowledgement timed out"
|
|
2632
|
+
}
|
|
2633
|
+
},
|
|
2634
|
+
true
|
|
2635
|
+
);
|
|
2636
|
+
}
|
|
2637
|
+
}, SUBSCRIBE_TIMEOUT);
|
|
2638
|
+
socket.emit("realtime:subscribe", { channel }, (response) => {
|
|
2639
|
+
if (this.subscriptions.get(channel) !== subscription || subscription.epoch !== epoch) {
|
|
2640
|
+
return;
|
|
2641
|
+
}
|
|
2642
|
+
if (response.ok) {
|
|
2643
|
+
subscription.status = "subscribed";
|
|
2644
|
+
subscription.members = new Map(
|
|
2645
|
+
response.presence.members.map((member) => [member.presenceId, member])
|
|
2646
|
+
);
|
|
2647
|
+
} else {
|
|
2648
|
+
subscription.status = "rejected";
|
|
2649
|
+
subscription.members.clear();
|
|
2650
|
+
}
|
|
2651
|
+
this.settleSubscription(subscription, response, false);
|
|
2652
|
+
});
|
|
2653
|
+
});
|
|
2654
|
+
return subscription.pending;
|
|
2655
|
+
}
|
|
2656
|
+
settleSubscription(subscription, response, incrementEpoch) {
|
|
2657
|
+
if (incrementEpoch) {
|
|
2658
|
+
subscription.epoch++;
|
|
2659
|
+
}
|
|
2660
|
+
subscription.settlePending?.(response);
|
|
2661
|
+
}
|
|
2662
|
+
applyPresenceEvent(event, message) {
|
|
2663
|
+
if (event !== "presence:join" && event !== "presence:leave") {
|
|
2664
|
+
return;
|
|
2665
|
+
}
|
|
2666
|
+
const presenceEvent = message;
|
|
2667
|
+
const channel = presenceEvent.meta?.channel;
|
|
2668
|
+
const member = presenceEvent.member;
|
|
2669
|
+
if (!channel || !member) {
|
|
2670
|
+
return;
|
|
2671
|
+
}
|
|
2672
|
+
const subscription = this.subscriptions.get(channel);
|
|
2673
|
+
if (!subscription) {
|
|
2674
|
+
return;
|
|
2675
|
+
}
|
|
2676
|
+
if (event === "presence:join") {
|
|
2677
|
+
subscription.members.set(member.presenceId, member);
|
|
2678
|
+
} else {
|
|
2679
|
+
subscription.members.delete(member.presenceId);
|
|
2479
2680
|
}
|
|
2480
2681
|
}
|
|
2481
|
-
/**
|
|
2482
|
-
* Check if connected to the realtime server
|
|
2483
|
-
*/
|
|
2484
2682
|
get isConnected() {
|
|
2485
2683
|
return this.socket?.connected ?? false;
|
|
2486
2684
|
}
|
|
2487
|
-
/**
|
|
2488
|
-
* Get the current connection state
|
|
2489
|
-
*/
|
|
2490
2685
|
get connectionState() {
|
|
2491
|
-
if (!this.socket)
|
|
2492
|
-
|
|
2493
|
-
|
|
2686
|
+
if (!this.socket) {
|
|
2687
|
+
return "disconnected";
|
|
2688
|
+
}
|
|
2689
|
+
return this.socket.connected ? "connected" : "connecting";
|
|
2494
2690
|
}
|
|
2495
|
-
/**
|
|
2496
|
-
* Get the socket ID (if connected)
|
|
2497
|
-
*/
|
|
2498
2691
|
get socketId() {
|
|
2499
2692
|
return this.socket?.id;
|
|
2500
2693
|
}
|
|
2501
|
-
/**
|
|
2502
|
-
* Subscribe to a channel
|
|
2503
|
-
*
|
|
2504
|
-
* Automatically connects if not already connected.
|
|
2505
|
-
*
|
|
2506
|
-
* @param channel - Channel name (e.g., 'orders:123', 'broadcast')
|
|
2507
|
-
* @returns Promise with the subscription response
|
|
2508
|
-
*/
|
|
2509
2694
|
async subscribe(channel) {
|
|
2510
|
-
|
|
2511
|
-
|
|
2695
|
+
let subscription = this.subscriptions.get(channel);
|
|
2696
|
+
if (subscription) {
|
|
2697
|
+
if (subscription.pending) {
|
|
2698
|
+
return subscription.pending;
|
|
2699
|
+
}
|
|
2700
|
+
if (subscription.status === "subscribed") {
|
|
2701
|
+
return { ok: true, channel, presence: { members: [...subscription.members.values()] } };
|
|
2702
|
+
}
|
|
2703
|
+
} else {
|
|
2704
|
+
subscription = { channel, epoch: 0, status: "pending", members: /* @__PURE__ */ new Map() };
|
|
2705
|
+
this.subscriptions.set(channel, subscription);
|
|
2512
2706
|
}
|
|
2513
2707
|
if (!this.socket?.connected) {
|
|
2514
2708
|
try {
|
|
2515
2709
|
await this.connect();
|
|
2516
2710
|
} catch (error) {
|
|
2711
|
+
if (this.subscriptions.get(channel) === subscription) {
|
|
2712
|
+
this.subscriptions.delete(channel);
|
|
2713
|
+
}
|
|
2517
2714
|
const message = error instanceof Error ? error.message : "Connection failed";
|
|
2518
2715
|
return { ok: false, channel, error: { code: "CONNECTION_FAILED", message } };
|
|
2519
2716
|
}
|
|
2520
2717
|
}
|
|
2521
|
-
return
|
|
2522
|
-
this.socket.emit("realtime:subscribe", { channel }, (response) => {
|
|
2523
|
-
if (response.ok) {
|
|
2524
|
-
this.subscribedChannels.add(channel);
|
|
2525
|
-
}
|
|
2526
|
-
resolve(response);
|
|
2527
|
-
});
|
|
2528
|
-
});
|
|
2718
|
+
return subscription.pending ?? this.requestSubscription(channel, subscription);
|
|
2529
2719
|
}
|
|
2530
|
-
/**
|
|
2531
|
-
* Unsubscribe from a channel (fire-and-forget)
|
|
2532
|
-
*
|
|
2533
|
-
* @param channel - Channel name to unsubscribe from
|
|
2534
|
-
*/
|
|
2535
2720
|
unsubscribe(channel) {
|
|
2536
|
-
this.
|
|
2721
|
+
const subscription = this.subscriptions.get(channel);
|
|
2722
|
+
if (!subscription) {
|
|
2723
|
+
return;
|
|
2724
|
+
}
|
|
2725
|
+
this.subscriptions.delete(channel);
|
|
2726
|
+
this.settleSubscription(
|
|
2727
|
+
subscription,
|
|
2728
|
+
{
|
|
2729
|
+
ok: false,
|
|
2730
|
+
channel,
|
|
2731
|
+
error: { code: "SUBSCRIPTION_CANCELLED", message: "Subscription cancelled" }
|
|
2732
|
+
},
|
|
2733
|
+
true
|
|
2734
|
+
);
|
|
2537
2735
|
if (this.socket?.connected) {
|
|
2538
2736
|
this.socket.emit("realtime:unsubscribe", { channel });
|
|
2539
2737
|
}
|
|
2540
2738
|
}
|
|
2541
|
-
/**
|
|
2542
|
-
* Publish a message to a channel
|
|
2543
|
-
*
|
|
2544
|
-
* @param channel - Channel name
|
|
2545
|
-
* @param event - Event name
|
|
2546
|
-
* @param payload - Message payload
|
|
2547
|
-
*/
|
|
2548
2739
|
async publish(channel, event, payload) {
|
|
2549
2740
|
if (!this.socket?.connected) {
|
|
2550
2741
|
throw new Error("Not connected to realtime server. Call connect() first.");
|
|
2551
2742
|
}
|
|
2552
2743
|
this.socket.emit("realtime:publish", { channel, event, payload });
|
|
2553
2744
|
}
|
|
2554
|
-
/**
|
|
2555
|
-
* Listen for events
|
|
2556
|
-
*
|
|
2557
|
-
* Reserved event names:
|
|
2558
|
-
* - 'connect' - Fired when connected to the server
|
|
2559
|
-
* - 'connect_error' - Fired when connection fails (payload: Error)
|
|
2560
|
-
* - 'disconnect' - Fired when disconnected (payload: reason string)
|
|
2561
|
-
* - 'error' - Fired when a realtime error occurs (payload: RealtimeErrorPayload)
|
|
2562
|
-
*
|
|
2563
|
-
* All other events receive a `SocketMessage` payload with metadata.
|
|
2564
|
-
*
|
|
2565
|
-
* @param event - Event name to listen for
|
|
2566
|
-
* @param callback - Callback function when event is received
|
|
2567
|
-
*/
|
|
2568
2745
|
on(event, callback) {
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
this.eventListeners.get(event).add(callback);
|
|
2746
|
+
const listeners = this.eventListeners.get(event) ?? /* @__PURE__ */ new Set();
|
|
2747
|
+
listeners.add(callback);
|
|
2748
|
+
this.eventListeners.set(event, listeners);
|
|
2573
2749
|
}
|
|
2574
|
-
/**
|
|
2575
|
-
* Remove a listener for a specific event
|
|
2576
|
-
*
|
|
2577
|
-
* @param event - Event name
|
|
2578
|
-
* @param callback - The callback function to remove
|
|
2579
|
-
*/
|
|
2580
2750
|
off(event, callback) {
|
|
2581
2751
|
const listeners = this.eventListeners.get(event);
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
this.eventListeners.delete(event);
|
|
2586
|
-
}
|
|
2752
|
+
listeners?.delete(callback);
|
|
2753
|
+
if (listeners?.size === 0) {
|
|
2754
|
+
this.eventListeners.delete(event);
|
|
2587
2755
|
}
|
|
2588
2756
|
}
|
|
2589
|
-
/**
|
|
2590
|
-
* Listen for an event only once, then automatically remove the listener
|
|
2591
|
-
*
|
|
2592
|
-
* @param event - Event name to listen for
|
|
2593
|
-
* @param callback - Callback function when event is received
|
|
2594
|
-
*/
|
|
2595
2757
|
once(event, callback) {
|
|
2596
2758
|
const wrapper = (payload) => {
|
|
2597
2759
|
this.off(event, wrapper);
|
|
@@ -2599,13 +2761,11 @@ var Realtime = class {
|
|
|
2599
2761
|
};
|
|
2600
2762
|
this.on(event, wrapper);
|
|
2601
2763
|
}
|
|
2602
|
-
/**
|
|
2603
|
-
* Get all currently subscribed channels
|
|
2604
|
-
*
|
|
2605
|
-
* @returns Array of channel names
|
|
2606
|
-
*/
|
|
2607
2764
|
getSubscribedChannels() {
|
|
2608
|
-
return
|
|
2765
|
+
return [...this.subscriptions.values()].filter((subscription) => subscription.status === "subscribed").map((subscription) => subscription.channel);
|
|
2766
|
+
}
|
|
2767
|
+
getPresenceState(channel) {
|
|
2768
|
+
return [...this.subscriptions.get(channel)?.members.values() ?? []];
|
|
2609
2769
|
}
|
|
2610
2770
|
};
|
|
2611
2771
|
|
|
@@ -2620,13 +2780,12 @@ var Emails = class {
|
|
|
2620
2780
|
*/
|
|
2621
2781
|
async send(options) {
|
|
2622
2782
|
try {
|
|
2623
|
-
const data = await this.http.post(
|
|
2624
|
-
"/api/email/send-raw",
|
|
2625
|
-
options
|
|
2626
|
-
);
|
|
2783
|
+
const data = await this.http.post("/api/email/send-raw", options);
|
|
2627
2784
|
return { data, error: null };
|
|
2628
2785
|
} catch (error) {
|
|
2629
|
-
if (error instanceof Error && error.name === "AbortError")
|
|
2786
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2787
|
+
throw error;
|
|
2788
|
+
}
|
|
2630
2789
|
return {
|
|
2631
2790
|
data: null,
|
|
2632
2791
|
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
@@ -2709,10 +2868,7 @@ var RazorpayPayments = class {
|
|
|
2709
2868
|
);
|
|
2710
2869
|
return { data, error: null };
|
|
2711
2870
|
} catch (error) {
|
|
2712
|
-
return wrapError(
|
|
2713
|
-
error,
|
|
2714
|
-
"Razorpay order creation failed"
|
|
2715
|
-
);
|
|
2871
|
+
return wrapError(error, "Razorpay order creation failed");
|
|
2716
2872
|
}
|
|
2717
2873
|
}
|
|
2718
2874
|
async verifyOrder(environment, request) {
|
|
@@ -2723,10 +2879,7 @@ var RazorpayPayments = class {
|
|
|
2723
2879
|
);
|
|
2724
2880
|
return { data, error: null };
|
|
2725
2881
|
} catch (error) {
|
|
2726
|
-
return wrapError(
|
|
2727
|
-
error,
|
|
2728
|
-
"Razorpay order verification failed"
|
|
2729
|
-
);
|
|
2882
|
+
return wrapError(error, "Razorpay order verification failed");
|
|
2730
2883
|
}
|
|
2731
2884
|
}
|
|
2732
2885
|
async createSubscription(environment, request) {
|
|
@@ -2828,14 +2981,15 @@ var InsForgeClient = class {
|
|
|
2828
2981
|
isServerMode: config.isServerMode ?? !!accessToken,
|
|
2829
2982
|
detectOAuthCallback: config.auth?.detectOAuthCallback
|
|
2830
2983
|
});
|
|
2831
|
-
this.database = new Database(this.http);
|
|
2984
|
+
this.database = new Database(this.http, config.db?.schema);
|
|
2832
2985
|
this.storage = new Storage(this.http);
|
|
2833
2986
|
this.ai = new AI(this.http);
|
|
2834
2987
|
this.functions = new Functions(this.http, config.functionsUrl);
|
|
2835
2988
|
this.realtime = new Realtime(
|
|
2836
2989
|
this.http.baseUrl,
|
|
2837
2990
|
this.tokenManager,
|
|
2838
|
-
config.anonKey
|
|
2991
|
+
config.anonKey,
|
|
2992
|
+
() => this.http.getValidAccessToken()
|
|
2839
2993
|
);
|
|
2840
2994
|
this.emails = new Emails(this.http);
|
|
2841
2995
|
this.payments = new Payments(this.http);
|
|
@@ -2855,32 +3009,35 @@ var InsForgeClient = class {
|
|
|
2855
3009
|
/**
|
|
2856
3010
|
* Set the access token used by every SDK surface. Updates both the HTTP
|
|
2857
3011
|
* client (database / storage / functions / AI / emails) and the realtime
|
|
2858
|
-
* token manager
|
|
2859
|
-
*
|
|
3012
|
+
* token manager. Pass `null` to sign out. By default a token replacement is
|
|
3013
|
+
* treated as a sign-in boundary and reconnects realtime. Pass
|
|
3014
|
+
* `AuthChangeEvent.TOKEN_REFRESHED` for a same-identity refresh to preserve a live socket; the
|
|
3015
|
+
* refreshed token is then used at the next handshake.
|
|
2860
3016
|
*
|
|
2861
3017
|
* Use this when an external auth provider (Better Auth, Clerk, Auth0,
|
|
2862
3018
|
* WorkOS, Kinde, Stytch, …) issues the JWT and you need to keep the
|
|
2863
3019
|
* long-lived InsForge client in sync. Without this, you'd have to call
|
|
2864
3020
|
* `client.getHttpClient().setAuthToken(token)` AND reach into the private
|
|
2865
|
-
*
|
|
2866
|
-
* forgetting the second one silently breaks realtime auth.
|
|
3021
|
+
* realtime token manager separately.
|
|
2867
3022
|
*
|
|
2868
3023
|
* @example
|
|
2869
3024
|
* ```typescript
|
|
3025
|
+
* import { AuthChangeEvent } from '@insforge/sdk';
|
|
3026
|
+
*
|
|
2870
3027
|
* // Refresh a third-party-issued JWT periodically
|
|
2871
3028
|
* const { token } = await fetch('/api/insforge-token').then((r) => r.json());
|
|
2872
|
-
* client.setAccessToken(token);
|
|
3029
|
+
* client.setAccessToken(token, AuthChangeEvent.TOKEN_REFRESHED);
|
|
2873
3030
|
*
|
|
2874
3031
|
* // Sign-out
|
|
2875
3032
|
* client.setAccessToken(null);
|
|
2876
3033
|
* ```
|
|
2877
3034
|
*/
|
|
2878
|
-
setAccessToken(token) {
|
|
3035
|
+
setAccessToken(token, event = AuthChangeEvent.SIGNED_IN) {
|
|
2879
3036
|
this.http.setAuthToken(token);
|
|
2880
3037
|
if (token === null) {
|
|
2881
3038
|
this.tokenManager.clearSession();
|
|
2882
3039
|
} else {
|
|
2883
|
-
this.tokenManager.setAccessToken(token);
|
|
3040
|
+
this.tokenManager.setAccessToken(token, event);
|
|
2884
3041
|
}
|
|
2885
3042
|
}
|
|
2886
3043
|
/**
|
|
@@ -2914,6 +3071,7 @@ var src_default = InsForgeClient;
|
|
|
2914
3071
|
0 && (module.exports = {
|
|
2915
3072
|
AI,
|
|
2916
3073
|
Auth,
|
|
3074
|
+
AuthChangeEvent,
|
|
2917
3075
|
Database,
|
|
2918
3076
|
Emails,
|
|
2919
3077
|
Functions,
|
|
@@ -2925,7 +3083,6 @@ var src_default = InsForgeClient;
|
|
|
2925
3083
|
Realtime,
|
|
2926
3084
|
Storage,
|
|
2927
3085
|
StorageBucket,
|
|
2928
|
-
TokenManager,
|
|
2929
3086
|
createAdminClient,
|
|
2930
3087
|
createClient
|
|
2931
3088
|
});
|