@insforge/sdk 1.4.3 → 1.4.5-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +124 -143
- package/SDK-REFERENCE.md +146 -168
- package/dist/{client-B6eZHolm.d.mts → client-BZyLCjO1.d.mts} +44 -120
- package/dist/{client-DUmOm_3W.d.ts → client-Dh7GOydb.d.ts} +44 -120
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +556 -423
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +559 -423
- package/dist/index.mjs.map +1 -1
- 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 +1 -1
- package/dist/ssr.d.ts +1 -1
- package/dist/ssr.js +651 -539
- package/dist/ssr.js.map +1 -1
- package/dist/ssr.mjs +655 -540
- package/dist/ssr.mjs.map +1 -1
- 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");
|
|
@@ -1048,11 +1126,7 @@ var Auth = class {
|
|
|
1048
1126
|
if (!signInOptions || !signInOptions.redirectTo) {
|
|
1049
1127
|
return {
|
|
1050
1128
|
data: {},
|
|
1051
|
-
error: new InsForgeError(
|
|
1052
|
-
"Redirect URI is required",
|
|
1053
|
-
400,
|
|
1054
|
-
ERROR_CODES.INVALID_INPUT
|
|
1055
|
-
)
|
|
1129
|
+
error: new InsForgeError("Redirect URI is required", 400, ERROR_CODES.INVALID_INPUT)
|
|
1056
1130
|
};
|
|
1057
1131
|
}
|
|
1058
1132
|
const { provider } = signInOptions;
|
|
@@ -1127,10 +1201,7 @@ var Auth = class {
|
|
|
1127
1201
|
error: null
|
|
1128
1202
|
};
|
|
1129
1203
|
} catch (error) {
|
|
1130
|
-
return wrapError(
|
|
1131
|
-
error,
|
|
1132
|
-
"An unexpected error occurred during OAuth code exchange"
|
|
1133
|
-
);
|
|
1204
|
+
return wrapError(error, "An unexpected error occurred during OAuth code exchange");
|
|
1134
1205
|
}
|
|
1135
1206
|
}
|
|
1136
1207
|
/**
|
|
@@ -1157,10 +1228,7 @@ var Auth = class {
|
|
|
1157
1228
|
error: null
|
|
1158
1229
|
};
|
|
1159
1230
|
} catch (error) {
|
|
1160
|
-
return wrapError(
|
|
1161
|
-
error,
|
|
1162
|
-
"An unexpected error occurred during ID token sign in"
|
|
1163
|
-
);
|
|
1231
|
+
return wrapError(error, "An unexpected error occurred during ID token sign in");
|
|
1164
1232
|
}
|
|
1165
1233
|
}
|
|
1166
1234
|
// ============================================================================
|
|
@@ -1201,14 +1269,11 @@ var Auth = class {
|
|
|
1201
1269
|
}
|
|
1202
1270
|
);
|
|
1203
1271
|
if (response.accessToken) {
|
|
1204
|
-
this.saveSessionFromResponse(response);
|
|
1272
|
+
this.saveSessionFromResponse(response, AuthChangeEvent.TOKEN_REFRESHED);
|
|
1205
1273
|
}
|
|
1206
1274
|
return { data: response, error: null };
|
|
1207
1275
|
} catch (error) {
|
|
1208
|
-
return wrapError(
|
|
1209
|
-
error,
|
|
1210
|
-
"An unexpected error occurred during session refresh"
|
|
1211
|
-
);
|
|
1276
|
+
return wrapError(error, "An unexpected error occurred during session refresh");
|
|
1212
1277
|
}
|
|
1213
1278
|
}
|
|
1214
1279
|
/**
|
|
@@ -1219,11 +1284,11 @@ var Auth = class {
|
|
|
1219
1284
|
try {
|
|
1220
1285
|
if (this.isServerMode()) {
|
|
1221
1286
|
const accessToken = this.tokenManager.getAccessToken();
|
|
1222
|
-
if (!accessToken)
|
|
1287
|
+
if (!accessToken) {
|
|
1288
|
+
return { data: { user: null }, error: null };
|
|
1289
|
+
}
|
|
1223
1290
|
this.http.setAuthToken(accessToken);
|
|
1224
|
-
const response = await this.http.get(
|
|
1225
|
-
"/api/auth/sessions/current"
|
|
1226
|
-
);
|
|
1291
|
+
const response = await this.http.get("/api/auth/sessions/current");
|
|
1227
1292
|
const user = response.user ?? null;
|
|
1228
1293
|
return { data: { user }, error: null };
|
|
1229
1294
|
}
|
|
@@ -1261,25 +1326,17 @@ var Auth = class {
|
|
|
1261
1326
|
// ============================================================================
|
|
1262
1327
|
async getProfile(userId) {
|
|
1263
1328
|
try {
|
|
1264
|
-
const response = await this.http.get(
|
|
1265
|
-
`/api/auth/profiles/${userId}`
|
|
1266
|
-
);
|
|
1329
|
+
const response = await this.http.get(`/api/auth/profiles/${userId}`);
|
|
1267
1330
|
return { data: response, error: null };
|
|
1268
1331
|
} catch (error) {
|
|
1269
|
-
return wrapError(
|
|
1270
|
-
error,
|
|
1271
|
-
"An unexpected error occurred while fetching user profile"
|
|
1272
|
-
);
|
|
1332
|
+
return wrapError(error, "An unexpected error occurred while fetching user profile");
|
|
1273
1333
|
}
|
|
1274
1334
|
}
|
|
1275
1335
|
async setProfile(profile) {
|
|
1276
1336
|
try {
|
|
1277
|
-
const response = await this.http.patch(
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
profile
|
|
1281
|
-
}
|
|
1282
|
-
);
|
|
1337
|
+
const response = await this.http.patch("/api/auth/profiles/current", {
|
|
1338
|
+
profile
|
|
1339
|
+
});
|
|
1283
1340
|
const currentUser = this.tokenManager.getUser();
|
|
1284
1341
|
if (!this.isServerMode() && currentUser && response.profile !== void 0) {
|
|
1285
1342
|
this.tokenManager.setUser({
|
|
@@ -1289,10 +1346,7 @@ var Auth = class {
|
|
|
1289
1346
|
}
|
|
1290
1347
|
return { data: response, error: null };
|
|
1291
1348
|
} catch (error) {
|
|
1292
|
-
return wrapError(
|
|
1293
|
-
error,
|
|
1294
|
-
"An unexpected error occurred while updating user profile"
|
|
1295
|
-
);
|
|
1349
|
+
return wrapError(error, "An unexpected error occurred while updating user profile");
|
|
1296
1350
|
}
|
|
1297
1351
|
}
|
|
1298
1352
|
// ============================================================================
|
|
@@ -1305,10 +1359,7 @@ var Auth = class {
|
|
|
1305
1359
|
});
|
|
1306
1360
|
return { data: response, error: null };
|
|
1307
1361
|
} catch (error) {
|
|
1308
|
-
return wrapError(
|
|
1309
|
-
error,
|
|
1310
|
-
"An unexpected error occurred while sending verification email"
|
|
1311
|
-
);
|
|
1362
|
+
return wrapError(error, "An unexpected error occurred while sending verification email");
|
|
1312
1363
|
}
|
|
1313
1364
|
}
|
|
1314
1365
|
async verifyEmail(request) {
|
|
@@ -1324,10 +1375,7 @@ var Auth = class {
|
|
|
1324
1375
|
}
|
|
1325
1376
|
return { data: response, error: null };
|
|
1326
1377
|
} catch (error) {
|
|
1327
|
-
return wrapError(
|
|
1328
|
-
error,
|
|
1329
|
-
"An unexpected error occurred while verifying email"
|
|
1330
|
-
);
|
|
1378
|
+
return wrapError(error, "An unexpected error occurred while verifying email");
|
|
1331
1379
|
}
|
|
1332
1380
|
}
|
|
1333
1381
|
// ============================================================================
|
|
@@ -1340,10 +1388,7 @@ var Auth = class {
|
|
|
1340
1388
|
});
|
|
1341
1389
|
return { data: response, error: null };
|
|
1342
1390
|
} catch (error) {
|
|
1343
|
-
return wrapError(
|
|
1344
|
-
error,
|
|
1345
|
-
"An unexpected error occurred while sending password reset email"
|
|
1346
|
-
);
|
|
1391
|
+
return wrapError(error, "An unexpected error occurred while sending password reset email");
|
|
1347
1392
|
}
|
|
1348
1393
|
}
|
|
1349
1394
|
async exchangeResetPasswordToken(request) {
|
|
@@ -1355,10 +1400,7 @@ var Auth = class {
|
|
|
1355
1400
|
);
|
|
1356
1401
|
return { data: response, error: null };
|
|
1357
1402
|
} catch (error) {
|
|
1358
|
-
return wrapError(
|
|
1359
|
-
error,
|
|
1360
|
-
"An unexpected error occurred while verifying reset code"
|
|
1361
|
-
);
|
|
1403
|
+
return wrapError(error, "An unexpected error occurred while verifying reset code");
|
|
1362
1404
|
}
|
|
1363
1405
|
}
|
|
1364
1406
|
async resetPassword(request) {
|
|
@@ -1370,10 +1412,7 @@ var Auth = class {
|
|
|
1370
1412
|
);
|
|
1371
1413
|
return { data: response, error: null };
|
|
1372
1414
|
} catch (error) {
|
|
1373
|
-
return wrapError(
|
|
1374
|
-
error,
|
|
1375
|
-
"An unexpected error occurred while resetting password"
|
|
1376
|
-
);
|
|
1415
|
+
return wrapError(error, "An unexpected error occurred while resetting password");
|
|
1377
1416
|
}
|
|
1378
1417
|
}
|
|
1379
1418
|
// ============================================================================
|
|
@@ -1381,16 +1420,12 @@ var Auth = class {
|
|
|
1381
1420
|
// ============================================================================
|
|
1382
1421
|
async getPublicAuthConfig() {
|
|
1383
1422
|
try {
|
|
1384
|
-
const response = await this.http.get(
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
);
|
|
1423
|
+
const response = await this.http.get("/api/auth/public-config", {
|
|
1424
|
+
skipAuthRefresh: true
|
|
1425
|
+
});
|
|
1388
1426
|
return { data: response, error: null };
|
|
1389
1427
|
} catch (error) {
|
|
1390
|
-
return wrapError(
|
|
1391
|
-
error,
|
|
1392
|
-
"An unexpected error occurred while fetching auth configuration"
|
|
1393
|
-
);
|
|
1428
|
+
return wrapError(error, "An unexpected error occurred while fetching auth configuration");
|
|
1394
1429
|
}
|
|
1395
1430
|
}
|
|
1396
1431
|
};
|
|
@@ -1548,11 +1583,7 @@ var StorageBucket = class {
|
|
|
1548
1583
|
} catch (error) {
|
|
1549
1584
|
return {
|
|
1550
1585
|
data: null,
|
|
1551
|
-
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
1552
|
-
"Upload failed",
|
|
1553
|
-
500,
|
|
1554
|
-
"STORAGE_ERROR"
|
|
1555
|
-
)
|
|
1586
|
+
error: error instanceof InsForgeError ? error : new InsForgeError("Upload failed", 500, "STORAGE_ERROR")
|
|
1556
1587
|
};
|
|
1557
1588
|
}
|
|
1558
1589
|
}
|
|
@@ -1598,11 +1629,7 @@ var StorageBucket = class {
|
|
|
1598
1629
|
} catch (error) {
|
|
1599
1630
|
return {
|
|
1600
1631
|
data: null,
|
|
1601
|
-
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
1602
|
-
"Upload failed",
|
|
1603
|
-
500,
|
|
1604
|
-
"STORAGE_ERROR"
|
|
1605
|
-
)
|
|
1632
|
+
error: error instanceof InsForgeError ? error : new InsForgeError("Upload failed", 500, "STORAGE_ERROR")
|
|
1606
1633
|
};
|
|
1607
1634
|
}
|
|
1608
1635
|
}
|
|
@@ -1630,13 +1657,10 @@ var StorageBucket = class {
|
|
|
1630
1657
|
);
|
|
1631
1658
|
}
|
|
1632
1659
|
if (strategy.confirmRequired && strategy.confirmUrl) {
|
|
1633
|
-
const confirmResponse = await this.http.post(
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
contentType: file.type || "application/octet-stream"
|
|
1638
|
-
}
|
|
1639
|
-
);
|
|
1660
|
+
const confirmResponse = await this.http.post(strategy.confirmUrl, {
|
|
1661
|
+
size: file.size,
|
|
1662
|
+
contentType: file.type || "application/octet-stream"
|
|
1663
|
+
});
|
|
1640
1664
|
return { data: confirmResponse, error: null };
|
|
1641
1665
|
}
|
|
1642
1666
|
return {
|
|
@@ -1651,11 +1675,7 @@ var StorageBucket = class {
|
|
|
1651
1675
|
error: null
|
|
1652
1676
|
};
|
|
1653
1677
|
} catch (error) {
|
|
1654
|
-
throw error instanceof InsForgeError ? error : new InsForgeError(
|
|
1655
|
-
"Presigned upload failed",
|
|
1656
|
-
500,
|
|
1657
|
-
"STORAGE_ERROR"
|
|
1658
|
-
);
|
|
1678
|
+
throw error instanceof InsForgeError ? error : new InsForgeError("Presigned upload failed", 500, "STORAGE_ERROR");
|
|
1659
1679
|
}
|
|
1660
1680
|
}
|
|
1661
1681
|
/**
|
|
@@ -1709,11 +1729,7 @@ var StorageBucket = class {
|
|
|
1709
1729
|
} catch (error) {
|
|
1710
1730
|
return {
|
|
1711
1731
|
data: null,
|
|
1712
|
-
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
1713
|
-
"Download failed",
|
|
1714
|
-
500,
|
|
1715
|
-
"STORAGE_ERROR"
|
|
1716
|
-
)
|
|
1732
|
+
error: error instanceof InsForgeError ? error : new InsForgeError("Download failed", 500, "STORAGE_ERROR")
|
|
1717
1733
|
};
|
|
1718
1734
|
}
|
|
1719
1735
|
}
|
|
@@ -1748,7 +1764,9 @@ var StorageBucket = class {
|
|
|
1748
1764
|
} catch (error) {
|
|
1749
1765
|
const status = error instanceof InsForgeError ? error.statusCode : void 0;
|
|
1750
1766
|
const isMissingRoute = (status === 404 || status === 405) && !(error instanceof InsForgeError && error.error === "STORAGE_NOT_FOUND");
|
|
1751
|
-
if (!isMissingRoute)
|
|
1767
|
+
if (!isMissingRoute) {
|
|
1768
|
+
throw error;
|
|
1769
|
+
}
|
|
1752
1770
|
return await this.http.post(
|
|
1753
1771
|
`/api/storage/buckets/${this.bucketName}/objects/${encoded}/download-strategy`,
|
|
1754
1772
|
{ expiresIn }
|
|
@@ -1824,10 +1842,18 @@ var StorageBucket = class {
|
|
|
1824
1842
|
async list(options) {
|
|
1825
1843
|
try {
|
|
1826
1844
|
const params = {};
|
|
1827
|
-
if (options?.prefix)
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
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
|
+
}
|
|
1831
1857
|
const response = await this.http.get(
|
|
1832
1858
|
`/api/storage/buckets/${this.bucketName}/objects`,
|
|
1833
1859
|
{ params }
|
|
@@ -1836,11 +1862,7 @@ var StorageBucket = class {
|
|
|
1836
1862
|
} catch (error) {
|
|
1837
1863
|
return {
|
|
1838
1864
|
data: null,
|
|
1839
|
-
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
1840
|
-
"List failed",
|
|
1841
|
-
500,
|
|
1842
|
-
"STORAGE_ERROR"
|
|
1843
|
-
)
|
|
1865
|
+
error: error instanceof InsForgeError ? error : new InsForgeError("List failed", 500, "STORAGE_ERROR")
|
|
1844
1866
|
};
|
|
1845
1867
|
}
|
|
1846
1868
|
}
|
|
@@ -1857,11 +1879,7 @@ var StorageBucket = class {
|
|
|
1857
1879
|
} catch (error) {
|
|
1858
1880
|
return {
|
|
1859
1881
|
data: null,
|
|
1860
|
-
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
1861
|
-
"Delete failed",
|
|
1862
|
-
500,
|
|
1863
|
-
"STORAGE_ERROR"
|
|
1864
|
-
)
|
|
1882
|
+
error: error instanceof InsForgeError ? error : new InsForgeError("Delete failed", 500, "STORAGE_ERROR")
|
|
1865
1883
|
};
|
|
1866
1884
|
}
|
|
1867
1885
|
}
|
|
@@ -1983,14 +2001,11 @@ var ChatCompletions = class {
|
|
|
1983
2001
|
if (params.stream) {
|
|
1984
2002
|
const headers = this.http.getHeaders();
|
|
1985
2003
|
headers["Content-Type"] = "application/json";
|
|
1986
|
-
const response2 = await this.http.fetch(
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
body: JSON.stringify(backendParams)
|
|
1992
|
-
}
|
|
1993
|
-
);
|
|
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
|
+
});
|
|
1994
2009
|
if (!response2.ok) {
|
|
1995
2010
|
const error = await response2.json();
|
|
1996
2011
|
throw new Error(error.error || "Stream request failed");
|
|
@@ -2038,7 +2053,9 @@ var ChatCompletions = class {
|
|
|
2038
2053
|
try {
|
|
2039
2054
|
while (true) {
|
|
2040
2055
|
const { done, value } = await reader.read();
|
|
2041
|
-
if (done)
|
|
2056
|
+
if (done) {
|
|
2057
|
+
break;
|
|
2058
|
+
}
|
|
2042
2059
|
buffer += decoder.decode(value, { stream: true });
|
|
2043
2060
|
const lines = buffer.split("\n");
|
|
2044
2061
|
buffer = lines.pop() || "";
|
|
@@ -2086,7 +2103,7 @@ var ChatCompletions = class {
|
|
|
2086
2103
|
reader.releaseLock();
|
|
2087
2104
|
return;
|
|
2088
2105
|
}
|
|
2089
|
-
} catch
|
|
2106
|
+
} catch {
|
|
2090
2107
|
console.warn("Failed to parse SSE data:", dataStr);
|
|
2091
2108
|
}
|
|
2092
2109
|
}
|
|
@@ -2139,10 +2156,7 @@ var Embeddings = class {
|
|
|
2139
2156
|
* ```
|
|
2140
2157
|
*/
|
|
2141
2158
|
async create(params) {
|
|
2142
|
-
const response = await this.http.post(
|
|
2143
|
-
"/api/ai/embeddings",
|
|
2144
|
-
params
|
|
2145
|
-
);
|
|
2159
|
+
const response = await this.http.post("/api/ai/embeddings", params);
|
|
2146
2160
|
return {
|
|
2147
2161
|
object: response.object,
|
|
2148
2162
|
data: response.data,
|
|
@@ -2228,7 +2242,9 @@ var Functions = class _Functions {
|
|
|
2228
2242
|
static deriveSubhostingUrl(baseUrl) {
|
|
2229
2243
|
try {
|
|
2230
2244
|
const { hostname } = new URL(baseUrl);
|
|
2231
|
-
if (!hostname.endsWith(".insforge.app"))
|
|
2245
|
+
if (!hostname.endsWith(".insforge.app")) {
|
|
2246
|
+
return void 0;
|
|
2247
|
+
}
|
|
2232
2248
|
const appKey = hostname.split(".")[0];
|
|
2233
2249
|
return `https://${appKey}.functions.insforge.app`;
|
|
2234
2250
|
} catch {
|
|
@@ -2274,7 +2290,9 @@ var Functions = class _Functions {
|
|
|
2274
2290
|
const data = await parseResponse(res);
|
|
2275
2291
|
return { data, error: null };
|
|
2276
2292
|
} catch (error) {
|
|
2277
|
-
if (error instanceof Error && error.name === "AbortError")
|
|
2293
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2294
|
+
throw error;
|
|
2295
|
+
}
|
|
2278
2296
|
return {
|
|
2279
2297
|
data: null,
|
|
2280
2298
|
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
@@ -2293,7 +2311,9 @@ var Functions = class _Functions {
|
|
|
2293
2311
|
});
|
|
2294
2312
|
return { data, error: null };
|
|
2295
2313
|
} catch (error) {
|
|
2296
|
-
if (error instanceof Error && error.name === "AbortError")
|
|
2314
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2315
|
+
throw error;
|
|
2316
|
+
}
|
|
2297
2317
|
if (error instanceof InsForgeError && error.statusCode === 404) {
|
|
2298
2318
|
} else {
|
|
2299
2319
|
return {
|
|
@@ -2312,7 +2332,9 @@ var Functions = class _Functions {
|
|
|
2312
2332
|
const data = await this.http.request(method, path, { body, headers });
|
|
2313
2333
|
return { data, error: null };
|
|
2314
2334
|
} catch (error) {
|
|
2315
|
-
if (error instanceof Error && error.name === "AbortError")
|
|
2335
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2336
|
+
throw error;
|
|
2337
|
+
}
|
|
2316
2338
|
return {
|
|
2317
2339
|
data: null,
|
|
2318
2340
|
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
@@ -2327,32 +2349,37 @@ var Functions = class _Functions {
|
|
|
2327
2349
|
|
|
2328
2350
|
// src/modules/realtime.ts
|
|
2329
2351
|
var CONNECT_TIMEOUT = 1e4;
|
|
2352
|
+
var SUBSCRIBE_TIMEOUT = 1e4;
|
|
2330
2353
|
var Realtime = class {
|
|
2331
|
-
constructor(baseUrl, tokenManager, anonKey) {
|
|
2332
|
-
this.socket = null;
|
|
2333
|
-
this.connectPromise = null;
|
|
2334
|
-
this.subscribedChannels = /* @__PURE__ */ new Set();
|
|
2335
|
-
this.eventListeners = /* @__PURE__ */ new Map();
|
|
2354
|
+
constructor(baseUrl, tokenManager, anonKey, getValidAccessToken = async () => tokenManager.getAccessToken()) {
|
|
2336
2355
|
this.baseUrl = baseUrl;
|
|
2337
2356
|
this.tokenManager = tokenManager;
|
|
2338
2357
|
this.anonKey = anonKey;
|
|
2339
|
-
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
|
+
});
|
|
2340
2370
|
}
|
|
2341
2371
|
notifyListeners(event, payload) {
|
|
2342
|
-
const
|
|
2343
|
-
if (!listeners) return;
|
|
2344
|
-
for (const cb of listeners) {
|
|
2372
|
+
for (const callback of this.eventListeners.get(event) ?? []) {
|
|
2345
2373
|
try {
|
|
2346
|
-
|
|
2347
|
-
} catch (
|
|
2348
|
-
console.error(`Error in ${event} callback:`,
|
|
2374
|
+
callback(payload);
|
|
2375
|
+
} catch (error) {
|
|
2376
|
+
console.error(`Error in ${event} callback:`, error);
|
|
2349
2377
|
}
|
|
2350
2378
|
}
|
|
2351
2379
|
}
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
*/
|
|
2380
|
+
async getHandshakeToken() {
|
|
2381
|
+
return await this.getValidAccessToken() ?? this.anonKey ?? null;
|
|
2382
|
+
}
|
|
2356
2383
|
connect() {
|
|
2357
2384
|
if (this.socket?.connected) {
|
|
2358
2385
|
return Promise.resolve();
|
|
@@ -2360,210 +2387,324 @@ var Realtime = class {
|
|
|
2360
2387
|
if (this.connectPromise) {
|
|
2361
2388
|
return this.connectPromise;
|
|
2362
2389
|
}
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
}
|
|
2379
|
-
};
|
|
2380
|
-
timeoutId = setTimeout(() => {
|
|
2381
|
-
if (initialConnection) {
|
|
2382
|
-
initialConnection = false;
|
|
2383
|
-
this.connectPromise = null;
|
|
2384
|
-
this.socket?.disconnect();
|
|
2385
|
-
this.socket = null;
|
|
2386
|
-
reject(new Error(`Connection timeout after ${CONNECT_TIMEOUT}ms`));
|
|
2387
|
-
}
|
|
2388
|
-
}, CONNECT_TIMEOUT);
|
|
2389
|
-
this.socket.on("connect", () => {
|
|
2390
|
-
cleanup();
|
|
2391
|
-
for (const channel of this.subscribedChannels) {
|
|
2392
|
-
this.socket.emit("realtime:subscribe", { channel });
|
|
2393
|
-
}
|
|
2394
|
-
this.notifyListeners("connect");
|
|
2395
|
-
if (initialConnection) {
|
|
2396
|
-
initialConnection = false;
|
|
2397
|
-
this.connectPromise = null;
|
|
2398
|
-
resolve();
|
|
2399
|
-
}
|
|
2400
|
-
});
|
|
2401
|
-
this.socket.on("connect_error", (error) => {
|
|
2402
|
-
cleanup();
|
|
2403
|
-
this.notifyListeners("connect_error", error);
|
|
2404
|
-
if (initialConnection) {
|
|
2405
|
-
initialConnection = false;
|
|
2406
|
-
this.connectPromise = null;
|
|
2407
|
-
reject(error);
|
|
2408
|
-
}
|
|
2409
|
-
});
|
|
2410
|
-
this.socket.on("disconnect", (reason) => {
|
|
2411
|
-
this.notifyListeners("disconnect", reason);
|
|
2412
|
-
});
|
|
2413
|
-
this.socket.on("realtime:error", (error) => {
|
|
2414
|
-
this.notifyListeners("error", error);
|
|
2415
|
-
});
|
|
2416
|
-
this.socket.onAny((event, message) => {
|
|
2417
|
-
if (event === "realtime:error") return;
|
|
2418
|
-
this.notifyListeners(event, message);
|
|
2419
|
-
});
|
|
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
|
+
}
|
|
2420
2405
|
});
|
|
2421
|
-
|
|
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) {
|
|
2422
2487
|
this.connectPromise = null;
|
|
2423
|
-
throw error;
|
|
2424
2488
|
}
|
|
2425
|
-
})
|
|
2426
|
-
|
|
2489
|
+
});
|
|
2490
|
+
this.connectPromise = trackedConnection;
|
|
2491
|
+
return trackedConnection;
|
|
2427
2492
|
}
|
|
2428
|
-
/**
|
|
2429
|
-
* Disconnect from the realtime server
|
|
2430
|
-
*/
|
|
2431
2493
|
disconnect() {
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
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
|
+
);
|
|
2435
2509
|
}
|
|
2436
|
-
this.
|
|
2510
|
+
this.subscriptions.clear();
|
|
2437
2511
|
}
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
onTokenChange() {
|
|
2444
|
-
const token = this.tokenManager.getAccessToken() ?? this.anonKey;
|
|
2445
|
-
if (this.socket) {
|
|
2446
|
-
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
|
+
}
|
|
2447
2517
|
}
|
|
2448
|
-
if (this.socket
|
|
2449
|
-
|
|
2450
|
-
|
|
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);
|
|
2451
2631
|
}
|
|
2452
2632
|
}
|
|
2453
|
-
/**
|
|
2454
|
-
* Check if connected to the realtime server
|
|
2455
|
-
*/
|
|
2456
2633
|
get isConnected() {
|
|
2457
2634
|
return this.socket?.connected ?? false;
|
|
2458
2635
|
}
|
|
2459
|
-
/**
|
|
2460
|
-
* Get the current connection state
|
|
2461
|
-
*/
|
|
2462
2636
|
get connectionState() {
|
|
2463
|
-
if (!this.socket)
|
|
2464
|
-
|
|
2465
|
-
|
|
2637
|
+
if (!this.socket) {
|
|
2638
|
+
return "disconnected";
|
|
2639
|
+
}
|
|
2640
|
+
return this.socket.connected ? "connected" : "connecting";
|
|
2466
2641
|
}
|
|
2467
|
-
/**
|
|
2468
|
-
* Get the socket ID (if connected)
|
|
2469
|
-
*/
|
|
2470
2642
|
get socketId() {
|
|
2471
2643
|
return this.socket?.id;
|
|
2472
2644
|
}
|
|
2473
|
-
/**
|
|
2474
|
-
* Subscribe to a channel
|
|
2475
|
-
*
|
|
2476
|
-
* Automatically connects if not already connected.
|
|
2477
|
-
*
|
|
2478
|
-
* @param channel - Channel name (e.g., 'orders:123', 'broadcast')
|
|
2479
|
-
* @returns Promise with the subscription response
|
|
2480
|
-
*/
|
|
2481
2645
|
async subscribe(channel) {
|
|
2482
|
-
|
|
2483
|
-
|
|
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);
|
|
2484
2657
|
}
|
|
2485
2658
|
if (!this.socket?.connected) {
|
|
2486
2659
|
try {
|
|
2487
2660
|
await this.connect();
|
|
2488
2661
|
} catch (error) {
|
|
2662
|
+
if (this.subscriptions.get(channel) === subscription) {
|
|
2663
|
+
this.subscriptions.delete(channel);
|
|
2664
|
+
}
|
|
2489
2665
|
const message = error instanceof Error ? error.message : "Connection failed";
|
|
2490
2666
|
return { ok: false, channel, error: { code: "CONNECTION_FAILED", message } };
|
|
2491
2667
|
}
|
|
2492
2668
|
}
|
|
2493
|
-
return
|
|
2494
|
-
this.socket.emit("realtime:subscribe", { channel }, (response) => {
|
|
2495
|
-
if (response.ok) {
|
|
2496
|
-
this.subscribedChannels.add(channel);
|
|
2497
|
-
}
|
|
2498
|
-
resolve(response);
|
|
2499
|
-
});
|
|
2500
|
-
});
|
|
2669
|
+
return subscription.pending ?? this.requestSubscription(channel, subscription);
|
|
2501
2670
|
}
|
|
2502
|
-
/**
|
|
2503
|
-
* Unsubscribe from a channel (fire-and-forget)
|
|
2504
|
-
*
|
|
2505
|
-
* @param channel - Channel name to unsubscribe from
|
|
2506
|
-
*/
|
|
2507
2671
|
unsubscribe(channel) {
|
|
2508
|
-
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
|
+
);
|
|
2509
2686
|
if (this.socket?.connected) {
|
|
2510
2687
|
this.socket.emit("realtime:unsubscribe", { channel });
|
|
2511
2688
|
}
|
|
2512
2689
|
}
|
|
2513
|
-
/**
|
|
2514
|
-
* Publish a message to a channel
|
|
2515
|
-
*
|
|
2516
|
-
* @param channel - Channel name
|
|
2517
|
-
* @param event - Event name
|
|
2518
|
-
* @param payload - Message payload
|
|
2519
|
-
*/
|
|
2520
2690
|
async publish(channel, event, payload) {
|
|
2521
2691
|
if (!this.socket?.connected) {
|
|
2522
2692
|
throw new Error("Not connected to realtime server. Call connect() first.");
|
|
2523
2693
|
}
|
|
2524
2694
|
this.socket.emit("realtime:publish", { channel, event, payload });
|
|
2525
2695
|
}
|
|
2526
|
-
/**
|
|
2527
|
-
* Listen for events
|
|
2528
|
-
*
|
|
2529
|
-
* Reserved event names:
|
|
2530
|
-
* - 'connect' - Fired when connected to the server
|
|
2531
|
-
* - 'connect_error' - Fired when connection fails (payload: Error)
|
|
2532
|
-
* - 'disconnect' - Fired when disconnected (payload: reason string)
|
|
2533
|
-
* - 'error' - Fired when a realtime error occurs (payload: RealtimeErrorPayload)
|
|
2534
|
-
*
|
|
2535
|
-
* All other events receive a `SocketMessage` payload with metadata.
|
|
2536
|
-
*
|
|
2537
|
-
* @param event - Event name to listen for
|
|
2538
|
-
* @param callback - Callback function when event is received
|
|
2539
|
-
*/
|
|
2540
2696
|
on(event, callback) {
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
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);
|
|
2545
2700
|
}
|
|
2546
|
-
/**
|
|
2547
|
-
* Remove a listener for a specific event
|
|
2548
|
-
*
|
|
2549
|
-
* @param event - Event name
|
|
2550
|
-
* @param callback - The callback function to remove
|
|
2551
|
-
*/
|
|
2552
2701
|
off(event, callback) {
|
|
2553
2702
|
const listeners = this.eventListeners.get(event);
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
this.eventListeners.delete(event);
|
|
2558
|
-
}
|
|
2703
|
+
listeners?.delete(callback);
|
|
2704
|
+
if (listeners?.size === 0) {
|
|
2705
|
+
this.eventListeners.delete(event);
|
|
2559
2706
|
}
|
|
2560
2707
|
}
|
|
2561
|
-
/**
|
|
2562
|
-
* Listen for an event only once, then automatically remove the listener
|
|
2563
|
-
*
|
|
2564
|
-
* @param event - Event name to listen for
|
|
2565
|
-
* @param callback - Callback function when event is received
|
|
2566
|
-
*/
|
|
2567
2708
|
once(event, callback) {
|
|
2568
2709
|
const wrapper = (payload) => {
|
|
2569
2710
|
this.off(event, wrapper);
|
|
@@ -2571,13 +2712,11 @@ var Realtime = class {
|
|
|
2571
2712
|
};
|
|
2572
2713
|
this.on(event, wrapper);
|
|
2573
2714
|
}
|
|
2574
|
-
/**
|
|
2575
|
-
* Get all currently subscribed channels
|
|
2576
|
-
*
|
|
2577
|
-
* @returns Array of channel names
|
|
2578
|
-
*/
|
|
2579
2715
|
getSubscribedChannels() {
|
|
2580
|
-
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() ?? []];
|
|
2581
2720
|
}
|
|
2582
2721
|
};
|
|
2583
2722
|
|
|
@@ -2592,13 +2731,12 @@ var Emails = class {
|
|
|
2592
2731
|
*/
|
|
2593
2732
|
async send(options) {
|
|
2594
2733
|
try {
|
|
2595
|
-
const data = await this.http.post(
|
|
2596
|
-
"/api/email/send-raw",
|
|
2597
|
-
options
|
|
2598
|
-
);
|
|
2734
|
+
const data = await this.http.post("/api/email/send-raw", options);
|
|
2599
2735
|
return { data, error: null };
|
|
2600
2736
|
} catch (error) {
|
|
2601
|
-
if (error instanceof Error && error.name === "AbortError")
|
|
2737
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2738
|
+
throw error;
|
|
2739
|
+
}
|
|
2602
2740
|
return {
|
|
2603
2741
|
data: null,
|
|
2604
2742
|
error: error instanceof InsForgeError ? error : new InsForgeError(
|
|
@@ -2681,10 +2819,7 @@ var RazorpayPayments = class {
|
|
|
2681
2819
|
);
|
|
2682
2820
|
return { data, error: null };
|
|
2683
2821
|
} catch (error) {
|
|
2684
|
-
return wrapError(
|
|
2685
|
-
error,
|
|
2686
|
-
"Razorpay order creation failed"
|
|
2687
|
-
);
|
|
2822
|
+
return wrapError(error, "Razorpay order creation failed");
|
|
2688
2823
|
}
|
|
2689
2824
|
}
|
|
2690
2825
|
async verifyOrder(environment, request) {
|
|
@@ -2695,10 +2830,7 @@ var RazorpayPayments = class {
|
|
|
2695
2830
|
);
|
|
2696
2831
|
return { data, error: null };
|
|
2697
2832
|
} catch (error) {
|
|
2698
|
-
return wrapError(
|
|
2699
|
-
error,
|
|
2700
|
-
"Razorpay order verification failed"
|
|
2701
|
-
);
|
|
2833
|
+
return wrapError(error, "Razorpay order verification failed");
|
|
2702
2834
|
}
|
|
2703
2835
|
}
|
|
2704
2836
|
async createSubscription(environment, request) {
|
|
@@ -2807,7 +2939,8 @@ var InsForgeClient = class {
|
|
|
2807
2939
|
this.realtime = new Realtime(
|
|
2808
2940
|
this.http.baseUrl,
|
|
2809
2941
|
this.tokenManager,
|
|
2810
|
-
config.anonKey
|
|
2942
|
+
config.anonKey,
|
|
2943
|
+
() => this.http.getValidAccessToken()
|
|
2811
2944
|
);
|
|
2812
2945
|
this.emails = new Emails(this.http);
|
|
2813
2946
|
this.payments = new Payments(this.http);
|
|
@@ -2827,32 +2960,35 @@ var InsForgeClient = class {
|
|
|
2827
2960
|
/**
|
|
2828
2961
|
* Set the access token used by every SDK surface. Updates both the HTTP
|
|
2829
2962
|
* client (database / storage / functions / AI / emails) and the realtime
|
|
2830
|
-
* token manager
|
|
2831
|
-
*
|
|
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.
|
|
2832
2967
|
*
|
|
2833
2968
|
* Use this when an external auth provider (Better Auth, Clerk, Auth0,
|
|
2834
2969
|
* WorkOS, Kinde, Stytch, …) issues the JWT and you need to keep the
|
|
2835
2970
|
* long-lived InsForge client in sync. Without this, you'd have to call
|
|
2836
2971
|
* `client.getHttpClient().setAuthToken(token)` AND reach into the private
|
|
2837
|
-
*
|
|
2838
|
-
* forgetting the second one silently breaks realtime auth.
|
|
2972
|
+
* realtime token manager separately.
|
|
2839
2973
|
*
|
|
2840
2974
|
* @example
|
|
2841
2975
|
* ```typescript
|
|
2976
|
+
* import { AuthChangeEvent } from '@insforge/sdk';
|
|
2977
|
+
*
|
|
2842
2978
|
* // Refresh a third-party-issued JWT periodically
|
|
2843
2979
|
* const { token } = await fetch('/api/insforge-token').then((r) => r.json());
|
|
2844
|
-
* client.setAccessToken(token);
|
|
2980
|
+
* client.setAccessToken(token, AuthChangeEvent.TOKEN_REFRESHED);
|
|
2845
2981
|
*
|
|
2846
2982
|
* // Sign-out
|
|
2847
2983
|
* client.setAccessToken(null);
|
|
2848
2984
|
* ```
|
|
2849
2985
|
*/
|
|
2850
|
-
setAccessToken(token) {
|
|
2986
|
+
setAccessToken(token, event = AuthChangeEvent.SIGNED_IN) {
|
|
2851
2987
|
this.http.setAuthToken(token);
|
|
2852
2988
|
if (token === null) {
|
|
2853
2989
|
this.tokenManager.clearSession();
|
|
2854
2990
|
} else {
|
|
2855
|
-
this.tokenManager.setAccessToken(token);
|
|
2991
|
+
this.tokenManager.setAccessToken(token, event);
|
|
2856
2992
|
}
|
|
2857
2993
|
}
|
|
2858
2994
|
/**
|
|
@@ -2885,6 +3021,7 @@ var src_default = InsForgeClient;
|
|
|
2885
3021
|
export {
|
|
2886
3022
|
AI,
|
|
2887
3023
|
Auth,
|
|
3024
|
+
AuthChangeEvent,
|
|
2888
3025
|
Database,
|
|
2889
3026
|
Emails,
|
|
2890
3027
|
Functions,
|
|
@@ -2896,7 +3033,6 @@ export {
|
|
|
2896
3033
|
Realtime,
|
|
2897
3034
|
Storage,
|
|
2898
3035
|
StorageBucket,
|
|
2899
|
-
TokenManager,
|
|
2900
3036
|
createAdminClient,
|
|
2901
3037
|
createClient,
|
|
2902
3038
|
src_default as default
|