@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.mjs
CHANGED
|
@@ -45,7 +45,9 @@ function redactHeaders(headers) {
|
|
|
45
45
|
return redacted;
|
|
46
46
|
}
|
|
47
47
|
function sanitizeBody(body) {
|
|
48
|
-
if (body === null || body === void 0)
|
|
48
|
+
if (body === null || body === void 0) {
|
|
49
|
+
return body;
|
|
50
|
+
}
|
|
49
51
|
if (typeof body === "string") {
|
|
50
52
|
try {
|
|
51
53
|
const parsed = JSON.parse(body);
|
|
@@ -54,7 +56,9 @@ function sanitizeBody(body) {
|
|
|
54
56
|
return body;
|
|
55
57
|
}
|
|
56
58
|
}
|
|
57
|
-
if (Array.isArray(body))
|
|
59
|
+
if (Array.isArray(body)) {
|
|
60
|
+
return body.map(sanitizeBody);
|
|
61
|
+
}
|
|
58
62
|
if (typeof body === "object") {
|
|
59
63
|
const sanitized = {};
|
|
60
64
|
for (const [key, value] of Object.entries(body)) {
|
|
@@ -69,7 +73,9 @@ function sanitizeBody(body) {
|
|
|
69
73
|
return body;
|
|
70
74
|
}
|
|
71
75
|
function formatBody(body) {
|
|
72
|
-
if (body === void 0 || body === null)
|
|
76
|
+
if (body === void 0 || body === null) {
|
|
77
|
+
return "";
|
|
78
|
+
}
|
|
73
79
|
if (typeof body === "string") {
|
|
74
80
|
try {
|
|
75
81
|
return JSON.stringify(JSON.parse(body), null, 2);
|
|
@@ -106,7 +112,9 @@ var Logger = class {
|
|
|
106
112
|
* @param args - Additional arguments to pass to the log function
|
|
107
113
|
*/
|
|
108
114
|
log(message, ...args) {
|
|
109
|
-
if (!this.enabled)
|
|
115
|
+
if (!this.enabled) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
110
118
|
const formatted = `[InsForge Debug] ${message}`;
|
|
111
119
|
if (this.customLog) {
|
|
112
120
|
this.customLog(formatted, ...args);
|
|
@@ -120,7 +128,9 @@ var Logger = class {
|
|
|
120
128
|
* @param args - Additional arguments to pass to the log function
|
|
121
129
|
*/
|
|
122
130
|
warn(message, ...args) {
|
|
123
|
-
if (!this.enabled)
|
|
131
|
+
if (!this.enabled) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
124
134
|
const formatted = `[InsForge Debug] ${message}`;
|
|
125
135
|
if (this.customLog) {
|
|
126
136
|
this.customLog(formatted, ...args);
|
|
@@ -134,7 +144,9 @@ var Logger = class {
|
|
|
134
144
|
* @param args - Additional arguments to pass to the log function
|
|
135
145
|
*/
|
|
136
146
|
error(message, ...args) {
|
|
137
|
-
if (!this.enabled)
|
|
147
|
+
if (!this.enabled) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
138
150
|
const formatted = `[InsForge Debug] ${message}`;
|
|
139
151
|
if (this.customLog) {
|
|
140
152
|
this.customLog(formatted, ...args);
|
|
@@ -151,10 +163,10 @@ var Logger = class {
|
|
|
151
163
|
* @param body - Request body (sensitive fields will be masked)
|
|
152
164
|
*/
|
|
153
165
|
logRequest(method, url, headers, body) {
|
|
154
|
-
if (!this.enabled)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
];
|
|
166
|
+
if (!this.enabled) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const parts = [`\u2192 ${method} ${url}`];
|
|
158
170
|
if (headers && Object.keys(headers).length > 0) {
|
|
159
171
|
parts.push(` Headers: ${JSON.stringify(redactHeaders(headers))}`);
|
|
160
172
|
}
|
|
@@ -175,10 +187,10 @@ var Logger = class {
|
|
|
175
187
|
* @param body - Response body (sensitive fields will be masked, large bodies truncated)
|
|
176
188
|
*/
|
|
177
189
|
logResponse(method, url, status, durationMs, body) {
|
|
178
|
-
if (!this.enabled)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
];
|
|
190
|
+
if (!this.enabled) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const parts = [`\u2190 ${method} ${url} ${status} (${durationMs}ms)`];
|
|
182
194
|
const formattedBody = formatBody(sanitizeBody(body));
|
|
183
195
|
if (formattedBody) {
|
|
184
196
|
const truncated = formattedBody.length > 1e3 ? formattedBody.slice(0, 1e3) + "... [truncated]" : formattedBody;
|
|
@@ -193,21 +205,34 @@ var Logger = class {
|
|
|
193
205
|
};
|
|
194
206
|
|
|
195
207
|
// src/lib/token-manager.ts
|
|
208
|
+
var AuthChangeEvent = {
|
|
209
|
+
SIGNED_IN: "signedIn",
|
|
210
|
+
SIGNED_OUT: "signedOut",
|
|
211
|
+
TOKEN_REFRESHED: "tokenRefreshed"
|
|
212
|
+
};
|
|
196
213
|
var CSRF_TOKEN_COOKIE = "insforge_csrf_token";
|
|
197
214
|
function getCsrfToken() {
|
|
198
|
-
if (typeof document === "undefined")
|
|
215
|
+
if (typeof document === "undefined") {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
199
218
|
const match = document.cookie.split(";").find((c) => c.trim().startsWith(`${CSRF_TOKEN_COOKIE}=`));
|
|
200
|
-
if (!match)
|
|
219
|
+
if (!match) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
201
222
|
return match.split("=")[1] || null;
|
|
202
223
|
}
|
|
203
224
|
function setCsrfToken(token) {
|
|
204
|
-
if (typeof document === "undefined")
|
|
225
|
+
if (typeof document === "undefined") {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
205
228
|
const maxAge = 7 * 24 * 60 * 60;
|
|
206
229
|
const secure = typeof window !== "undefined" && window.location.protocol === "https:" ? "; Secure" : "";
|
|
207
230
|
document.cookie = `${CSRF_TOKEN_COOKIE}=${encodeURIComponent(token)}; path=/; max-age=${maxAge}; SameSite=Lax${secure}`;
|
|
208
231
|
}
|
|
209
232
|
function clearCsrfToken() {
|
|
210
|
-
if (typeof document === "undefined")
|
|
233
|
+
if (typeof document === "undefined") {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
211
236
|
const secure = typeof window !== "undefined" && window.location.protocol === "https:" ? "; Secure" : "";
|
|
212
237
|
document.cookie = `${CSRF_TOKEN_COOKIE}=; path=/; max-age=0; SameSite=Lax${secure}`;
|
|
213
238
|
}
|
|
@@ -216,25 +241,26 @@ var TokenManager = class {
|
|
|
216
241
|
// In-memory storage
|
|
217
242
|
this.accessToken = null;
|
|
218
243
|
this.user = null;
|
|
219
|
-
|
|
220
|
-
this.onTokenChange = null;
|
|
244
|
+
this.authStateChangeCallbacks = /* @__PURE__ */ new Map();
|
|
221
245
|
}
|
|
222
246
|
/**
|
|
223
247
|
* Save session in memory
|
|
224
248
|
*/
|
|
225
|
-
saveSession(session) {
|
|
249
|
+
saveSession(session, event = AuthChangeEvent.SIGNED_IN) {
|
|
226
250
|
const tokenChanged = session.accessToken !== this.accessToken;
|
|
227
251
|
this.accessToken = session.accessToken;
|
|
228
252
|
this.user = session.user;
|
|
229
|
-
if (tokenChanged
|
|
230
|
-
this.
|
|
253
|
+
if (tokenChanged) {
|
|
254
|
+
this.notifyAuthStateChange(event);
|
|
231
255
|
}
|
|
232
256
|
}
|
|
233
257
|
/**
|
|
234
258
|
* Get current session
|
|
235
259
|
*/
|
|
236
260
|
getSession() {
|
|
237
|
-
if (!this.accessToken || !this.user)
|
|
261
|
+
if (!this.accessToken || !this.user) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
238
264
|
return {
|
|
239
265
|
accessToken: this.accessToken,
|
|
240
266
|
user: this.user
|
|
@@ -249,11 +275,11 @@ var TokenManager = class {
|
|
|
249
275
|
/**
|
|
250
276
|
* Set access token
|
|
251
277
|
*/
|
|
252
|
-
setAccessToken(token) {
|
|
278
|
+
setAccessToken(token, event = AuthChangeEvent.SIGNED_IN) {
|
|
253
279
|
const tokenChanged = token !== this.accessToken;
|
|
254
280
|
this.accessToken = token;
|
|
255
|
-
if (tokenChanged
|
|
256
|
-
this.
|
|
281
|
+
if (tokenChanged) {
|
|
282
|
+
this.notifyAuthStateChange(event);
|
|
257
283
|
}
|
|
258
284
|
}
|
|
259
285
|
/**
|
|
@@ -275,22 +301,74 @@ var TokenManager = class {
|
|
|
275
301
|
const hadToken = this.accessToken !== null;
|
|
276
302
|
this.accessToken = null;
|
|
277
303
|
this.user = null;
|
|
278
|
-
if (hadToken
|
|
279
|
-
this.
|
|
304
|
+
if (hadToken) {
|
|
305
|
+
this.notifyAuthStateChange(AuthChangeEvent.SIGNED_OUT);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
onAuthStateChange(callback) {
|
|
309
|
+
const id = Symbol("auth-state-change");
|
|
310
|
+
this.authStateChangeCallbacks.set(id, callback);
|
|
311
|
+
return () => this.authStateChangeCallbacks.delete(id);
|
|
312
|
+
}
|
|
313
|
+
notifyAuthStateChange(event) {
|
|
314
|
+
for (const callback of this.authStateChangeCallbacks.values()) {
|
|
315
|
+
try {
|
|
316
|
+
callback(event);
|
|
317
|
+
} catch (error) {
|
|
318
|
+
console.error("Error in auth state change callback:", error);
|
|
319
|
+
}
|
|
280
320
|
}
|
|
281
321
|
}
|
|
282
322
|
};
|
|
283
323
|
|
|
324
|
+
// src/lib/jwt.ts
|
|
325
|
+
function decodeBase64Url(input) {
|
|
326
|
+
const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
327
|
+
const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
|
|
328
|
+
const binary = atob(padded);
|
|
329
|
+
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
330
|
+
return new TextDecoder().decode(bytes);
|
|
331
|
+
}
|
|
332
|
+
function getJwtExpiration(token) {
|
|
333
|
+
if (!token) {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
const [, payload] = token.split(".");
|
|
337
|
+
if (!payload) {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
try {
|
|
341
|
+
const parsed = JSON.parse(decodeBase64Url(payload));
|
|
342
|
+
if (typeof parsed.exp !== "number" || !Number.isFinite(parsed.exp)) {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
return new Date(parsed.exp * 1e3);
|
|
346
|
+
} catch {
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function isJwtExpiredOrExpiring(token, leewaySeconds = 60) {
|
|
351
|
+
if (!token) {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
const expires = getJwtExpiration(token);
|
|
355
|
+
if (!expires) {
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
return expires.getTime() <= Date.now() + leewaySeconds * 1e3;
|
|
359
|
+
}
|
|
360
|
+
|
|
284
361
|
// src/lib/http-client.ts
|
|
285
362
|
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([500, 502, 503, 504]);
|
|
286
363
|
var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
287
|
-
var REFRESHABLE_AUTH_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
288
|
-
"AUTH_UNAUTHORIZED",
|
|
289
|
-
"PGRST301"
|
|
290
|
-
]);
|
|
364
|
+
var REFRESHABLE_AUTH_ERROR_CODES = /* @__PURE__ */ new Set(["AUTH_UNAUTHORIZED", "PGRST301"]);
|
|
291
365
|
function serializeBody(method, body, headers) {
|
|
292
|
-
if (body === void 0)
|
|
293
|
-
|
|
366
|
+
if (body === void 0) {
|
|
367
|
+
return void 0;
|
|
368
|
+
}
|
|
369
|
+
if (method === "GET" || method === "HEAD") {
|
|
370
|
+
return void 0;
|
|
371
|
+
}
|
|
294
372
|
if (typeof FormData !== "undefined" && body instanceof FormData) {
|
|
295
373
|
return body;
|
|
296
374
|
}
|
|
@@ -298,7 +376,9 @@ function serializeBody(method, body, headers) {
|
|
|
298
376
|
return JSON.stringify(body);
|
|
299
377
|
}
|
|
300
378
|
async function parseResponse(response) {
|
|
301
|
-
if (response.status === 204)
|
|
379
|
+
if (response.status === 204) {
|
|
380
|
+
return void 0;
|
|
381
|
+
}
|
|
302
382
|
let data;
|
|
303
383
|
const contentType = response.headers.get("content-type");
|
|
304
384
|
try {
|
|
@@ -400,31 +480,24 @@ var HttpClient = class {
|
|
|
400
480
|
return statusCode === 401 && REFRESHABLE_AUTH_ERROR_CODES.has(errorCode ?? "") && !this.config.isServerMode && !this.config.accessToken && !this.config.edgeFunctionToken && !options.skipAuthRefresh && authToken !== null;
|
|
401
481
|
}
|
|
402
482
|
async fetchWithRetry(args) {
|
|
403
|
-
const {
|
|
404
|
-
method,
|
|
405
|
-
url,
|
|
406
|
-
headers,
|
|
407
|
-
body,
|
|
408
|
-
fetchOptions,
|
|
409
|
-
callerSignal,
|
|
410
|
-
maxAttempts
|
|
411
|
-
} = args;
|
|
483
|
+
const { method, url, headers, body, fetchOptions, callerSignal, maxAttempts } = args;
|
|
412
484
|
let lastError;
|
|
413
485
|
for (let attempt = 0; attempt <= maxAttempts; attempt++) {
|
|
414
486
|
if (attempt > 0) {
|
|
415
487
|
const delay = this.computeRetryDelay(attempt);
|
|
416
|
-
this.logger.warn(
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
488
|
+
this.logger.warn(`Retry ${attempt}/${maxAttempts} for ${method} ${url} in ${delay}ms`);
|
|
489
|
+
if (callerSignal?.aborted) {
|
|
490
|
+
throw callerSignal.reason;
|
|
491
|
+
}
|
|
420
492
|
await new Promise((resolve, reject) => {
|
|
421
493
|
const onAbort = () => {
|
|
422
494
|
clearTimeout(timer2);
|
|
423
495
|
reject(callerSignal.reason);
|
|
424
496
|
};
|
|
425
497
|
const timer2 = setTimeout(() => {
|
|
426
|
-
if (callerSignal)
|
|
498
|
+
if (callerSignal) {
|
|
427
499
|
callerSignal.removeEventListener("abort", onAbort);
|
|
500
|
+
}
|
|
428
501
|
resolve();
|
|
429
502
|
}, delay);
|
|
430
503
|
if (callerSignal) {
|
|
@@ -466,7 +539,9 @@ var HttpClient = class {
|
|
|
466
539
|
...controller ? { signal: controller.signal } : {}
|
|
467
540
|
});
|
|
468
541
|
if (this.isRetryableStatus(response.status) && attempt < maxAttempts) {
|
|
469
|
-
if (timer !== void 0)
|
|
542
|
+
if (timer !== void 0) {
|
|
543
|
+
clearTimeout(timer);
|
|
544
|
+
}
|
|
470
545
|
await response.body?.cancel();
|
|
471
546
|
lastError = new InsForgeError(
|
|
472
547
|
`Server error: ${response.status} ${response.statusText}`,
|
|
@@ -475,10 +550,14 @@ var HttpClient = class {
|
|
|
475
550
|
);
|
|
476
551
|
continue;
|
|
477
552
|
}
|
|
478
|
-
if (timer !== void 0)
|
|
553
|
+
if (timer !== void 0) {
|
|
554
|
+
clearTimeout(timer);
|
|
555
|
+
}
|
|
479
556
|
return response;
|
|
480
557
|
} catch (err) {
|
|
481
|
-
if (timer !== void 0)
|
|
558
|
+
if (timer !== void 0) {
|
|
559
|
+
clearTimeout(timer);
|
|
560
|
+
}
|
|
482
561
|
if (err?.name === "AbortError") {
|
|
483
562
|
if (controller && controller.signal.aborted && this.timeout > 0 && !callerSignal?.aborted) {
|
|
484
563
|
throw new InsForgeError(
|
|
@@ -500,11 +579,7 @@ var HttpClient = class {
|
|
|
500
579
|
);
|
|
501
580
|
}
|
|
502
581
|
}
|
|
503
|
-
throw lastError || new InsForgeError(
|
|
504
|
-
"Request failed after all retry attempts",
|
|
505
|
-
0,
|
|
506
|
-
"NETWORK_ERROR"
|
|
507
|
-
);
|
|
582
|
+
throw lastError || new InsForgeError("Request failed after all retry attempts", 0, "NETWORK_ERROR");
|
|
508
583
|
}
|
|
509
584
|
/**
|
|
510
585
|
* Performs an HTTP request with automatic retry and timeout handling.
|
|
@@ -584,31 +659,15 @@ var HttpClient = class {
|
|
|
584
659
|
}
|
|
585
660
|
throw err;
|
|
586
661
|
}
|
|
587
|
-
this.logger.logResponse(
|
|
588
|
-
method,
|
|
589
|
-
url,
|
|
590
|
-
response.status,
|
|
591
|
-
Date.now() - startTime,
|
|
592
|
-
data
|
|
593
|
-
);
|
|
662
|
+
this.logger.logResponse(method, url, response.status, Date.now() - startTime, data);
|
|
594
663
|
return data;
|
|
595
664
|
}
|
|
596
665
|
async request(method, path, options = {}) {
|
|
597
666
|
const tokenUsed = this.userToken;
|
|
598
667
|
try {
|
|
599
|
-
return await this.handleRequest(
|
|
600
|
-
method,
|
|
601
|
-
path,
|
|
602
|
-
{ ...options },
|
|
603
|
-
tokenUsed
|
|
604
|
-
);
|
|
668
|
+
return await this.handleRequest(method, path, { ...options }, tokenUsed);
|
|
605
669
|
} catch (error) {
|
|
606
|
-
if (!(error instanceof InsForgeError) || !this.shouldRefreshAccessToken(
|
|
607
|
-
error.statusCode,
|
|
608
|
-
error.error,
|
|
609
|
-
tokenUsed,
|
|
610
|
-
options
|
|
611
|
-
)) {
|
|
670
|
+
if (!(error instanceof InsForgeError) || !this.shouldRefreshAccessToken(error.statusCode, error.error, tokenUsed, options)) {
|
|
612
671
|
throw error;
|
|
613
672
|
}
|
|
614
673
|
if (tokenUsed !== this.userToken) {
|
|
@@ -693,12 +752,7 @@ var HttpClient = class {
|
|
|
693
752
|
callerSignal,
|
|
694
753
|
maxAttempts
|
|
695
754
|
});
|
|
696
|
-
this.logger.logResponse(
|
|
697
|
-
method,
|
|
698
|
-
url,
|
|
699
|
-
response.status,
|
|
700
|
-
Date.now() - startTime
|
|
701
|
-
);
|
|
755
|
+
this.logger.logResponse(method, url, response.status, Date.now() - startTime);
|
|
702
756
|
let errorCode = null;
|
|
703
757
|
if (response.status === 401) {
|
|
704
758
|
try {
|
|
@@ -712,12 +766,7 @@ var HttpClient = class {
|
|
|
712
766
|
} catch {
|
|
713
767
|
}
|
|
714
768
|
}
|
|
715
|
-
if (!this.shouldRefreshAccessToken(
|
|
716
|
-
response.status,
|
|
717
|
-
errorCode,
|
|
718
|
-
tokenUsed,
|
|
719
|
-
options
|
|
720
|
-
)) {
|
|
769
|
+
if (!this.shouldRefreshAccessToken(response.status, errorCode, tokenUsed, options)) {
|
|
721
770
|
return response;
|
|
722
771
|
}
|
|
723
772
|
if (tokenUsed !== this.userToken) {
|
|
@@ -811,10 +860,30 @@ var HttpClient = class {
|
|
|
811
860
|
})();
|
|
812
861
|
return this.refreshPromise;
|
|
813
862
|
}
|
|
863
|
+
/** Returns a token safe to use for a new connection handshake. */
|
|
864
|
+
async getValidAccessToken(leewaySeconds = 60) {
|
|
865
|
+
const accessToken = this.tokenManager.getAccessToken() ?? this.userToken;
|
|
866
|
+
if (!accessToken || !isJwtExpiredOrExpiring(accessToken, leewaySeconds)) {
|
|
867
|
+
return accessToken;
|
|
868
|
+
}
|
|
869
|
+
const canRefresh = !this.config.isServerMode && !this.config.accessToken && !this.config.edgeFunctionToken && this.userToken !== null;
|
|
870
|
+
if (!canRefresh) {
|
|
871
|
+
return accessToken;
|
|
872
|
+
}
|
|
873
|
+
try {
|
|
874
|
+
const refreshed = await this.refreshAndSaveSession();
|
|
875
|
+
return refreshed.accessToken;
|
|
876
|
+
} catch (error) {
|
|
877
|
+
if (error instanceof InsForgeError && (error.statusCode === 401 || error.statusCode === 403) && this.userToken === accessToken) {
|
|
878
|
+
this.clearAuthSession();
|
|
879
|
+
}
|
|
880
|
+
throw error;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
814
883
|
async refreshAndSaveSession() {
|
|
815
884
|
const newTokenData = await this.refreshAccessToken();
|
|
816
885
|
this.setAuthToken(newTokenData.accessToken);
|
|
817
|
-
this.tokenManager.saveSession(newTokenData);
|
|
886
|
+
this.tokenManager.saveSession(newTokenData, AuthChangeEvent.TOKEN_REFRESHED);
|
|
818
887
|
if (newTokenData.csrfToken) {
|
|
819
888
|
setCsrfToken(newTokenData.csrfToken);
|
|
820
889
|
}
|
|
@@ -899,7 +968,10 @@ function cleanUrlParams(...params) {
|
|
|
899
968
|
}
|
|
900
969
|
|
|
901
970
|
// src/modules/auth/auth.ts
|
|
902
|
-
import {
|
|
971
|
+
import {
|
|
972
|
+
ERROR_CODES,
|
|
973
|
+
oAuthProvidersSchema
|
|
974
|
+
} from "@insforge/shared-schemas";
|
|
903
975
|
var Auth = class {
|
|
904
976
|
constructor(http, tokenManager, options = {}) {
|
|
905
977
|
this.http = http;
|
|
@@ -910,11 +982,15 @@ var Auth = class {
|
|
|
910
982
|
isServerMode() {
|
|
911
983
|
return !!this.options.isServerMode;
|
|
912
984
|
}
|
|
985
|
+
/** Subscribe to SDK authentication state changes. */
|
|
986
|
+
onAuthStateChange(callback) {
|
|
987
|
+
return this.tokenManager.onAuthStateChange(callback);
|
|
988
|
+
}
|
|
913
989
|
/**
|
|
914
990
|
* Save session from API response
|
|
915
991
|
* Handles token storage, CSRF token, and HTTP auth header
|
|
916
992
|
*/
|
|
917
|
-
saveSessionFromResponse(response) {
|
|
993
|
+
saveSessionFromResponse(response, event = AuthChangeEvent.SIGNED_IN) {
|
|
918
994
|
if (!response.accessToken || !response.user) {
|
|
919
995
|
return false;
|
|
920
996
|
}
|
|
@@ -926,7 +1002,7 @@ var Auth = class {
|
|
|
926
1002
|
setCsrfToken(response.csrfToken);
|
|
927
1003
|
}
|
|
928
1004
|
if (!this.isServerMode()) {
|
|
929
|
-
this.tokenManager.saveSession(session);
|
|
1005
|
+
this.tokenManager.saveSession(session, event);
|
|
930
1006
|
}
|
|
931
1007
|
this.http.setAuthToken(response.accessToken);
|
|
932
1008
|
this.http.setRefreshToken(response.refreshToken ?? null);
|
|
@@ -940,7 +1016,9 @@ var Auth = class {
|
|
|
940
1016
|
* Supports PKCE flow (insforge_code)
|
|
941
1017
|
*/
|
|
942
1018
|
async detectAuthCallback() {
|
|
943
|
-
if (this.isServerMode() || typeof window === "undefined")
|
|
1019
|
+
if (this.isServerMode() || typeof window === "undefined") {
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
944
1022
|
try {
|
|
945
1023
|
const params = new URLSearchParams(window.location.search);
|
|
946
1024
|
const error = params.get("error");
|
|
@@ -1002,10 +1080,16 @@ var Auth = class {
|
|
|
1002
1080
|
async signOut() {
|
|
1003
1081
|
try {
|
|
1004
1082
|
try {
|
|
1083
|
+
const serverMode = this.isServerMode();
|
|
1084
|
+
const csrfToken = !serverMode ? getCsrfToken() : null;
|
|
1005
1085
|
await this.http.post(
|
|
1006
|
-
|
|
1086
|
+
serverMode ? "/api/auth/logout?client_type=mobile" : "/api/auth/logout",
|
|
1007
1087
|
void 0,
|
|
1008
|
-
{
|
|
1088
|
+
{
|
|
1089
|
+
credentials: "include",
|
|
1090
|
+
skipAuthRefresh: true,
|
|
1091
|
+
...csrfToken ? { headers: { "X-CSRF-Token": csrfToken } } : {}
|
|
1092
|
+
}
|
|
1009
1093
|
);
|
|
1010
1094
|
} catch {
|
|
1011
1095
|
}
|
|
@@ -1042,11 +1126,7 @@ var Auth = class {
|
|
|
1042
1126
|
if (!signInOptions || !signInOptions.redirectTo) {
|
|
1043
1127
|
return {
|
|
1044
1128
|
data: {},
|
|
1045
|
-
error: new InsForgeError(
|
|
1046
|
-
"Redirect URI is required",
|
|
1047
|
-
400,
|
|
1048
|
-
ERROR_CODES.INVALID_INPUT
|
|
1049
|
-
)
|
|
1129
|
+
error: new InsForgeError("Redirect URI is required", 400, ERROR_CODES.INVALID_INPUT)
|
|
1050
1130
|
};
|
|
1051
1131
|
}
|
|
1052
1132
|
const { provider } = signInOptions;
|
|
@@ -1121,10 +1201,7 @@ var Auth = class {
|
|
|
1121
1201
|
error: null
|
|
1122
1202
|
};
|
|
1123
1203
|
} catch (error) {
|
|
1124
|
-
return wrapError(
|
|
1125
|
-
error,
|
|
1126
|
-
"An unexpected error occurred during OAuth code exchange"
|
|
1127
|
-
);
|
|
1204
|
+
return wrapError(error, "An unexpected error occurred during OAuth code exchange");
|
|
1128
1205
|
}
|
|
1129
1206
|
}
|
|
1130
1207
|
/**
|
|
@@ -1151,10 +1228,7 @@ var Auth = class {
|
|
|
1151
1228
|
error: null
|
|
1152
1229
|
};
|
|
1153
1230
|
} catch (error) {
|
|
1154
|
-
return wrapError(
|
|
1155
|
-
error,
|
|
1156
|
-
"An unexpected error occurred during ID token sign in"
|
|
1157
|
-
);
|
|
1231
|
+
return wrapError(error, "An unexpected error occurred during ID token sign in");
|
|
1158
1232
|
}
|
|
1159
1233
|
}
|
|
1160
1234
|
// ============================================================================
|
|
@@ -1195,14 +1269,11 @@ var Auth = class {
|
|
|
1195
1269
|
}
|
|
1196
1270
|
);
|
|
1197
1271
|
if (response.accessToken) {
|
|
1198
|
-
this.saveSessionFromResponse(response);
|
|
1272
|
+
this.saveSessionFromResponse(response, AuthChangeEvent.TOKEN_REFRESHED);
|
|
1199
1273
|
}
|
|
1200
1274
|
return { data: response, error: null };
|
|
1201
1275
|
} catch (error) {
|
|
1202
|
-
return wrapError(
|
|
1203
|
-
error,
|
|
1204
|
-
"An unexpected error occurred during session refresh"
|
|
1205
|
-
);
|
|
1276
|
+
return wrapError(error, "An unexpected error occurred during session refresh");
|
|
1206
1277
|
}
|
|
1207
1278
|
}
|
|
1208
1279
|
/**
|
|
@@ -1213,11 +1284,11 @@ var Auth = class {
|
|
|
1213
1284
|
try {
|
|
1214
1285
|
if (this.isServerMode()) {
|
|
1215
1286
|
const accessToken = this.tokenManager.getAccessToken();
|
|
1216
|
-
if (!accessToken)
|
|
1287
|
+
if (!accessToken) {
|
|
1288
|
+
return { data: { user: null }, error: null };
|
|
1289
|
+
}
|
|
1217
1290
|
this.http.setAuthToken(accessToken);
|
|
1218
|
-
const response = await this.http.get(
|
|
1219
|
-
"/api/auth/sessions/current"
|
|
1220
|
-
);
|
|
1291
|
+
const response = await this.http.get("/api/auth/sessions/current");
|
|
1221
1292
|
const user = response.user ?? null;
|
|
1222
1293
|
return { data: { user }, error: null };
|
|
1223
1294
|
}
|
|
@@ -1255,25 +1326,17 @@ var Auth = class {
|
|
|
1255
1326
|
// ============================================================================
|
|
1256
1327
|
async getProfile(userId) {
|
|
1257
1328
|
try {
|
|
1258
|
-
const response = await this.http.get(
|
|
1259
|
-
`/api/auth/profiles/${userId}`
|
|
1260
|
-
);
|
|
1329
|
+
const response = await this.http.get(`/api/auth/profiles/${userId}`);
|
|
1261
1330
|
return { data: response, error: null };
|
|
1262
1331
|
} catch (error) {
|
|
1263
|
-
return wrapError(
|
|
1264
|
-
error,
|
|
1265
|
-
"An unexpected error occurred while fetching user profile"
|
|
1266
|
-
);
|
|
1332
|
+
return wrapError(error, "An unexpected error occurred while fetching user profile");
|
|
1267
1333
|
}
|
|
1268
1334
|
}
|
|
1269
1335
|
async setProfile(profile) {
|
|
1270
1336
|
try {
|
|
1271
|
-
const response = await this.http.patch(
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
profile
|
|
1275
|
-
}
|
|
1276
|
-
);
|
|
1337
|
+
const response = await this.http.patch("/api/auth/profiles/current", {
|
|
1338
|
+
profile
|
|
1339
|
+
});
|
|
1277
1340
|
const currentUser = this.tokenManager.getUser();
|
|
1278
1341
|
if (!this.isServerMode() && currentUser && response.profile !== void 0) {
|
|
1279
1342
|
this.tokenManager.setUser({
|
|
@@ -1283,10 +1346,7 @@ var Auth = class {
|
|
|
1283
1346
|
}
|
|
1284
1347
|
return { data: response, error: null };
|
|
1285
1348
|
} catch (error) {
|
|
1286
|
-
return wrapError(
|
|
1287
|
-
error,
|
|
1288
|
-
"An unexpected error occurred while updating user profile"
|
|
1289
|
-
);
|
|
1349
|
+
return wrapError(error, "An unexpected error occurred while updating user profile");
|
|
1290
1350
|
}
|
|
1291
1351
|
}
|
|
1292
1352
|
// ============================================================================
|
|
@@ -1299,10 +1359,7 @@ var Auth = class {
|
|
|
1299
1359
|
});
|
|
1300
1360
|
return { data: response, error: null };
|
|
1301
1361
|
} catch (error) {
|
|
1302
|
-
return wrapError(
|
|
1303
|
-
error,
|
|
1304
|
-
"An unexpected error occurred while sending verification email"
|
|
1305
|
-
);
|
|
1362
|
+
return wrapError(error, "An unexpected error occurred while sending verification email");
|
|
1306
1363
|
}
|
|
1307
1364
|
}
|
|
1308
1365
|
async verifyEmail(request) {
|
|
@@ -1318,10 +1375,7 @@ var Auth = class {
|
|
|
1318
1375
|
}
|
|
1319
1376
|
return { data: response, error: null };
|
|
1320
1377
|
} catch (error) {
|
|
1321
|
-
return wrapError(
|
|
1322
|
-
error,
|
|
1323
|
-
"An unexpected error occurred while verifying email"
|
|
1324
|
-
);
|
|
1378
|
+
return wrapError(error, "An unexpected error occurred while verifying email");
|
|
1325
1379
|
}
|
|
1326
1380
|
}
|
|
1327
1381
|
// ============================================================================
|
|
@@ -1334,10 +1388,7 @@ var Auth = class {
|
|
|
1334
1388
|
});
|
|
1335
1389
|
return { data: response, error: null };
|
|
1336
1390
|
} catch (error) {
|
|
1337
|
-
return wrapError(
|
|
1338
|
-
error,
|
|
1339
|
-
"An unexpected error occurred while sending password reset email"
|
|
1340
|
-
);
|
|
1391
|
+
return wrapError(error, "An unexpected error occurred while sending password reset email");
|
|
1341
1392
|
}
|
|
1342
1393
|
}
|
|
1343
1394
|
async exchangeResetPasswordToken(request) {
|
|
@@ -1349,10 +1400,7 @@ var Auth = class {
|
|
|
1349
1400
|
);
|
|
1350
1401
|
return { data: response, error: null };
|
|
1351
1402
|
} catch (error) {
|
|
1352
|
-
return wrapError(
|
|
1353
|
-
error,
|
|
1354
|
-
"An unexpected error occurred while verifying reset code"
|
|
1355
|
-
);
|
|
1403
|
+
return wrapError(error, "An unexpected error occurred while verifying reset code");
|
|
1356
1404
|
}
|
|
1357
1405
|
}
|
|
1358
1406
|
async resetPassword(request) {
|
|
@@ -1364,10 +1412,7 @@ var Auth = class {
|
|
|
1364
1412
|
);
|
|
1365
1413
|
return { data: response, error: null };
|
|
1366
1414
|
} catch (error) {
|
|
1367
|
-
return wrapError(
|
|
1368
|
-
error,
|
|
1369
|
-
"An unexpected error occurred while resetting password"
|
|
1370
|
-
);
|
|
1415
|
+
return wrapError(error, "An unexpected error occurred while resetting password");
|
|
1371
1416
|
}
|
|
1372
1417
|
}
|
|
1373
1418
|
// ============================================================================
|
|
@@ -1375,16 +1420,12 @@ var Auth = class {
|
|
|
1375
1420
|
// ============================================================================
|
|
1376
1421
|
async getPublicAuthConfig() {
|
|
1377
1422
|
try {
|
|
1378
|
-
const response = await this.http.get(
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
);
|
|
1423
|
+
const response = await this.http.get("/api/auth/public-config", {
|
|
1424
|
+
skipAuthRefresh: true
|
|
1425
|
+
});
|
|
1382
1426
|
return { data: response, error: null };
|
|
1383
1427
|
} catch (error) {
|
|
1384
|
-
return wrapError(
|
|
1385
|
-
error,
|
|
1386
|
-
"An unexpected error occurred while fetching auth configuration"
|
|
1387
|
-
);
|
|
1428
|
+
return wrapError(error, "An unexpected error occurred while fetching auth configuration");
|
|
1388
1429
|
}
|
|
1389
1430
|
}
|
|
1390
1431
|
};
|
|
@@ -1411,12 +1452,30 @@ function createInsForgePostgrestFetch(httpClient) {
|
|
|
1411
1452
|
};
|
|
1412
1453
|
}
|
|
1413
1454
|
var Database = class {
|
|
1414
|
-
constructor(httpClient) {
|
|
1455
|
+
constructor(httpClient, defaultSchema) {
|
|
1415
1456
|
this.postgrest = new PostgrestClient("http://dummy", {
|
|
1416
1457
|
fetch: createInsForgePostgrestFetch(httpClient),
|
|
1417
|
-
headers: {}
|
|
1458
|
+
headers: {},
|
|
1459
|
+
...defaultSchema ? { schema: defaultSchema } : {}
|
|
1418
1460
|
});
|
|
1419
1461
|
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Select a non-default Postgres schema for the chained query. Maps to
|
|
1464
|
+
* PostgREST's `Accept-Profile` (reads) / `Content-Profile` (writes) header.
|
|
1465
|
+
* The schema must be exposed by the backend.
|
|
1466
|
+
*
|
|
1467
|
+
* @example
|
|
1468
|
+
* const { data } = await client.database
|
|
1469
|
+
* .schema('analytics')
|
|
1470
|
+
* .from('events')
|
|
1471
|
+
* .select('*');
|
|
1472
|
+
*
|
|
1473
|
+
* @example
|
|
1474
|
+
* await client.database.schema('analytics').rpc('rollup', { day: '2026-01-01' });
|
|
1475
|
+
*/
|
|
1476
|
+
schema(schemaName) {
|
|
1477
|
+
return this.postgrest.schema(schemaName);
|
|
1478
|
+
}
|
|
1420
1479
|
/**
|
|
1421
1480
|
* Create a query builder for a table
|
|
1422
1481
|
*
|
|
@@ -1524,11 +1583,7 @@ var StorageBucket = class {
|
|
|
1524
1583
|
} catch (error) {
|
|
1525
1584
|
return {
|
|
1526
1585
|
data: null,
|
|
1527
|
-
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
1528
|
-
"Upload failed",
|
|
1529
|
-
500,
|
|
1530
|
-
"STORAGE_ERROR"
|
|
1531
|
-
)
|
|
1586
|
+
error: error instanceof InsForgeError ? error : new InsForgeError("Upload failed", 500, "STORAGE_ERROR")
|
|
1532
1587
|
};
|
|
1533
1588
|
}
|
|
1534
1589
|
}
|
|
@@ -1574,11 +1629,7 @@ var StorageBucket = class {
|
|
|
1574
1629
|
} catch (error) {
|
|
1575
1630
|
return {
|
|
1576
1631
|
data: null,
|
|
1577
|
-
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
1578
|
-
"Upload failed",
|
|
1579
|
-
500,
|
|
1580
|
-
"STORAGE_ERROR"
|
|
1581
|
-
)
|
|
1632
|
+
error: error instanceof InsForgeError ? error : new InsForgeError("Upload failed", 500, "STORAGE_ERROR")
|
|
1582
1633
|
};
|
|
1583
1634
|
}
|
|
1584
1635
|
}
|
|
@@ -1606,13 +1657,10 @@ var StorageBucket = class {
|
|
|
1606
1657
|
);
|
|
1607
1658
|
}
|
|
1608
1659
|
if (strategy.confirmRequired && strategy.confirmUrl) {
|
|
1609
|
-
const confirmResponse = await this.http.post(
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
contentType: file.type || "application/octet-stream"
|
|
1614
|
-
}
|
|
1615
|
-
);
|
|
1660
|
+
const confirmResponse = await this.http.post(strategy.confirmUrl, {
|
|
1661
|
+
size: file.size,
|
|
1662
|
+
contentType: file.type || "application/octet-stream"
|
|
1663
|
+
});
|
|
1616
1664
|
return { data: confirmResponse, error: null };
|
|
1617
1665
|
}
|
|
1618
1666
|
return {
|
|
@@ -1627,11 +1675,7 @@ var StorageBucket = class {
|
|
|
1627
1675
|
error: null
|
|
1628
1676
|
};
|
|
1629
1677
|
} catch (error) {
|
|
1630
|
-
throw error instanceof InsForgeError ? error : new InsForgeError(
|
|
1631
|
-
"Presigned upload failed",
|
|
1632
|
-
500,
|
|
1633
|
-
"STORAGE_ERROR"
|
|
1634
|
-
);
|
|
1678
|
+
throw error instanceof InsForgeError ? error : new InsForgeError("Presigned upload failed", 500, "STORAGE_ERROR");
|
|
1635
1679
|
}
|
|
1636
1680
|
}
|
|
1637
1681
|
/**
|
|
@@ -1685,11 +1729,7 @@ var StorageBucket = class {
|
|
|
1685
1729
|
} catch (error) {
|
|
1686
1730
|
return {
|
|
1687
1731
|
data: null,
|
|
1688
|
-
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
1689
|
-
"Download failed",
|
|
1690
|
-
500,
|
|
1691
|
-
"STORAGE_ERROR"
|
|
1692
|
-
)
|
|
1732
|
+
error: error instanceof InsForgeError ? error : new InsForgeError("Download failed", 500, "STORAGE_ERROR")
|
|
1693
1733
|
};
|
|
1694
1734
|
}
|
|
1695
1735
|
}
|
|
@@ -1724,7 +1764,9 @@ var StorageBucket = class {
|
|
|
1724
1764
|
} catch (error) {
|
|
1725
1765
|
const status = error instanceof InsForgeError ? error.statusCode : void 0;
|
|
1726
1766
|
const isMissingRoute = (status === 404 || status === 405) && !(error instanceof InsForgeError && error.error === "STORAGE_NOT_FOUND");
|
|
1727
|
-
if (!isMissingRoute)
|
|
1767
|
+
if (!isMissingRoute) {
|
|
1768
|
+
throw error;
|
|
1769
|
+
}
|
|
1728
1770
|
return await this.http.post(
|
|
1729
1771
|
`/api/storage/buckets/${this.bucketName}/objects/${encoded}/download-strategy`,
|
|
1730
1772
|
{ expiresIn }
|
|
@@ -1800,10 +1842,18 @@ var StorageBucket = class {
|
|
|
1800
1842
|
async list(options) {
|
|
1801
1843
|
try {
|
|
1802
1844
|
const params = {};
|
|
1803
|
-
if (options?.prefix)
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
if (options?.
|
|
1845
|
+
if (options?.prefix) {
|
|
1846
|
+
params.prefix = options.prefix;
|
|
1847
|
+
}
|
|
1848
|
+
if (options?.search) {
|
|
1849
|
+
params.search = options.search;
|
|
1850
|
+
}
|
|
1851
|
+
if (options?.limit) {
|
|
1852
|
+
params.limit = options.limit.toString();
|
|
1853
|
+
}
|
|
1854
|
+
if (options?.offset) {
|
|
1855
|
+
params.offset = options.offset.toString();
|
|
1856
|
+
}
|
|
1807
1857
|
const response = await this.http.get(
|
|
1808
1858
|
`/api/storage/buckets/${this.bucketName}/objects`,
|
|
1809
1859
|
{ params }
|
|
@@ -1812,11 +1862,7 @@ var StorageBucket = class {
|
|
|
1812
1862
|
} catch (error) {
|
|
1813
1863
|
return {
|
|
1814
1864
|
data: null,
|
|
1815
|
-
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
1816
|
-
"List failed",
|
|
1817
|
-
500,
|
|
1818
|
-
"STORAGE_ERROR"
|
|
1819
|
-
)
|
|
1865
|
+
error: error instanceof InsForgeError ? error : new InsForgeError("List failed", 500, "STORAGE_ERROR")
|
|
1820
1866
|
};
|
|
1821
1867
|
}
|
|
1822
1868
|
}
|
|
@@ -1833,11 +1879,7 @@ var StorageBucket = class {
|
|
|
1833
1879
|
} catch (error) {
|
|
1834
1880
|
return {
|
|
1835
1881
|
data: null,
|
|
1836
|
-
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
1837
|
-
"Delete failed",
|
|
1838
|
-
500,
|
|
1839
|
-
"STORAGE_ERROR"
|
|
1840
|
-
)
|
|
1882
|
+
error: error instanceof InsForgeError ? error : new InsForgeError("Delete failed", 500, "STORAGE_ERROR")
|
|
1841
1883
|
};
|
|
1842
1884
|
}
|
|
1843
1885
|
}
|
|
@@ -1959,14 +2001,11 @@ var ChatCompletions = class {
|
|
|
1959
2001
|
if (params.stream) {
|
|
1960
2002
|
const headers = this.http.getHeaders();
|
|
1961
2003
|
headers["Content-Type"] = "application/json";
|
|
1962
|
-
const response2 = await this.http.fetch(
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
body: JSON.stringify(backendParams)
|
|
1968
|
-
}
|
|
1969
|
-
);
|
|
2004
|
+
const response2 = await this.http.fetch(`${this.http.baseUrl}/api/ai/chat/completion`, {
|
|
2005
|
+
method: "POST",
|
|
2006
|
+
headers,
|
|
2007
|
+
body: JSON.stringify(backendParams)
|
|
2008
|
+
});
|
|
1970
2009
|
if (!response2.ok) {
|
|
1971
2010
|
const error = await response2.json();
|
|
1972
2011
|
throw new Error(error.error || "Stream request failed");
|
|
@@ -2014,7 +2053,9 @@ var ChatCompletions = class {
|
|
|
2014
2053
|
try {
|
|
2015
2054
|
while (true) {
|
|
2016
2055
|
const { done, value } = await reader.read();
|
|
2017
|
-
if (done)
|
|
2056
|
+
if (done) {
|
|
2057
|
+
break;
|
|
2058
|
+
}
|
|
2018
2059
|
buffer += decoder.decode(value, { stream: true });
|
|
2019
2060
|
const lines = buffer.split("\n");
|
|
2020
2061
|
buffer = lines.pop() || "";
|
|
@@ -2062,7 +2103,7 @@ var ChatCompletions = class {
|
|
|
2062
2103
|
reader.releaseLock();
|
|
2063
2104
|
return;
|
|
2064
2105
|
}
|
|
2065
|
-
} catch
|
|
2106
|
+
} catch {
|
|
2066
2107
|
console.warn("Failed to parse SSE data:", dataStr);
|
|
2067
2108
|
}
|
|
2068
2109
|
}
|
|
@@ -2115,10 +2156,7 @@ var Embeddings = class {
|
|
|
2115
2156
|
* ```
|
|
2116
2157
|
*/
|
|
2117
2158
|
async create(params) {
|
|
2118
|
-
const response = await this.http.post(
|
|
2119
|
-
"/api/ai/embeddings",
|
|
2120
|
-
params
|
|
2121
|
-
);
|
|
2159
|
+
const response = await this.http.post("/api/ai/embeddings", params);
|
|
2122
2160
|
return {
|
|
2123
2161
|
object: response.object,
|
|
2124
2162
|
data: response.data,
|
|
@@ -2204,7 +2242,9 @@ var Functions = class _Functions {
|
|
|
2204
2242
|
static deriveSubhostingUrl(baseUrl) {
|
|
2205
2243
|
try {
|
|
2206
2244
|
const { hostname } = new URL(baseUrl);
|
|
2207
|
-
if (!hostname.endsWith(".insforge.app"))
|
|
2245
|
+
if (!hostname.endsWith(".insforge.app")) {
|
|
2246
|
+
return void 0;
|
|
2247
|
+
}
|
|
2208
2248
|
const appKey = hostname.split(".")[0];
|
|
2209
2249
|
return `https://${appKey}.functions.insforge.app`;
|
|
2210
2250
|
} catch {
|
|
@@ -2250,7 +2290,9 @@ var Functions = class _Functions {
|
|
|
2250
2290
|
const data = await parseResponse(res);
|
|
2251
2291
|
return { data, error: null };
|
|
2252
2292
|
} catch (error) {
|
|
2253
|
-
if (error instanceof Error && error.name === "AbortError")
|
|
2293
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2294
|
+
throw error;
|
|
2295
|
+
}
|
|
2254
2296
|
return {
|
|
2255
2297
|
data: null,
|
|
2256
2298
|
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
@@ -2269,7 +2311,9 @@ var Functions = class _Functions {
|
|
|
2269
2311
|
});
|
|
2270
2312
|
return { data, error: null };
|
|
2271
2313
|
} catch (error) {
|
|
2272
|
-
if (error instanceof Error && error.name === "AbortError")
|
|
2314
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2315
|
+
throw error;
|
|
2316
|
+
}
|
|
2273
2317
|
if (error instanceof InsForgeError && error.statusCode === 404) {
|
|
2274
2318
|
} else {
|
|
2275
2319
|
return {
|
|
@@ -2288,7 +2332,9 @@ var Functions = class _Functions {
|
|
|
2288
2332
|
const data = await this.http.request(method, path, { body, headers });
|
|
2289
2333
|
return { data, error: null };
|
|
2290
2334
|
} catch (error) {
|
|
2291
|
-
if (error instanceof Error && error.name === "AbortError")
|
|
2335
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2336
|
+
throw error;
|
|
2337
|
+
}
|
|
2292
2338
|
return {
|
|
2293
2339
|
data: null,
|
|
2294
2340
|
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
@@ -2303,32 +2349,37 @@ var Functions = class _Functions {
|
|
|
2303
2349
|
|
|
2304
2350
|
// src/modules/realtime.ts
|
|
2305
2351
|
var CONNECT_TIMEOUT = 1e4;
|
|
2352
|
+
var SUBSCRIBE_TIMEOUT = 1e4;
|
|
2306
2353
|
var Realtime = class {
|
|
2307
|
-
constructor(baseUrl, tokenManager, anonKey) {
|
|
2308
|
-
this.socket = null;
|
|
2309
|
-
this.connectPromise = null;
|
|
2310
|
-
this.subscribedChannels = /* @__PURE__ */ new Set();
|
|
2311
|
-
this.eventListeners = /* @__PURE__ */ new Map();
|
|
2354
|
+
constructor(baseUrl, tokenManager, anonKey, getValidAccessToken = async () => tokenManager.getAccessToken()) {
|
|
2312
2355
|
this.baseUrl = baseUrl;
|
|
2313
2356
|
this.tokenManager = tokenManager;
|
|
2314
2357
|
this.anonKey = anonKey;
|
|
2315
|
-
this.
|
|
2358
|
+
this.getValidAccessToken = getValidAccessToken;
|
|
2359
|
+
this.socket = null;
|
|
2360
|
+
this.connectPromise = null;
|
|
2361
|
+
this.connectionAttempt = null;
|
|
2362
|
+
this.nextConnectionAttemptId = 0;
|
|
2363
|
+
this.subscriptions = /* @__PURE__ */ new Map();
|
|
2364
|
+
this.eventListeners = /* @__PURE__ */ new Map();
|
|
2365
|
+
this.tokenManager.onAuthStateChange((event) => {
|
|
2366
|
+
if (event !== AuthChangeEvent.TOKEN_REFRESHED) {
|
|
2367
|
+
this.reconnectForAuthChange();
|
|
2368
|
+
}
|
|
2369
|
+
});
|
|
2316
2370
|
}
|
|
2317
2371
|
notifyListeners(event, payload) {
|
|
2318
|
-
const
|
|
2319
|
-
if (!listeners) return;
|
|
2320
|
-
for (const cb of listeners) {
|
|
2372
|
+
for (const callback of this.eventListeners.get(event) ?? []) {
|
|
2321
2373
|
try {
|
|
2322
|
-
|
|
2323
|
-
} catch (
|
|
2324
|
-
console.error(`Error in ${event} callback:`,
|
|
2374
|
+
callback(payload);
|
|
2375
|
+
} catch (error) {
|
|
2376
|
+
console.error(`Error in ${event} callback:`, error);
|
|
2325
2377
|
}
|
|
2326
2378
|
}
|
|
2327
2379
|
}
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
*/
|
|
2380
|
+
async getHandshakeToken() {
|
|
2381
|
+
return await this.getValidAccessToken() ?? this.anonKey ?? null;
|
|
2382
|
+
}
|
|
2332
2383
|
connect() {
|
|
2333
2384
|
if (this.socket?.connected) {
|
|
2334
2385
|
return Promise.resolve();
|
|
@@ -2336,210 +2387,324 @@ var Realtime = class {
|
|
|
2336
2387
|
if (this.connectPromise) {
|
|
2337
2388
|
return this.connectPromise;
|
|
2338
2389
|
}
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
}
|
|
2355
|
-
};
|
|
2356
|
-
timeoutId = setTimeout(() => {
|
|
2357
|
-
if (initialConnection) {
|
|
2358
|
-
initialConnection = false;
|
|
2359
|
-
this.connectPromise = null;
|
|
2360
|
-
this.socket?.disconnect();
|
|
2361
|
-
this.socket = null;
|
|
2362
|
-
reject(new Error(`Connection timeout after ${CONNECT_TIMEOUT}ms`));
|
|
2363
|
-
}
|
|
2364
|
-
}, CONNECT_TIMEOUT);
|
|
2365
|
-
this.socket.on("connect", () => {
|
|
2366
|
-
cleanup();
|
|
2367
|
-
for (const channel of this.subscribedChannels) {
|
|
2368
|
-
this.socket.emit("realtime:subscribe", { channel });
|
|
2369
|
-
}
|
|
2370
|
-
this.notifyListeners("connect");
|
|
2371
|
-
if (initialConnection) {
|
|
2372
|
-
initialConnection = false;
|
|
2373
|
-
this.connectPromise = null;
|
|
2374
|
-
resolve();
|
|
2375
|
-
}
|
|
2376
|
-
});
|
|
2377
|
-
this.socket.on("connect_error", (error) => {
|
|
2378
|
-
cleanup();
|
|
2379
|
-
this.notifyListeners("connect_error", error);
|
|
2380
|
-
if (initialConnection) {
|
|
2381
|
-
initialConnection = false;
|
|
2382
|
-
this.connectPromise = null;
|
|
2383
|
-
reject(error);
|
|
2384
|
-
}
|
|
2385
|
-
});
|
|
2386
|
-
this.socket.on("disconnect", (reason) => {
|
|
2387
|
-
this.notifyListeners("disconnect", reason);
|
|
2388
|
-
});
|
|
2389
|
-
this.socket.on("realtime:error", (error) => {
|
|
2390
|
-
this.notifyListeners("error", error);
|
|
2391
|
-
});
|
|
2392
|
-
this.socket.onAny((event, message) => {
|
|
2393
|
-
if (event === "realtime:error") return;
|
|
2394
|
-
this.notifyListeners(event, message);
|
|
2395
|
-
});
|
|
2390
|
+
const attemptId = ++this.nextConnectionAttemptId;
|
|
2391
|
+
const connection = (async () => {
|
|
2392
|
+
const { io } = await import("socket.io-client");
|
|
2393
|
+
if (attemptId !== this.nextConnectionAttemptId) {
|
|
2394
|
+
throw new Error("Connection cancelled");
|
|
2395
|
+
}
|
|
2396
|
+
await new Promise((resolve, reject) => {
|
|
2397
|
+
const socket = io(this.baseUrl, {
|
|
2398
|
+
transports: ["websocket"],
|
|
2399
|
+
auth: (callback) => {
|
|
2400
|
+
void this.getHandshakeToken().then(
|
|
2401
|
+
(token) => callback(token ? { token } : {}),
|
|
2402
|
+
() => callback({})
|
|
2403
|
+
);
|
|
2404
|
+
}
|
|
2396
2405
|
});
|
|
2397
|
-
|
|
2406
|
+
this.socket = socket;
|
|
2407
|
+
let initialConnection = true;
|
|
2408
|
+
let timeoutId = null;
|
|
2409
|
+
const clearConnectTimeout = () => {
|
|
2410
|
+
if (timeoutId) {
|
|
2411
|
+
clearTimeout(timeoutId);
|
|
2412
|
+
timeoutId = null;
|
|
2413
|
+
}
|
|
2414
|
+
};
|
|
2415
|
+
const dispose = () => {
|
|
2416
|
+
clearConnectTimeout();
|
|
2417
|
+
socket.off("connect", onConnect);
|
|
2418
|
+
socket.off("connect_error", onConnectError);
|
|
2419
|
+
socket.off("disconnect", onDisconnect);
|
|
2420
|
+
socket.off("realtime:error", onRealtimeError);
|
|
2421
|
+
socket.offAny(onAny);
|
|
2422
|
+
socket.disconnect();
|
|
2423
|
+
if (this.socket === socket) {
|
|
2424
|
+
this.socket = null;
|
|
2425
|
+
}
|
|
2426
|
+
if (this.connectionAttempt?.id === attemptId) {
|
|
2427
|
+
this.connectionAttempt = null;
|
|
2428
|
+
}
|
|
2429
|
+
};
|
|
2430
|
+
const fail = (error) => {
|
|
2431
|
+
if (!initialConnection) {
|
|
2432
|
+
return;
|
|
2433
|
+
}
|
|
2434
|
+
initialConnection = false;
|
|
2435
|
+
dispose();
|
|
2436
|
+
reject(error);
|
|
2437
|
+
};
|
|
2438
|
+
const onConnect = () => {
|
|
2439
|
+
if (this.socket !== socket) {
|
|
2440
|
+
return;
|
|
2441
|
+
}
|
|
2442
|
+
clearConnectTimeout();
|
|
2443
|
+
this.resubscribeChannels();
|
|
2444
|
+
this.notifyListeners("connect");
|
|
2445
|
+
if (initialConnection) {
|
|
2446
|
+
initialConnection = false;
|
|
2447
|
+
if (this.connectionAttempt?.id === attemptId) {
|
|
2448
|
+
this.connectionAttempt = null;
|
|
2449
|
+
}
|
|
2450
|
+
resolve();
|
|
2451
|
+
}
|
|
2452
|
+
};
|
|
2453
|
+
const onConnectError = (error) => {
|
|
2454
|
+
clearConnectTimeout();
|
|
2455
|
+
this.notifyListeners("connect_error", error);
|
|
2456
|
+
if (initialConnection) {
|
|
2457
|
+
fail(error);
|
|
2458
|
+
}
|
|
2459
|
+
};
|
|
2460
|
+
const onDisconnect = (reason) => {
|
|
2461
|
+
this.handleDisconnect(reason);
|
|
2462
|
+
};
|
|
2463
|
+
const onRealtimeError = (error) => {
|
|
2464
|
+
this.notifyListeners("error", error);
|
|
2465
|
+
};
|
|
2466
|
+
const onAny = (event, message) => {
|
|
2467
|
+
if (event === "realtime:error") {
|
|
2468
|
+
return;
|
|
2469
|
+
}
|
|
2470
|
+
this.applyPresenceEvent(event, message);
|
|
2471
|
+
this.notifyListeners(event, message);
|
|
2472
|
+
};
|
|
2473
|
+
this.connectionAttempt = { id: attemptId, socket, cancel: fail };
|
|
2474
|
+
socket.on("connect", onConnect);
|
|
2475
|
+
socket.on("connect_error", onConnectError);
|
|
2476
|
+
socket.on("disconnect", onDisconnect);
|
|
2477
|
+
socket.on("realtime:error", onRealtimeError);
|
|
2478
|
+
socket.onAny(onAny);
|
|
2479
|
+
timeoutId = setTimeout(
|
|
2480
|
+
() => fail(new Error(`Connection timeout after ${CONNECT_TIMEOUT}ms`)),
|
|
2481
|
+
CONNECT_TIMEOUT
|
|
2482
|
+
);
|
|
2483
|
+
});
|
|
2484
|
+
})();
|
|
2485
|
+
const trackedConnection = connection.finally(() => {
|
|
2486
|
+
if (this.connectPromise === trackedConnection) {
|
|
2398
2487
|
this.connectPromise = null;
|
|
2399
|
-
throw error;
|
|
2400
2488
|
}
|
|
2401
|
-
})
|
|
2402
|
-
|
|
2489
|
+
});
|
|
2490
|
+
this.connectPromise = trackedConnection;
|
|
2491
|
+
return trackedConnection;
|
|
2403
2492
|
}
|
|
2404
|
-
/**
|
|
2405
|
-
* Disconnect from the realtime server
|
|
2406
|
-
*/
|
|
2407
2493
|
disconnect() {
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2494
|
+
this.nextConnectionAttemptId++;
|
|
2495
|
+
this.connectionAttempt?.cancel(new Error("Disconnected"));
|
|
2496
|
+
this.socket?.disconnect();
|
|
2497
|
+
this.socket = null;
|
|
2498
|
+
this.connectPromise = null;
|
|
2499
|
+
for (const subscription of this.subscriptions.values()) {
|
|
2500
|
+
this.settleSubscription(
|
|
2501
|
+
subscription,
|
|
2502
|
+
{
|
|
2503
|
+
ok: false,
|
|
2504
|
+
channel: subscription.channel,
|
|
2505
|
+
error: { code: "DISCONNECTED", message: "Disconnected" }
|
|
2506
|
+
},
|
|
2507
|
+
false
|
|
2508
|
+
);
|
|
2411
2509
|
}
|
|
2412
|
-
this.
|
|
2510
|
+
this.subscriptions.clear();
|
|
2413
2511
|
}
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
onTokenChange() {
|
|
2420
|
-
const token = this.tokenManager.getAccessToken() ?? this.anonKey;
|
|
2421
|
-
if (this.socket) {
|
|
2422
|
-
this.socket.auth = token ? { token } : {};
|
|
2512
|
+
reconnectForAuthChange() {
|
|
2513
|
+
for (const subscription of this.subscriptions.values()) {
|
|
2514
|
+
if (subscription.status === "rejected") {
|
|
2515
|
+
subscription.status = "pending";
|
|
2516
|
+
}
|
|
2423
2517
|
}
|
|
2424
|
-
if (this.socket
|
|
2425
|
-
|
|
2426
|
-
|
|
2518
|
+
if (!this.socket) {
|
|
2519
|
+
return;
|
|
2520
|
+
}
|
|
2521
|
+
this.socket.disconnect();
|
|
2522
|
+
this.socket.connect();
|
|
2523
|
+
}
|
|
2524
|
+
handleDisconnect(reason) {
|
|
2525
|
+
for (const subscription of this.subscriptions.values()) {
|
|
2526
|
+
if (subscription.status === "rejected") {
|
|
2527
|
+
continue;
|
|
2528
|
+
}
|
|
2529
|
+
subscription.status = "pending";
|
|
2530
|
+
this.settleSubscription(
|
|
2531
|
+
subscription,
|
|
2532
|
+
{
|
|
2533
|
+
ok: false,
|
|
2534
|
+
channel: subscription.channel,
|
|
2535
|
+
error: { code: "DISCONNECTED", message: "Connection lost before subscription completed" }
|
|
2536
|
+
},
|
|
2537
|
+
true
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2540
|
+
this.notifyListeners("disconnect", reason);
|
|
2541
|
+
}
|
|
2542
|
+
resubscribeChannels() {
|
|
2543
|
+
for (const [channel, subscription] of this.subscriptions) {
|
|
2544
|
+
if (subscription.status === "pending") {
|
|
2545
|
+
this.requestSubscription(channel, subscription);
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
requestSubscription(channel, subscription) {
|
|
2550
|
+
if (subscription.pending) {
|
|
2551
|
+
return subscription.pending;
|
|
2552
|
+
}
|
|
2553
|
+
const socket = this.socket;
|
|
2554
|
+
if (!socket?.connected) {
|
|
2555
|
+
return Promise.resolve({
|
|
2556
|
+
ok: false,
|
|
2557
|
+
channel,
|
|
2558
|
+
error: { code: "CONNECTION_FAILED", message: "Not connected to realtime server" }
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2561
|
+
subscription.status = "pending";
|
|
2562
|
+
const epoch = ++subscription.epoch;
|
|
2563
|
+
let timeoutId;
|
|
2564
|
+
subscription.pending = new Promise((resolve) => {
|
|
2565
|
+
subscription.settlePending = (response) => {
|
|
2566
|
+
if (timeoutId) {
|
|
2567
|
+
clearTimeout(timeoutId);
|
|
2568
|
+
}
|
|
2569
|
+
subscription.pending = void 0;
|
|
2570
|
+
subscription.settlePending = void 0;
|
|
2571
|
+
resolve(response);
|
|
2572
|
+
};
|
|
2573
|
+
timeoutId = setTimeout(() => {
|
|
2574
|
+
if (this.subscriptions.get(channel) === subscription && subscription.epoch === epoch) {
|
|
2575
|
+
this.settleSubscription(
|
|
2576
|
+
subscription,
|
|
2577
|
+
{
|
|
2578
|
+
ok: false,
|
|
2579
|
+
channel,
|
|
2580
|
+
error: {
|
|
2581
|
+
code: "SUBSCRIBE_TIMEOUT",
|
|
2582
|
+
message: "Subscription acknowledgement timed out"
|
|
2583
|
+
}
|
|
2584
|
+
},
|
|
2585
|
+
true
|
|
2586
|
+
);
|
|
2587
|
+
}
|
|
2588
|
+
}, SUBSCRIBE_TIMEOUT);
|
|
2589
|
+
socket.emit("realtime:subscribe", { channel }, (response) => {
|
|
2590
|
+
if (this.subscriptions.get(channel) !== subscription || subscription.epoch !== epoch) {
|
|
2591
|
+
return;
|
|
2592
|
+
}
|
|
2593
|
+
if (response.ok) {
|
|
2594
|
+
subscription.status = "subscribed";
|
|
2595
|
+
subscription.members = new Map(
|
|
2596
|
+
response.presence.members.map((member) => [member.presenceId, member])
|
|
2597
|
+
);
|
|
2598
|
+
} else {
|
|
2599
|
+
subscription.status = "rejected";
|
|
2600
|
+
subscription.members.clear();
|
|
2601
|
+
}
|
|
2602
|
+
this.settleSubscription(subscription, response, false);
|
|
2603
|
+
});
|
|
2604
|
+
});
|
|
2605
|
+
return subscription.pending;
|
|
2606
|
+
}
|
|
2607
|
+
settleSubscription(subscription, response, incrementEpoch) {
|
|
2608
|
+
if (incrementEpoch) {
|
|
2609
|
+
subscription.epoch++;
|
|
2610
|
+
}
|
|
2611
|
+
subscription.settlePending?.(response);
|
|
2612
|
+
}
|
|
2613
|
+
applyPresenceEvent(event, message) {
|
|
2614
|
+
if (event !== "presence:join" && event !== "presence:leave") {
|
|
2615
|
+
return;
|
|
2616
|
+
}
|
|
2617
|
+
const presenceEvent = message;
|
|
2618
|
+
const channel = presenceEvent.meta?.channel;
|
|
2619
|
+
const member = presenceEvent.member;
|
|
2620
|
+
if (!channel || !member) {
|
|
2621
|
+
return;
|
|
2622
|
+
}
|
|
2623
|
+
const subscription = this.subscriptions.get(channel);
|
|
2624
|
+
if (!subscription) {
|
|
2625
|
+
return;
|
|
2626
|
+
}
|
|
2627
|
+
if (event === "presence:join") {
|
|
2628
|
+
subscription.members.set(member.presenceId, member);
|
|
2629
|
+
} else {
|
|
2630
|
+
subscription.members.delete(member.presenceId);
|
|
2427
2631
|
}
|
|
2428
2632
|
}
|
|
2429
|
-
/**
|
|
2430
|
-
* Check if connected to the realtime server
|
|
2431
|
-
*/
|
|
2432
2633
|
get isConnected() {
|
|
2433
2634
|
return this.socket?.connected ?? false;
|
|
2434
2635
|
}
|
|
2435
|
-
/**
|
|
2436
|
-
* Get the current connection state
|
|
2437
|
-
*/
|
|
2438
2636
|
get connectionState() {
|
|
2439
|
-
if (!this.socket)
|
|
2440
|
-
|
|
2441
|
-
|
|
2637
|
+
if (!this.socket) {
|
|
2638
|
+
return "disconnected";
|
|
2639
|
+
}
|
|
2640
|
+
return this.socket.connected ? "connected" : "connecting";
|
|
2442
2641
|
}
|
|
2443
|
-
/**
|
|
2444
|
-
* Get the socket ID (if connected)
|
|
2445
|
-
*/
|
|
2446
2642
|
get socketId() {
|
|
2447
2643
|
return this.socket?.id;
|
|
2448
2644
|
}
|
|
2449
|
-
/**
|
|
2450
|
-
* Subscribe to a channel
|
|
2451
|
-
*
|
|
2452
|
-
* Automatically connects if not already connected.
|
|
2453
|
-
*
|
|
2454
|
-
* @param channel - Channel name (e.g., 'orders:123', 'broadcast')
|
|
2455
|
-
* @returns Promise with the subscription response
|
|
2456
|
-
*/
|
|
2457
2645
|
async subscribe(channel) {
|
|
2458
|
-
|
|
2459
|
-
|
|
2646
|
+
let subscription = this.subscriptions.get(channel);
|
|
2647
|
+
if (subscription) {
|
|
2648
|
+
if (subscription.pending) {
|
|
2649
|
+
return subscription.pending;
|
|
2650
|
+
}
|
|
2651
|
+
if (subscription.status === "subscribed") {
|
|
2652
|
+
return { ok: true, channel, presence: { members: [...subscription.members.values()] } };
|
|
2653
|
+
}
|
|
2654
|
+
} else {
|
|
2655
|
+
subscription = { channel, epoch: 0, status: "pending", members: /* @__PURE__ */ new Map() };
|
|
2656
|
+
this.subscriptions.set(channel, subscription);
|
|
2460
2657
|
}
|
|
2461
2658
|
if (!this.socket?.connected) {
|
|
2462
2659
|
try {
|
|
2463
2660
|
await this.connect();
|
|
2464
2661
|
} catch (error) {
|
|
2662
|
+
if (this.subscriptions.get(channel) === subscription) {
|
|
2663
|
+
this.subscriptions.delete(channel);
|
|
2664
|
+
}
|
|
2465
2665
|
const message = error instanceof Error ? error.message : "Connection failed";
|
|
2466
2666
|
return { ok: false, channel, error: { code: "CONNECTION_FAILED", message } };
|
|
2467
2667
|
}
|
|
2468
2668
|
}
|
|
2469
|
-
return
|
|
2470
|
-
this.socket.emit("realtime:subscribe", { channel }, (response) => {
|
|
2471
|
-
if (response.ok) {
|
|
2472
|
-
this.subscribedChannels.add(channel);
|
|
2473
|
-
}
|
|
2474
|
-
resolve(response);
|
|
2475
|
-
});
|
|
2476
|
-
});
|
|
2669
|
+
return subscription.pending ?? this.requestSubscription(channel, subscription);
|
|
2477
2670
|
}
|
|
2478
|
-
/**
|
|
2479
|
-
* Unsubscribe from a channel (fire-and-forget)
|
|
2480
|
-
*
|
|
2481
|
-
* @param channel - Channel name to unsubscribe from
|
|
2482
|
-
*/
|
|
2483
2671
|
unsubscribe(channel) {
|
|
2484
|
-
this.
|
|
2672
|
+
const subscription = this.subscriptions.get(channel);
|
|
2673
|
+
if (!subscription) {
|
|
2674
|
+
return;
|
|
2675
|
+
}
|
|
2676
|
+
this.subscriptions.delete(channel);
|
|
2677
|
+
this.settleSubscription(
|
|
2678
|
+
subscription,
|
|
2679
|
+
{
|
|
2680
|
+
ok: false,
|
|
2681
|
+
channel,
|
|
2682
|
+
error: { code: "SUBSCRIPTION_CANCELLED", message: "Subscription cancelled" }
|
|
2683
|
+
},
|
|
2684
|
+
true
|
|
2685
|
+
);
|
|
2485
2686
|
if (this.socket?.connected) {
|
|
2486
2687
|
this.socket.emit("realtime:unsubscribe", { channel });
|
|
2487
2688
|
}
|
|
2488
2689
|
}
|
|
2489
|
-
/**
|
|
2490
|
-
* Publish a message to a channel
|
|
2491
|
-
*
|
|
2492
|
-
* @param channel - Channel name
|
|
2493
|
-
* @param event - Event name
|
|
2494
|
-
* @param payload - Message payload
|
|
2495
|
-
*/
|
|
2496
2690
|
async publish(channel, event, payload) {
|
|
2497
2691
|
if (!this.socket?.connected) {
|
|
2498
2692
|
throw new Error("Not connected to realtime server. Call connect() first.");
|
|
2499
2693
|
}
|
|
2500
2694
|
this.socket.emit("realtime:publish", { channel, event, payload });
|
|
2501
2695
|
}
|
|
2502
|
-
/**
|
|
2503
|
-
* Listen for events
|
|
2504
|
-
*
|
|
2505
|
-
* Reserved event names:
|
|
2506
|
-
* - 'connect' - Fired when connected to the server
|
|
2507
|
-
* - 'connect_error' - Fired when connection fails (payload: Error)
|
|
2508
|
-
* - 'disconnect' - Fired when disconnected (payload: reason string)
|
|
2509
|
-
* - 'error' - Fired when a realtime error occurs (payload: RealtimeErrorPayload)
|
|
2510
|
-
*
|
|
2511
|
-
* All other events receive a `SocketMessage` payload with metadata.
|
|
2512
|
-
*
|
|
2513
|
-
* @param event - Event name to listen for
|
|
2514
|
-
* @param callback - Callback function when event is received
|
|
2515
|
-
*/
|
|
2516
2696
|
on(event, callback) {
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
this.eventListeners.get(event).add(callback);
|
|
2697
|
+
const listeners = this.eventListeners.get(event) ?? /* @__PURE__ */ new Set();
|
|
2698
|
+
listeners.add(callback);
|
|
2699
|
+
this.eventListeners.set(event, listeners);
|
|
2521
2700
|
}
|
|
2522
|
-
/**
|
|
2523
|
-
* Remove a listener for a specific event
|
|
2524
|
-
*
|
|
2525
|
-
* @param event - Event name
|
|
2526
|
-
* @param callback - The callback function to remove
|
|
2527
|
-
*/
|
|
2528
2701
|
off(event, callback) {
|
|
2529
2702
|
const listeners = this.eventListeners.get(event);
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
this.eventListeners.delete(event);
|
|
2534
|
-
}
|
|
2703
|
+
listeners?.delete(callback);
|
|
2704
|
+
if (listeners?.size === 0) {
|
|
2705
|
+
this.eventListeners.delete(event);
|
|
2535
2706
|
}
|
|
2536
2707
|
}
|
|
2537
|
-
/**
|
|
2538
|
-
* Listen for an event only once, then automatically remove the listener
|
|
2539
|
-
*
|
|
2540
|
-
* @param event - Event name to listen for
|
|
2541
|
-
* @param callback - Callback function when event is received
|
|
2542
|
-
*/
|
|
2543
2708
|
once(event, callback) {
|
|
2544
2709
|
const wrapper = (payload) => {
|
|
2545
2710
|
this.off(event, wrapper);
|
|
@@ -2547,13 +2712,11 @@ var Realtime = class {
|
|
|
2547
2712
|
};
|
|
2548
2713
|
this.on(event, wrapper);
|
|
2549
2714
|
}
|
|
2550
|
-
/**
|
|
2551
|
-
* Get all currently subscribed channels
|
|
2552
|
-
*
|
|
2553
|
-
* @returns Array of channel names
|
|
2554
|
-
*/
|
|
2555
2715
|
getSubscribedChannels() {
|
|
2556
|
-
return
|
|
2716
|
+
return [...this.subscriptions.values()].filter((subscription) => subscription.status === "subscribed").map((subscription) => subscription.channel);
|
|
2717
|
+
}
|
|
2718
|
+
getPresenceState(channel) {
|
|
2719
|
+
return [...this.subscriptions.get(channel)?.members.values() ?? []];
|
|
2557
2720
|
}
|
|
2558
2721
|
};
|
|
2559
2722
|
|
|
@@ -2568,13 +2731,12 @@ var Emails = class {
|
|
|
2568
2731
|
*/
|
|
2569
2732
|
async send(options) {
|
|
2570
2733
|
try {
|
|
2571
|
-
const data = await this.http.post(
|
|
2572
|
-
"/api/email/send-raw",
|
|
2573
|
-
options
|
|
2574
|
-
);
|
|
2734
|
+
const data = await this.http.post("/api/email/send-raw", options);
|
|
2575
2735
|
return { data, error: null };
|
|
2576
2736
|
} catch (error) {
|
|
2577
|
-
if (error instanceof Error && error.name === "AbortError")
|
|
2737
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2738
|
+
throw error;
|
|
2739
|
+
}
|
|
2578
2740
|
return {
|
|
2579
2741
|
data: null,
|
|
2580
2742
|
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
@@ -2657,10 +2819,7 @@ var RazorpayPayments = class {
|
|
|
2657
2819
|
);
|
|
2658
2820
|
return { data, error: null };
|
|
2659
2821
|
} catch (error) {
|
|
2660
|
-
return wrapError(
|
|
2661
|
-
error,
|
|
2662
|
-
"Razorpay order creation failed"
|
|
2663
|
-
);
|
|
2822
|
+
return wrapError(error, "Razorpay order creation failed");
|
|
2664
2823
|
}
|
|
2665
2824
|
}
|
|
2666
2825
|
async verifyOrder(environment, request) {
|
|
@@ -2671,10 +2830,7 @@ var RazorpayPayments = class {
|
|
|
2671
2830
|
);
|
|
2672
2831
|
return { data, error: null };
|
|
2673
2832
|
} catch (error) {
|
|
2674
|
-
return wrapError(
|
|
2675
|
-
error,
|
|
2676
|
-
"Razorpay order verification failed"
|
|
2677
|
-
);
|
|
2833
|
+
return wrapError(error, "Razorpay order verification failed");
|
|
2678
2834
|
}
|
|
2679
2835
|
}
|
|
2680
2836
|
async createSubscription(environment, request) {
|
|
@@ -2776,14 +2932,15 @@ var InsForgeClient = class {
|
|
|
2776
2932
|
isServerMode: config.isServerMode ?? !!accessToken,
|
|
2777
2933
|
detectOAuthCallback: config.auth?.detectOAuthCallback
|
|
2778
2934
|
});
|
|
2779
|
-
this.database = new Database(this.http);
|
|
2935
|
+
this.database = new Database(this.http, config.db?.schema);
|
|
2780
2936
|
this.storage = new Storage(this.http);
|
|
2781
2937
|
this.ai = new AI(this.http);
|
|
2782
2938
|
this.functions = new Functions(this.http, config.functionsUrl);
|
|
2783
2939
|
this.realtime = new Realtime(
|
|
2784
2940
|
this.http.baseUrl,
|
|
2785
2941
|
this.tokenManager,
|
|
2786
|
-
config.anonKey
|
|
2942
|
+
config.anonKey,
|
|
2943
|
+
() => this.http.getValidAccessToken()
|
|
2787
2944
|
);
|
|
2788
2945
|
this.emails = new Emails(this.http);
|
|
2789
2946
|
this.payments = new Payments(this.http);
|
|
@@ -2803,32 +2960,35 @@ var InsForgeClient = class {
|
|
|
2803
2960
|
/**
|
|
2804
2961
|
* Set the access token used by every SDK surface. Updates both the HTTP
|
|
2805
2962
|
* client (database / storage / functions / AI / emails) and the realtime
|
|
2806
|
-
* token manager
|
|
2807
|
-
*
|
|
2963
|
+
* token manager. Pass `null` to sign out. By default a token replacement is
|
|
2964
|
+
* treated as a sign-in boundary and reconnects realtime. Pass
|
|
2965
|
+
* `AuthChangeEvent.TOKEN_REFRESHED` for a same-identity refresh to preserve a live socket; the
|
|
2966
|
+
* refreshed token is then used at the next handshake.
|
|
2808
2967
|
*
|
|
2809
2968
|
* Use this when an external auth provider (Better Auth, Clerk, Auth0,
|
|
2810
2969
|
* WorkOS, Kinde, Stytch, …) issues the JWT and you need to keep the
|
|
2811
2970
|
* long-lived InsForge client in sync. Without this, you'd have to call
|
|
2812
2971
|
* `client.getHttpClient().setAuthToken(token)` AND reach into the private
|
|
2813
|
-
*
|
|
2814
|
-
* forgetting the second one silently breaks realtime auth.
|
|
2972
|
+
* realtime token manager separately.
|
|
2815
2973
|
*
|
|
2816
2974
|
* @example
|
|
2817
2975
|
* ```typescript
|
|
2976
|
+
* import { AuthChangeEvent } from '@insforge/sdk';
|
|
2977
|
+
*
|
|
2818
2978
|
* // Refresh a third-party-issued JWT periodically
|
|
2819
2979
|
* const { token } = await fetch('/api/insforge-token').then((r) => r.json());
|
|
2820
|
-
* client.setAccessToken(token);
|
|
2980
|
+
* client.setAccessToken(token, AuthChangeEvent.TOKEN_REFRESHED);
|
|
2821
2981
|
*
|
|
2822
2982
|
* // Sign-out
|
|
2823
2983
|
* client.setAccessToken(null);
|
|
2824
2984
|
* ```
|
|
2825
2985
|
*/
|
|
2826
|
-
setAccessToken(token) {
|
|
2986
|
+
setAccessToken(token, event = AuthChangeEvent.SIGNED_IN) {
|
|
2827
2987
|
this.http.setAuthToken(token);
|
|
2828
2988
|
if (token === null) {
|
|
2829
2989
|
this.tokenManager.clearSession();
|
|
2830
2990
|
} else {
|
|
2831
|
-
this.tokenManager.setAccessToken(token);
|
|
2991
|
+
this.tokenManager.setAccessToken(token, event);
|
|
2832
2992
|
}
|
|
2833
2993
|
}
|
|
2834
2994
|
/**
|
|
@@ -2861,6 +3021,7 @@ var src_default = InsForgeClient;
|
|
|
2861
3021
|
export {
|
|
2862
3022
|
AI,
|
|
2863
3023
|
Auth,
|
|
3024
|
+
AuthChangeEvent,
|
|
2864
3025
|
Database,
|
|
2865
3026
|
Emails,
|
|
2866
3027
|
Functions,
|
|
@@ -2872,7 +3033,6 @@ export {
|
|
|
2872
3033
|
Realtime,
|
|
2873
3034
|
Storage,
|
|
2874
3035
|
StorageBucket,
|
|
2875
|
-
TokenManager,
|
|
2876
3036
|
createAdminClient,
|
|
2877
3037
|
createClient,
|
|
2878
3038
|
src_default as default
|