@resultdev/sdk 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +76 -6
- package/THIRD-PARTY-NOTICES +209 -0
- package/dist/client-DeE086La.d.cts +1057 -0
- package/dist/client-Z0zUHGE3.d.ts +1057 -0
- package/dist/index.cjs +2622 -9240
- package/dist/index.d.cts +10 -7494
- package/dist/index.d.ts +10 -7494
- package/dist/index.js +1964 -1435
- package/dist/middleware-BBY_6hyx.d.cts +67 -0
- package/dist/middleware-TGaDOhE9.d.ts +67 -0
- package/dist/ssr/middleware.cjs +179 -7176
- package/dist/ssr/middleware.d.cts +2 -1881
- package/dist/ssr/middleware.d.ts +2 -1881
- package/dist/ssr/middleware.js +179 -25
- package/dist/ssr.cjs +3053 -9629
- package/dist/ssr.d.cts +66 -7432
- package/dist/ssr.d.ts +66 -7432
- package/dist/ssr.js +2420 -1846
- package/dist/types-CbsaMttt.d.cts +664 -0
- package/dist/types-CbsaMttt.d.ts +664 -0
- package/package.json +43 -12
- package/dist/chunk-Y7VCTXPN.js +0 -7165
package/dist/index.js
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
// ../../node_modules/.bun/@insforge+sdk@1.4.4/node_modules/@insforge/sdk/dist/index.mjs
|
|
7
|
-
import { PostgrestClient } from "@supabase/postgrest-js";
|
|
8
|
-
var InsForgeError = class _InsForgeError extends Error {
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var ResultError = class _ResultError extends Error {
|
|
3
|
+
statusCode;
|
|
4
|
+
error;
|
|
5
|
+
nextActions;
|
|
9
6
|
constructor(message, statusCode, error, nextActions) {
|
|
10
7
|
super(message);
|
|
11
|
-
this.name = "
|
|
8
|
+
this.name = "ResultError";
|
|
12
9
|
this.statusCode = statusCode;
|
|
13
10
|
this.error = error;
|
|
14
11
|
this.nextActions = nextActions;
|
|
15
12
|
}
|
|
16
13
|
static fromApiError(apiError) {
|
|
17
|
-
return new
|
|
14
|
+
return new _ResultError(
|
|
18
15
|
apiError.message,
|
|
19
16
|
apiError.statusCode,
|
|
20
17
|
apiError.error,
|
|
@@ -22,7 +19,54 @@ var InsForgeError = class _InsForgeError extends Error {
|
|
|
22
19
|
);
|
|
23
20
|
}
|
|
24
21
|
};
|
|
25
|
-
|
|
22
|
+
|
|
23
|
+
// src/lib/jwt.ts
|
|
24
|
+
function decodeBase64Url(input) {
|
|
25
|
+
const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
26
|
+
const padded = normalized.padEnd(
|
|
27
|
+
normalized.length + (4 - normalized.length % 4) % 4,
|
|
28
|
+
"="
|
|
29
|
+
);
|
|
30
|
+
const binary = atob(padded);
|
|
31
|
+
const bytes2 = Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
32
|
+
return new TextDecoder().decode(bytes2);
|
|
33
|
+
}
|
|
34
|
+
function getJwtExpiration(token) {
|
|
35
|
+
if (!token) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const [, payload] = token.split(".");
|
|
39
|
+
if (!payload) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const parsed = JSON.parse(decodeBase64Url(payload));
|
|
44
|
+
if (typeof parsed.exp !== "number" || !Number.isFinite(parsed.exp)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return new Date(parsed.exp * 1e3);
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function isJwtExpiredOrExpiring(token, leewaySeconds = 60) {
|
|
53
|
+
if (!token) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
const expires = getJwtExpiration(token);
|
|
57
|
+
if (!expires) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
return expires.getTime() <= Date.now() + leewaySeconds * 1e3;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/lib/logger.ts
|
|
64
|
+
var SENSITIVE_HEADERS = [
|
|
65
|
+
"authorization",
|
|
66
|
+
"x-api-key",
|
|
67
|
+
"cookie",
|
|
68
|
+
"set-cookie"
|
|
69
|
+
];
|
|
26
70
|
var SENSITIVE_BODY_KEYS = [
|
|
27
71
|
"password",
|
|
28
72
|
"token",
|
|
@@ -97,6 +141,9 @@ function formatBody(body) {
|
|
|
97
141
|
}
|
|
98
142
|
}
|
|
99
143
|
var Logger = class {
|
|
144
|
+
/** Whether debug logging is currently enabled */
|
|
145
|
+
enabled;
|
|
146
|
+
customLog;
|
|
100
147
|
/**
|
|
101
148
|
* Creates a new Logger instance.
|
|
102
149
|
* @param debug - Set to true to enable console logging, or pass a custom log function
|
|
@@ -119,7 +166,7 @@ var Logger = class {
|
|
|
119
166
|
if (!this.enabled) {
|
|
120
167
|
return;
|
|
121
168
|
}
|
|
122
|
-
const formatted = `[
|
|
169
|
+
const formatted = `[Result Debug] ${message}`;
|
|
123
170
|
if (this.customLog) {
|
|
124
171
|
this.customLog(formatted, ...args);
|
|
125
172
|
} else {
|
|
@@ -135,7 +182,7 @@ var Logger = class {
|
|
|
135
182
|
if (!this.enabled) {
|
|
136
183
|
return;
|
|
137
184
|
}
|
|
138
|
-
const formatted = `[
|
|
185
|
+
const formatted = `[Result Debug] ${message}`;
|
|
139
186
|
if (this.customLog) {
|
|
140
187
|
this.customLog(formatted, ...args);
|
|
141
188
|
} else {
|
|
@@ -151,7 +198,7 @@ var Logger = class {
|
|
|
151
198
|
if (!this.enabled) {
|
|
152
199
|
return;
|
|
153
200
|
}
|
|
154
|
-
const formatted = `[
|
|
201
|
+
const formatted = `[Result Debug] ${message}`;
|
|
155
202
|
if (this.customLog) {
|
|
156
203
|
this.customLog(formatted, ...args);
|
|
157
204
|
} else {
|
|
@@ -176,7 +223,7 @@ var Logger = class {
|
|
|
176
223
|
}
|
|
177
224
|
const formattedBody = formatBody(sanitizeBody(body));
|
|
178
225
|
if (formattedBody) {
|
|
179
|
-
const truncated = formattedBody.length > 1e3 ? formattedBody.slice(0, 1e3)
|
|
226
|
+
const truncated = formattedBody.length > 1e3 ? `${formattedBody.slice(0, 1e3)}... [truncated]` : formattedBody;
|
|
180
227
|
parts.push(` Body: ${truncated}`);
|
|
181
228
|
}
|
|
182
229
|
this.log(parts.join("\n"));
|
|
@@ -197,7 +244,7 @@ var Logger = class {
|
|
|
197
244
|
const parts = [`\u2190 ${method} ${url} ${status} (${durationMs}ms)`];
|
|
198
245
|
const formattedBody = formatBody(sanitizeBody(body));
|
|
199
246
|
if (formattedBody) {
|
|
200
|
-
const truncated = formattedBody.length > 1e3 ? formattedBody.slice(0, 1e3)
|
|
247
|
+
const truncated = formattedBody.length > 1e3 ? `${formattedBody.slice(0, 1e3)}... [truncated]` : formattedBody;
|
|
201
248
|
parts.push(` Body: ${truncated}`);
|
|
202
249
|
}
|
|
203
250
|
if (status >= 400) {
|
|
@@ -207,12 +254,14 @@ var Logger = class {
|
|
|
207
254
|
}
|
|
208
255
|
}
|
|
209
256
|
};
|
|
257
|
+
|
|
258
|
+
// src/lib/token-manager.ts
|
|
210
259
|
var AuthChangeEvent = {
|
|
211
260
|
SIGNED_IN: "signedIn",
|
|
212
261
|
SIGNED_OUT: "signedOut",
|
|
213
262
|
TOKEN_REFRESHED: "tokenRefreshed"
|
|
214
263
|
};
|
|
215
|
-
var CSRF_TOKEN_COOKIE = "
|
|
264
|
+
var CSRF_TOKEN_COOKIE = "result_csrf_token";
|
|
216
265
|
function getCsrfToken() {
|
|
217
266
|
if (typeof document === "undefined") {
|
|
218
267
|
return null;
|
|
@@ -239,11 +288,10 @@ function clearCsrfToken() {
|
|
|
239
288
|
document.cookie = `${CSRF_TOKEN_COOKIE}=; path=/; max-age=0; SameSite=Lax${secure}`;
|
|
240
289
|
}
|
|
241
290
|
var TokenManager = class {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
291
|
+
// In-memory storage
|
|
292
|
+
accessToken = null;
|
|
293
|
+
user = null;
|
|
294
|
+
authStateChangeCallbacks = /* @__PURE__ */ new Map();
|
|
247
295
|
/**
|
|
248
296
|
* Save session in memory
|
|
249
297
|
*/
|
|
@@ -321,41 +369,8 @@ var TokenManager = class {
|
|
|
321
369
|
}
|
|
322
370
|
}
|
|
323
371
|
};
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
|
|
327
|
-
const binary = atob(padded);
|
|
328
|
-
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
329
|
-
return new TextDecoder().decode(bytes);
|
|
330
|
-
}
|
|
331
|
-
function getJwtExpiration(token) {
|
|
332
|
-
if (!token) {
|
|
333
|
-
return null;
|
|
334
|
-
}
|
|
335
|
-
const [, payload] = token.split(".");
|
|
336
|
-
if (!payload) {
|
|
337
|
-
return null;
|
|
338
|
-
}
|
|
339
|
-
try {
|
|
340
|
-
const parsed = JSON.parse(decodeBase64Url(payload));
|
|
341
|
-
if (typeof parsed.exp !== "number" || !Number.isFinite(parsed.exp)) {
|
|
342
|
-
return null;
|
|
343
|
-
}
|
|
344
|
-
return new Date(parsed.exp * 1e3);
|
|
345
|
-
} catch {
|
|
346
|
-
return null;
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
function isJwtExpiredOrExpiring(token, leewaySeconds = 60) {
|
|
350
|
-
if (!token) {
|
|
351
|
-
return false;
|
|
352
|
-
}
|
|
353
|
-
const expires = getJwtExpiration(token);
|
|
354
|
-
if (!expires) {
|
|
355
|
-
return true;
|
|
356
|
-
}
|
|
357
|
-
return expires.getTime() <= Date.now() + leewaySeconds * 1e3;
|
|
358
|
-
}
|
|
372
|
+
|
|
373
|
+
// src/lib/http-client.ts
|
|
359
374
|
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([500, 502, 503, 504]);
|
|
360
375
|
var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
361
376
|
var REFRESHABLE_AUTH_ERROR_CODES = /* @__PURE__ */ new Set(["AUTH_UNAUTHORIZED", "PGRST301"]);
|
|
@@ -385,7 +400,7 @@ async function parseResponse(response) {
|
|
|
385
400
|
data = await response.text();
|
|
386
401
|
}
|
|
387
402
|
} catch (parseErr) {
|
|
388
|
-
throw new
|
|
403
|
+
throw new ResultError(
|
|
389
404
|
`Failed to parse response body: ${parseErr?.message || "Unknown error"}`,
|
|
390
405
|
response.status,
|
|
391
406
|
response.ok ? "PARSE_ERROR" : "REQUEST_FAILED"
|
|
@@ -393,8 +408,8 @@ async function parseResponse(response) {
|
|
|
393
408
|
}
|
|
394
409
|
if (!response.ok) {
|
|
395
410
|
if (data && typeof data === "object" && "error" in data) {
|
|
396
|
-
data.statusCode
|
|
397
|
-
const error =
|
|
411
|
+
data.statusCode ??= data.status ?? response.status;
|
|
412
|
+
const error = ResultError.fromApiError(data);
|
|
398
413
|
Object.keys(data).forEach((key) => {
|
|
399
414
|
if (key !== "error" && key !== "message" && key !== "statusCode") {
|
|
400
415
|
error[key] = data[key];
|
|
@@ -402,7 +417,7 @@ async function parseResponse(response) {
|
|
|
402
417
|
});
|
|
403
418
|
throw error;
|
|
404
419
|
}
|
|
405
|
-
throw new
|
|
420
|
+
throw new ResultError(
|
|
406
421
|
`Request failed: ${response.statusText}`,
|
|
407
422
|
response.status,
|
|
408
423
|
"REQUEST_FAILED"
|
|
@@ -411,6 +426,20 @@ async function parseResponse(response) {
|
|
|
411
426
|
return data;
|
|
412
427
|
}
|
|
413
428
|
var HttpClient = class {
|
|
429
|
+
baseUrl;
|
|
430
|
+
fetch;
|
|
431
|
+
config;
|
|
432
|
+
defaultHeaders;
|
|
433
|
+
anonKey;
|
|
434
|
+
userToken = null;
|
|
435
|
+
logger;
|
|
436
|
+
isRefreshing = false;
|
|
437
|
+
refreshPromise = null;
|
|
438
|
+
tokenManager;
|
|
439
|
+
refreshToken = null;
|
|
440
|
+
timeout;
|
|
441
|
+
retryCount;
|
|
442
|
+
retryDelay;
|
|
414
443
|
/**
|
|
415
444
|
* Creates a new HttpClient instance.
|
|
416
445
|
* @param config - SDK configuration including baseUrl, timeout, retry settings, and fetch implementation.
|
|
@@ -418,10 +447,6 @@ var HttpClient = class {
|
|
|
418
447
|
* @param logger - Optional logger instance for request/response debugging.
|
|
419
448
|
*/
|
|
420
449
|
constructor(config, tokenManager, logger) {
|
|
421
|
-
this.userToken = null;
|
|
422
|
-
this.isRefreshing = false;
|
|
423
|
-
this.refreshPromise = null;
|
|
424
|
-
this.refreshToken = null;
|
|
425
450
|
this.config = config;
|
|
426
451
|
this.baseUrl = config.baseUrl || "http://localhost:7130";
|
|
427
452
|
this.fetch = config.fetch || (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
|
|
@@ -469,7 +494,7 @@ var HttpClient = class {
|
|
|
469
494
|
* @returns Delay in milliseconds.
|
|
470
495
|
*/
|
|
471
496
|
computeRetryDelay(attempt) {
|
|
472
|
-
const base = this.retryDelay *
|
|
497
|
+
const base = this.retryDelay * 2 ** (attempt - 1);
|
|
473
498
|
const jitter = base * (0.85 + Math.random() * 0.3);
|
|
474
499
|
return Math.round(jitter);
|
|
475
500
|
}
|
|
@@ -477,19 +502,29 @@ var HttpClient = class {
|
|
|
477
502
|
return statusCode === 401 && REFRESHABLE_AUTH_ERROR_CODES.has(errorCode ?? "") && !this.config.isServerMode && !this.config.accessToken && !this.config.edgeFunctionToken && !options.skipAuthRefresh && authToken !== null;
|
|
478
503
|
}
|
|
479
504
|
async fetchWithRetry(args) {
|
|
480
|
-
const {
|
|
505
|
+
const {
|
|
506
|
+
method,
|
|
507
|
+
url,
|
|
508
|
+
headers,
|
|
509
|
+
body,
|
|
510
|
+
fetchOptions,
|
|
511
|
+
callerSignal,
|
|
512
|
+
maxAttempts
|
|
513
|
+
} = args;
|
|
481
514
|
let lastError;
|
|
482
515
|
for (let attempt = 0; attempt <= maxAttempts; attempt++) {
|
|
483
516
|
if (attempt > 0) {
|
|
484
517
|
const delay = this.computeRetryDelay(attempt);
|
|
485
|
-
this.logger.warn(
|
|
518
|
+
this.logger.warn(
|
|
519
|
+
`Retry ${attempt}/${maxAttempts} for ${method} ${url} in ${delay}ms`
|
|
520
|
+
);
|
|
486
521
|
if (callerSignal?.aborted) {
|
|
487
522
|
throw callerSignal.reason;
|
|
488
523
|
}
|
|
489
524
|
await new Promise((resolve, reject) => {
|
|
490
525
|
const onAbort = () => {
|
|
491
526
|
clearTimeout(timer2);
|
|
492
|
-
reject(callerSignal
|
|
527
|
+
reject(callerSignal?.reason);
|
|
493
528
|
};
|
|
494
529
|
const timer2 = setTimeout(() => {
|
|
495
530
|
if (callerSignal) {
|
|
@@ -507,13 +542,13 @@ var HttpClient = class {
|
|
|
507
542
|
if (this.timeout > 0 || callerSignal) {
|
|
508
543
|
controller = new AbortController();
|
|
509
544
|
if (this.timeout > 0) {
|
|
510
|
-
timer = setTimeout(() => controller
|
|
545
|
+
timer = setTimeout(() => controller?.abort(), this.timeout);
|
|
511
546
|
}
|
|
512
547
|
if (callerSignal) {
|
|
513
548
|
if (callerSignal.aborted) {
|
|
514
549
|
controller.abort(callerSignal.reason);
|
|
515
550
|
} else {
|
|
516
|
-
const onCallerAbort = () => controller
|
|
551
|
+
const onCallerAbort = () => controller?.abort(callerSignal.reason);
|
|
517
552
|
callerSignal.addEventListener("abort", onCallerAbort, {
|
|
518
553
|
once: true
|
|
519
554
|
});
|
|
@@ -540,7 +575,7 @@ var HttpClient = class {
|
|
|
540
575
|
clearTimeout(timer);
|
|
541
576
|
}
|
|
542
577
|
await response.body?.cancel();
|
|
543
|
-
lastError = new
|
|
578
|
+
lastError = new ResultError(
|
|
544
579
|
`Server error: ${response.status} ${response.statusText}`,
|
|
545
580
|
response.status,
|
|
546
581
|
"SERVER_ERROR"
|
|
@@ -556,8 +591,8 @@ var HttpClient = class {
|
|
|
556
591
|
clearTimeout(timer);
|
|
557
592
|
}
|
|
558
593
|
if (err?.name === "AbortError") {
|
|
559
|
-
if (controller
|
|
560
|
-
throw new
|
|
594
|
+
if (controller?.signal.aborted && this.timeout > 0 && !callerSignal?.aborted) {
|
|
595
|
+
throw new ResultError(
|
|
561
596
|
`Request timed out after ${this.timeout}ms`,
|
|
562
597
|
408,
|
|
563
598
|
"REQUEST_TIMEOUT"
|
|
@@ -569,14 +604,18 @@ var HttpClient = class {
|
|
|
569
604
|
lastError = err;
|
|
570
605
|
continue;
|
|
571
606
|
}
|
|
572
|
-
throw new
|
|
607
|
+
throw new ResultError(
|
|
573
608
|
`Network request failed: ${err?.message || "Unknown error"}`,
|
|
574
609
|
0,
|
|
575
610
|
"NETWORK_ERROR"
|
|
576
611
|
);
|
|
577
612
|
}
|
|
578
613
|
}
|
|
579
|
-
throw lastError || new
|
|
614
|
+
throw lastError || new ResultError(
|
|
615
|
+
"Request failed after all retry attempts",
|
|
616
|
+
0,
|
|
617
|
+
"NETWORK_ERROR"
|
|
618
|
+
);
|
|
580
619
|
}
|
|
581
620
|
/**
|
|
582
621
|
* Performs an HTTP request with automatic retry and timeout handling.
|
|
@@ -586,7 +625,7 @@ var HttpClient = class {
|
|
|
586
625
|
* @param path - API path relative to the base URL.
|
|
587
626
|
* @param options - Optional request configuration including headers, body, and query params.
|
|
588
627
|
* @returns Parsed response data.
|
|
589
|
-
* @throws {
|
|
628
|
+
* @throws {ResultError} On timeout, network failure, or HTTP error responses.
|
|
590
629
|
*/
|
|
591
630
|
async handleRequest(method, path, options = {}, tokenOverride) {
|
|
592
631
|
const {
|
|
@@ -606,14 +645,14 @@ var HttpClient = class {
|
|
|
606
645
|
};
|
|
607
646
|
const authToken = tokenOverride ?? this.userToken ?? this.anonKey;
|
|
608
647
|
if (authToken) {
|
|
609
|
-
requestHeaders
|
|
648
|
+
requestHeaders.Authorization = `Bearer ${authToken}`;
|
|
610
649
|
}
|
|
611
650
|
const processedBody = serializeBody(method, body, requestHeaders);
|
|
612
651
|
const setRequestHeader = (key, value) => {
|
|
613
652
|
if (key.toLowerCase() === "authorization") {
|
|
614
|
-
delete requestHeaders
|
|
615
|
-
delete requestHeaders
|
|
616
|
-
requestHeaders
|
|
653
|
+
delete requestHeaders.Authorization;
|
|
654
|
+
delete requestHeaders.authorization;
|
|
655
|
+
requestHeaders.Authorization = value;
|
|
617
656
|
return;
|
|
618
657
|
}
|
|
619
658
|
requestHeaders[key] = value;
|
|
@@ -645,7 +684,7 @@ var HttpClient = class {
|
|
|
645
684
|
try {
|
|
646
685
|
data = await parseResponse(response);
|
|
647
686
|
} catch (err) {
|
|
648
|
-
if (err instanceof
|
|
687
|
+
if (err instanceof ResultError) {
|
|
649
688
|
this.logger.logResponse(
|
|
650
689
|
method,
|
|
651
690
|
url,
|
|
@@ -656,15 +695,31 @@ var HttpClient = class {
|
|
|
656
695
|
}
|
|
657
696
|
throw err;
|
|
658
697
|
}
|
|
659
|
-
this.logger.logResponse(
|
|
698
|
+
this.logger.logResponse(
|
|
699
|
+
method,
|
|
700
|
+
url,
|
|
701
|
+
response.status,
|
|
702
|
+
Date.now() - startTime,
|
|
703
|
+
data
|
|
704
|
+
);
|
|
660
705
|
return data;
|
|
661
706
|
}
|
|
662
707
|
async request(method, path, options = {}) {
|
|
663
708
|
const tokenUsed = this.userToken;
|
|
664
709
|
try {
|
|
665
|
-
return await this.handleRequest(
|
|
710
|
+
return await this.handleRequest(
|
|
711
|
+
method,
|
|
712
|
+
path,
|
|
713
|
+
{ ...options },
|
|
714
|
+
tokenUsed
|
|
715
|
+
);
|
|
666
716
|
} catch (error) {
|
|
667
|
-
if (!(error instanceof
|
|
717
|
+
if (!(error instanceof ResultError) || !this.shouldRefreshAccessToken(
|
|
718
|
+
error.statusCode,
|
|
719
|
+
error.error,
|
|
720
|
+
tokenUsed,
|
|
721
|
+
options
|
|
722
|
+
)) {
|
|
668
723
|
throw error;
|
|
669
724
|
}
|
|
670
725
|
if (tokenUsed !== this.userToken) {
|
|
@@ -684,7 +739,7 @@ var HttpClient = class {
|
|
|
684
739
|
try {
|
|
685
740
|
await this.refreshAndSaveSession();
|
|
686
741
|
} catch (error2) {
|
|
687
|
-
if (error2 instanceof
|
|
742
|
+
if (error2 instanceof ResultError && (error2.statusCode === 401 || error2.statusCode === 403)) {
|
|
688
743
|
this.clearAuthSession();
|
|
689
744
|
}
|
|
690
745
|
throw error2;
|
|
@@ -749,7 +804,12 @@ var HttpClient = class {
|
|
|
749
804
|
callerSignal,
|
|
750
805
|
maxAttempts
|
|
751
806
|
});
|
|
752
|
-
this.logger.logResponse(
|
|
807
|
+
this.logger.logResponse(
|
|
808
|
+
method,
|
|
809
|
+
url,
|
|
810
|
+
response.status,
|
|
811
|
+
Date.now() - startTime
|
|
812
|
+
);
|
|
753
813
|
let errorCode = null;
|
|
754
814
|
if (response.status === 401) {
|
|
755
815
|
try {
|
|
@@ -763,7 +823,12 @@ var HttpClient = class {
|
|
|
763
823
|
} catch {
|
|
764
824
|
}
|
|
765
825
|
}
|
|
766
|
-
if (!this.shouldRefreshAccessToken(
|
|
826
|
+
if (!this.shouldRefreshAccessToken(
|
|
827
|
+
response.status,
|
|
828
|
+
errorCode,
|
|
829
|
+
tokenUsed,
|
|
830
|
+
options
|
|
831
|
+
)) {
|
|
767
832
|
return response;
|
|
768
833
|
}
|
|
769
834
|
if (tokenUsed !== this.userToken) {
|
|
@@ -782,7 +847,7 @@ var HttpClient = class {
|
|
|
782
847
|
try {
|
|
783
848
|
newTokenData = await this.refreshAndSaveSession();
|
|
784
849
|
} catch (error) {
|
|
785
|
-
if (error instanceof
|
|
850
|
+
if (error instanceof ResultError && (error.statusCode === 401 || error.statusCode === 403)) {
|
|
786
851
|
this.clearAuthSession();
|
|
787
852
|
}
|
|
788
853
|
throw error;
|
|
@@ -827,7 +892,7 @@ var HttpClient = class {
|
|
|
827
892
|
const headers = { ...this.defaultHeaders };
|
|
828
893
|
const authToken = this.userToken || this.anonKey;
|
|
829
894
|
if (authToken) {
|
|
830
|
-
headers
|
|
895
|
+
headers.Authorization = `Bearer ${authToken}`;
|
|
831
896
|
}
|
|
832
897
|
return headers;
|
|
833
898
|
}
|
|
@@ -871,7 +936,7 @@ var HttpClient = class {
|
|
|
871
936
|
const refreshed = await this.refreshAndSaveSession();
|
|
872
937
|
return refreshed.accessToken;
|
|
873
938
|
} catch (error) {
|
|
874
|
-
if (error instanceof
|
|
939
|
+
if (error instanceof ResultError && (error.statusCode === 401 || error.statusCode === 403) && this.userToken === accessToken) {
|
|
875
940
|
this.clearAuthSession();
|
|
876
941
|
}
|
|
877
942
|
throw error;
|
|
@@ -880,7 +945,10 @@ var HttpClient = class {
|
|
|
880
945
|
async refreshAndSaveSession() {
|
|
881
946
|
const newTokenData = await this.refreshAccessToken();
|
|
882
947
|
this.setAuthToken(newTokenData.accessToken);
|
|
883
|
-
this.tokenManager.saveSession(
|
|
948
|
+
this.tokenManager.saveSession(
|
|
949
|
+
newTokenData,
|
|
950
|
+
AuthChangeEvent.TOKEN_REFRESHED
|
|
951
|
+
);
|
|
884
952
|
if (newTokenData.csrfToken) {
|
|
885
953
|
setCsrfToken(newTokenData.csrfToken);
|
|
886
954
|
}
|
|
@@ -896,1328 +964,1404 @@ var HttpClient = class {
|
|
|
896
964
|
clearCsrfToken();
|
|
897
965
|
}
|
|
898
966
|
};
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
throw new Error("Web Crypto API is not available in this environment");
|
|
910
|
-
}
|
|
911
|
-
function base64UrlEncode(buffer) {
|
|
912
|
-
const base64 = btoa(String.fromCharCode(...buffer));
|
|
913
|
-
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
914
|
-
}
|
|
915
|
-
async function generateCodeVerifier() {
|
|
916
|
-
const webCrypto = await getWebCrypto();
|
|
917
|
-
const array = new Uint8Array(32);
|
|
918
|
-
webCrypto.getRandomValues(array);
|
|
919
|
-
return base64UrlEncode(array);
|
|
920
|
-
}
|
|
921
|
-
async function generateCodeChallenge(verifier) {
|
|
922
|
-
const webCrypto = await getWebCrypto();
|
|
923
|
-
const encoder = new TextEncoder();
|
|
924
|
-
const data = encoder.encode(verifier);
|
|
925
|
-
const hash = await webCrypto.subtle.digest("SHA-256", data);
|
|
926
|
-
return base64UrlEncode(new Uint8Array(hash));
|
|
927
|
-
}
|
|
928
|
-
function storePkceVerifier(verifier) {
|
|
929
|
-
if (typeof sessionStorage !== "undefined") {
|
|
930
|
-
sessionStorage.setItem(PKCE_VERIFIER_KEY, verifier);
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
function retrievePkceVerifier() {
|
|
934
|
-
if (typeof sessionStorage === "undefined") {
|
|
935
|
-
return null;
|
|
936
|
-
}
|
|
937
|
-
const verifier = sessionStorage.getItem(PKCE_VERIFIER_KEY);
|
|
938
|
-
if (verifier) {
|
|
939
|
-
sessionStorage.removeItem(PKCE_VERIFIER_KEY);
|
|
940
|
-
}
|
|
941
|
-
return verifier;
|
|
942
|
-
}
|
|
943
|
-
function wrapError(error, fallbackMessage) {
|
|
944
|
-
if (error instanceof InsForgeError) {
|
|
945
|
-
return { data: null, error };
|
|
967
|
+
|
|
968
|
+
// src/modules/ai.ts
|
|
969
|
+
var AI = class {
|
|
970
|
+
chat;
|
|
971
|
+
images;
|
|
972
|
+
embeddings;
|
|
973
|
+
constructor(http) {
|
|
974
|
+
this.chat = new Chat(http);
|
|
975
|
+
this.images = new Images(http);
|
|
976
|
+
this.embeddings = new Embeddings(http);
|
|
946
977
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
"UNEXPECTED_ERROR"
|
|
953
|
-
)
|
|
954
|
-
};
|
|
955
|
-
}
|
|
956
|
-
function cleanUrlParams(...params) {
|
|
957
|
-
if (typeof window === "undefined") {
|
|
958
|
-
return;
|
|
978
|
+
};
|
|
979
|
+
var Chat = class {
|
|
980
|
+
completions;
|
|
981
|
+
constructor(http) {
|
|
982
|
+
this.completions = new ChatCompletions(http);
|
|
959
983
|
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
}
|
|
964
|
-
var Auth = class {
|
|
965
|
-
constructor(http, tokenManager, options = {}) {
|
|
984
|
+
};
|
|
985
|
+
var ChatCompletions = class {
|
|
986
|
+
constructor(http) {
|
|
966
987
|
this.http = http;
|
|
967
|
-
this.tokenManager = tokenManager;
|
|
968
|
-
this.options = options;
|
|
969
|
-
this.authCallbackHandled = options.detectOAuthCallback === false ? Promise.resolve() : this.detectAuthCallback();
|
|
970
988
|
}
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
989
|
+
http;
|
|
990
|
+
async create(params) {
|
|
991
|
+
const backendParams = {
|
|
992
|
+
model: params.model,
|
|
993
|
+
messages: params.messages,
|
|
994
|
+
temperature: params.temperature,
|
|
995
|
+
maxTokens: params.maxTokens,
|
|
996
|
+
topP: params.topP,
|
|
997
|
+
stream: params.stream,
|
|
998
|
+
// New plugin options
|
|
999
|
+
webSearch: params.webSearch,
|
|
1000
|
+
fileParser: params.fileParser,
|
|
1001
|
+
thinking: params.thinking,
|
|
1002
|
+
// Tool calling options
|
|
1003
|
+
tools: params.tools,
|
|
1004
|
+
toolChoice: params.toolChoice,
|
|
1005
|
+
parallelToolCalls: params.parallelToolCalls
|
|
1006
|
+
};
|
|
1007
|
+
if (params.stream) {
|
|
1008
|
+
const headers = this.http.getHeaders();
|
|
1009
|
+
headers["Content-Type"] = "application/json";
|
|
1010
|
+
const response2 = await this.http.fetch(
|
|
1011
|
+
`${this.http.baseUrl}/api/ai/chat/completion`,
|
|
1012
|
+
{
|
|
1013
|
+
method: "POST",
|
|
1014
|
+
headers,
|
|
1015
|
+
body: JSON.stringify(backendParams)
|
|
1016
|
+
}
|
|
1017
|
+
);
|
|
1018
|
+
if (!response2.ok) {
|
|
1019
|
+
const error = await response2.json();
|
|
1020
|
+
throw new Error(error.error || "Stream request failed");
|
|
1021
|
+
}
|
|
1022
|
+
return this.parseSSEStream(response2, params.model);
|
|
1023
|
+
}
|
|
1024
|
+
const response = await this.http.post(
|
|
1025
|
+
"/api/ai/chat/completion",
|
|
1026
|
+
backendParams
|
|
1027
|
+
);
|
|
1028
|
+
const content = response.text || "";
|
|
1029
|
+
return {
|
|
1030
|
+
id: `chatcmpl-${Date.now()}`,
|
|
1031
|
+
object: "chat.completion",
|
|
1032
|
+
created: Math.floor(Date.now() / 1e3),
|
|
1033
|
+
model: response.metadata?.model,
|
|
1034
|
+
choices: [
|
|
1035
|
+
{
|
|
1036
|
+
index: 0,
|
|
1037
|
+
message: {
|
|
1038
|
+
role: "assistant",
|
|
1039
|
+
content,
|
|
1040
|
+
// Include tool_calls if present (from tool calling)
|
|
1041
|
+
...response.tool_calls?.length && {
|
|
1042
|
+
tool_calls: response.tool_calls
|
|
1043
|
+
},
|
|
1044
|
+
// Include annotations if present (from web search or file parsing)
|
|
1045
|
+
...response.annotations?.length && {
|
|
1046
|
+
annotations: response.annotations
|
|
1047
|
+
}
|
|
1048
|
+
},
|
|
1049
|
+
finish_reason: response.tool_calls?.length ? "tool_calls" : "stop"
|
|
1050
|
+
}
|
|
1051
|
+
],
|
|
1052
|
+
usage: {
|
|
1053
|
+
prompt_tokens: response.metadata?.usage?.promptTokens || 0,
|
|
1054
|
+
completion_tokens: response.metadata?.usage?.completionTokens || 0,
|
|
1055
|
+
total_tokens: response.metadata?.usage?.totalTokens || 0
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
977
1058
|
}
|
|
978
1059
|
/**
|
|
979
|
-
*
|
|
980
|
-
* Handles token storage, CSRF token, and HTTP auth header
|
|
1060
|
+
* Parse SSE stream into async iterable of OpenAI-like chunks
|
|
981
1061
|
*/
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
if (!this.isServerMode() && response.csrfToken) {
|
|
991
|
-
setCsrfToken(response.csrfToken);
|
|
1062
|
+
async *parseSSEStream(response, model) {
|
|
1063
|
+
const reader = response.body?.getReader();
|
|
1064
|
+
if (!reader) {
|
|
1065
|
+
throw new ResultError(
|
|
1066
|
+
"Streaming response has no body",
|
|
1067
|
+
500,
|
|
1068
|
+
"AI_UPSTREAM_UNAVAILABLE"
|
|
1069
|
+
);
|
|
992
1070
|
}
|
|
993
|
-
|
|
994
|
-
|
|
1071
|
+
const decoder = new TextDecoder();
|
|
1072
|
+
let buffer = "";
|
|
1073
|
+
try {
|
|
1074
|
+
while (true) {
|
|
1075
|
+
const { done, value } = await reader.read();
|
|
1076
|
+
if (done) {
|
|
1077
|
+
break;
|
|
1078
|
+
}
|
|
1079
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1080
|
+
const lines = buffer.split("\n");
|
|
1081
|
+
buffer = lines.pop() || "";
|
|
1082
|
+
for (const line of lines) {
|
|
1083
|
+
if (line.startsWith("data: ")) {
|
|
1084
|
+
const dataStr = line.slice(6).trim();
|
|
1085
|
+
if (dataStr) {
|
|
1086
|
+
try {
|
|
1087
|
+
const data = JSON.parse(dataStr);
|
|
1088
|
+
if (data.chunk || data.content) {
|
|
1089
|
+
yield {
|
|
1090
|
+
id: `chatcmpl-${Date.now()}`,
|
|
1091
|
+
object: "chat.completion.chunk",
|
|
1092
|
+
created: Math.floor(Date.now() / 1e3),
|
|
1093
|
+
model,
|
|
1094
|
+
choices: [
|
|
1095
|
+
{
|
|
1096
|
+
index: 0,
|
|
1097
|
+
delta: {
|
|
1098
|
+
content: data.chunk || data.content
|
|
1099
|
+
},
|
|
1100
|
+
finish_reason: null
|
|
1101
|
+
}
|
|
1102
|
+
]
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
if (data.tool_calls?.length) {
|
|
1106
|
+
yield {
|
|
1107
|
+
id: `chatcmpl-${Date.now()}`,
|
|
1108
|
+
object: "chat.completion.chunk",
|
|
1109
|
+
created: Math.floor(Date.now() / 1e3),
|
|
1110
|
+
model,
|
|
1111
|
+
choices: [
|
|
1112
|
+
{
|
|
1113
|
+
index: 0,
|
|
1114
|
+
delta: {
|
|
1115
|
+
tool_calls: data.tool_calls
|
|
1116
|
+
},
|
|
1117
|
+
finish_reason: "tool_calls"
|
|
1118
|
+
}
|
|
1119
|
+
]
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
if (data.done) {
|
|
1123
|
+
reader.releaseLock();
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
} catch {
|
|
1127
|
+
console.warn("Failed to parse SSE data:", dataStr);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
} finally {
|
|
1134
|
+
reader.releaseLock();
|
|
995
1135
|
}
|
|
996
|
-
this.http.setAuthToken(response.accessToken);
|
|
997
|
-
this.http.setRefreshToken(response.refreshToken ?? null);
|
|
998
|
-
return true;
|
|
999
1136
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1137
|
+
};
|
|
1138
|
+
var Embeddings = class {
|
|
1139
|
+
constructor(http) {
|
|
1140
|
+
this.http = http;
|
|
1141
|
+
}
|
|
1142
|
+
http;
|
|
1003
1143
|
/**
|
|
1004
|
-
*
|
|
1005
|
-
*
|
|
1144
|
+
* Create embeddings for text input - OpenAI-like response format
|
|
1145
|
+
*
|
|
1146
|
+
* @example
|
|
1147
|
+
* ```typescript
|
|
1148
|
+
* // Single text input
|
|
1149
|
+
* const response = await client.ai.embeddings.create({
|
|
1150
|
+
* model: 'openai/text-embedding-3-small',
|
|
1151
|
+
* input: 'Hello world'
|
|
1152
|
+
* });
|
|
1153
|
+
* console.log(response.data[0].embedding); // number[]
|
|
1154
|
+
*
|
|
1155
|
+
* // Multiple text inputs
|
|
1156
|
+
* const response = await client.ai.embeddings.create({
|
|
1157
|
+
* model: 'openai/text-embedding-3-small',
|
|
1158
|
+
* input: ['Hello world', 'Goodbye world']
|
|
1159
|
+
* });
|
|
1160
|
+
* response.data.forEach((item, i) => {
|
|
1161
|
+
* console.log(`Embedding ${i}:`, item.embedding.slice(0, 5)); // First 5 dimensions
|
|
1162
|
+
* });
|
|
1163
|
+
*
|
|
1164
|
+
* // With custom dimensions (if supported by model)
|
|
1165
|
+
* const response = await client.ai.embeddings.create({
|
|
1166
|
+
* model: 'openai/text-embedding-3-small',
|
|
1167
|
+
* input: 'Hello world',
|
|
1168
|
+
* dimensions: 256
|
|
1169
|
+
* });
|
|
1170
|
+
*
|
|
1171
|
+
* // With base64 encoding format
|
|
1172
|
+
* const response = await client.ai.embeddings.create({
|
|
1173
|
+
* model: 'openai/text-embedding-3-small',
|
|
1174
|
+
* input: 'Hello world',
|
|
1175
|
+
* encoding_format: 'base64'
|
|
1176
|
+
* });
|
|
1177
|
+
* ```
|
|
1006
1178
|
*/
|
|
1007
|
-
async
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1179
|
+
async create(params) {
|
|
1180
|
+
const response = await this.http.post(
|
|
1181
|
+
"/api/ai/embeddings",
|
|
1182
|
+
params
|
|
1183
|
+
);
|
|
1184
|
+
return {
|
|
1185
|
+
object: response.object,
|
|
1186
|
+
data: response.data,
|
|
1187
|
+
model: response.metadata?.model,
|
|
1188
|
+
usage: response.metadata?.usage ? {
|
|
1189
|
+
prompt_tokens: response.metadata.usage.promptTokens || 0,
|
|
1190
|
+
total_tokens: response.metadata.usage.totalTokens || 0
|
|
1191
|
+
} : {
|
|
1192
|
+
prompt_tokens: 0,
|
|
1193
|
+
total_tokens: 0
|
|
1018
1194
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
};
|
|
1198
|
+
var Images = class {
|
|
1199
|
+
constructor(http) {
|
|
1200
|
+
this.http = http;
|
|
1201
|
+
}
|
|
1202
|
+
http;
|
|
1203
|
+
/**
|
|
1204
|
+
* Generate images - OpenAI-like response format
|
|
1205
|
+
*
|
|
1206
|
+
* @example
|
|
1207
|
+
* ```typescript
|
|
1208
|
+
* // Text-to-image
|
|
1209
|
+
* const response = await client.ai.images.generate({
|
|
1210
|
+
* model: 'dall-e-3',
|
|
1211
|
+
* prompt: 'A sunset over mountains',
|
|
1212
|
+
* });
|
|
1213
|
+
* console.log(response.data[0].b64_json);
|
|
1214
|
+
*
|
|
1215
|
+
* // Image-to-image (with input images)
|
|
1216
|
+
* const response = await client.ai.images.generate({
|
|
1217
|
+
* model: 'stable-diffusion-xl',
|
|
1218
|
+
* prompt: 'Transform this into a watercolor painting',
|
|
1219
|
+
* images: [
|
|
1220
|
+
* { url: 'https://example.com/input.jpg' },
|
|
1221
|
+
* // or base64-encoded Data URI:
|
|
1222
|
+
* { url: 'data:image/jpeg;base64,/9j/4AAQ...' }
|
|
1223
|
+
* ]
|
|
1224
|
+
* });
|
|
1225
|
+
* ```
|
|
1226
|
+
*/
|
|
1227
|
+
async generate(params) {
|
|
1228
|
+
const response = await this.http.post(
|
|
1229
|
+
"/api/ai/image/generation",
|
|
1230
|
+
params
|
|
1231
|
+
);
|
|
1232
|
+
let data = [];
|
|
1233
|
+
if (response.images && response.images.length > 0) {
|
|
1234
|
+
data = response.images.map((img) => ({
|
|
1235
|
+
b64_json: img.imageUrl.replace(/^data:image\/\w+;base64,/, ""),
|
|
1236
|
+
content: response.text
|
|
1237
|
+
}));
|
|
1238
|
+
} else if (response.text) {
|
|
1239
|
+
data = [{ content: response.text }];
|
|
1240
|
+
}
|
|
1241
|
+
return {
|
|
1242
|
+
created: Math.floor(Date.now() / 1e3),
|
|
1243
|
+
data,
|
|
1244
|
+
...response.metadata?.usage && {
|
|
1245
|
+
usage: {
|
|
1246
|
+
total_tokens: response.metadata.usage.totalTokens || 0,
|
|
1247
|
+
input_tokens: response.metadata.usage.promptTokens || 0,
|
|
1248
|
+
output_tokens: response.metadata.usage.completionTokens || 0
|
|
1025
1249
|
}
|
|
1026
|
-
return;
|
|
1027
1250
|
}
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
};
|
|
1254
|
+
|
|
1255
|
+
// src/modules/analytics.ts
|
|
1256
|
+
var DEFAULT_ENDPOINT = "https://beta.result.dev/api/wa";
|
|
1257
|
+
var IDLE_MS = 30 * 60 * 1e3;
|
|
1258
|
+
var Analytics = class {
|
|
1259
|
+
siteId;
|
|
1260
|
+
endpoint;
|
|
1261
|
+
options;
|
|
1262
|
+
active = false;
|
|
1263
|
+
lastUrl = null;
|
|
1264
|
+
left = false;
|
|
1265
|
+
referrer = "";
|
|
1266
|
+
// localStorage with an in-memory fallback for private-mode browsers.
|
|
1267
|
+
mem = {};
|
|
1268
|
+
constructor(options) {
|
|
1269
|
+
this.options = options ?? { siteId: "" };
|
|
1270
|
+
this.siteId = this.options.siteId ?? "";
|
|
1271
|
+
this.endpoint = this.options.endpoint || DEFAULT_ENDPOINT;
|
|
1272
|
+
if (!this.siteId || typeof window === "undefined" || navigator.webdriver) {
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
if (window.rwa) {
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
if (this.read("rwa_disable") === "1") {
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
const domains = (this.options.domains ?? []).map(
|
|
1282
|
+
(domain) => domain.trim().toLowerCase().replace(/^www\./, "")
|
|
1283
|
+
).filter(Boolean);
|
|
1284
|
+
const here = location.hostname.toLowerCase().replace(/^www\./, "");
|
|
1285
|
+
if (domains.length && domains.indexOf(here) === -1) {
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
this.active = true;
|
|
1289
|
+
this.referrer = document.referrer || "";
|
|
1290
|
+
window.rwa = {
|
|
1291
|
+
track: (name, props) => this.track(name, props),
|
|
1292
|
+
identify: (id) => this.identify(id)
|
|
1293
|
+
};
|
|
1294
|
+
const doc = document;
|
|
1295
|
+
if (doc.prerendering) {
|
|
1296
|
+
document.addEventListener("prerenderingchange", () => this.start(), {
|
|
1297
|
+
once: true
|
|
1298
|
+
});
|
|
1299
|
+
} else {
|
|
1300
|
+
this.start();
|
|
1030
1301
|
}
|
|
1031
1302
|
}
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
try {
|
|
1037
|
-
const response = await this.http.post(
|
|
1038
|
-
this.isServerMode() ? "/api/auth/users?client_type=mobile" : "/api/auth/users",
|
|
1039
|
-
request,
|
|
1040
|
-
{ credentials: "include", skipAuthRefresh: true }
|
|
1041
|
-
);
|
|
1042
|
-
if (response.accessToken && response.user) {
|
|
1043
|
-
this.saveSessionFromResponse(response);
|
|
1044
|
-
}
|
|
1045
|
-
if (response.refreshToken) {
|
|
1046
|
-
this.http.setRefreshToken(response.refreshToken);
|
|
1047
|
-
}
|
|
1048
|
-
return { data: response, error: null };
|
|
1049
|
-
} catch (error) {
|
|
1050
|
-
return wrapError(error, "An unexpected error occurred during sign up");
|
|
1303
|
+
/** Send a custom event. No-op outside the browser or when inactive. */
|
|
1304
|
+
track(name, props) {
|
|
1305
|
+
if (!this.active || typeof name !== "string" || !name) {
|
|
1306
|
+
return;
|
|
1051
1307
|
}
|
|
1308
|
+
const event = {
|
|
1309
|
+
e: "ev",
|
|
1310
|
+
n: name.slice(0, 64),
|
|
1311
|
+
dt: document.title
|
|
1312
|
+
};
|
|
1313
|
+
if (props && typeof props === "object") {
|
|
1314
|
+
event.p = props;
|
|
1315
|
+
}
|
|
1316
|
+
this.post(event);
|
|
1052
1317
|
}
|
|
1053
|
-
|
|
1318
|
+
/** Attach your own user id to subsequent events. */
|
|
1319
|
+
identify(id) {
|
|
1320
|
+
if (!this.active || typeof id !== "string" || !id) {
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
this.write("rwa_uid", id.slice(0, 64));
|
|
1324
|
+
}
|
|
1325
|
+
// -- internals -------------------------------------------------------------
|
|
1326
|
+
read(key) {
|
|
1054
1327
|
try {
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
{ credentials: "include", skipAuthRefresh: true }
|
|
1059
|
-
);
|
|
1060
|
-
this.saveSessionFromResponse(response);
|
|
1061
|
-
if (response.refreshToken) {
|
|
1062
|
-
this.http.setRefreshToken(response.refreshToken);
|
|
1063
|
-
}
|
|
1064
|
-
return { data: response, error: null };
|
|
1065
|
-
} catch (error) {
|
|
1066
|
-
return wrapError(error, "An unexpected error occurred during sign in");
|
|
1328
|
+
return localStorage.getItem(key) || this.mem[key] || "";
|
|
1329
|
+
} catch {
|
|
1330
|
+
return this.mem[key] || "";
|
|
1067
1331
|
}
|
|
1068
1332
|
}
|
|
1069
|
-
|
|
1333
|
+
write(key, value) {
|
|
1334
|
+
this.mem[key] = value;
|
|
1070
1335
|
try {
|
|
1071
|
-
|
|
1072
|
-
const serverMode = this.isServerMode();
|
|
1073
|
-
const csrfToken = !serverMode ? getCsrfToken() : null;
|
|
1074
|
-
await this.http.post(
|
|
1075
|
-
serverMode ? "/api/auth/logout?client_type=mobile" : "/api/auth/logout",
|
|
1076
|
-
void 0,
|
|
1077
|
-
{
|
|
1078
|
-
credentials: "include",
|
|
1079
|
-
skipAuthRefresh: true,
|
|
1080
|
-
...csrfToken ? { headers: { "X-CSRF-Token": csrfToken } } : {}
|
|
1081
|
-
}
|
|
1082
|
-
);
|
|
1083
|
-
} catch {
|
|
1084
|
-
}
|
|
1085
|
-
this.tokenManager.clearSession();
|
|
1086
|
-
this.http.setAuthToken(null);
|
|
1087
|
-
this.http.setRefreshToken(null);
|
|
1088
|
-
if (!this.isServerMode()) {
|
|
1089
|
-
clearCsrfToken();
|
|
1090
|
-
}
|
|
1091
|
-
return { error: null };
|
|
1336
|
+
localStorage.setItem(key, value);
|
|
1092
1337
|
} catch {
|
|
1093
|
-
return {
|
|
1094
|
-
error: new InsForgeError("Failed to sign out", 500, "SIGNOUT_ERROR")
|
|
1095
|
-
};
|
|
1096
1338
|
}
|
|
1097
1339
|
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
const
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1340
|
+
visitor() {
|
|
1341
|
+
let visitor = this.read("rwa_id");
|
|
1342
|
+
if (!visitor) {
|
|
1343
|
+
visitor = uuid4();
|
|
1344
|
+
this.write("rwa_id", visitor);
|
|
1345
|
+
}
|
|
1346
|
+
return visitor;
|
|
1347
|
+
}
|
|
1348
|
+
session() {
|
|
1349
|
+
const parts = this.read("rwa_s").split(".");
|
|
1350
|
+
let id = parts[0];
|
|
1351
|
+
const last = Number(parts[1]) || 0;
|
|
1352
|
+
const now = Date.now();
|
|
1353
|
+
if (!id || now - last > IDLE_MS) {
|
|
1354
|
+
id = uuid7();
|
|
1355
|
+
}
|
|
1356
|
+
this.write("rwa_s", `${id}.${now}`);
|
|
1357
|
+
return id;
|
|
1358
|
+
}
|
|
1359
|
+
pageUrl() {
|
|
1360
|
+
let url = location.href;
|
|
1361
|
+
if (this.options.excludeHash) {
|
|
1362
|
+
url = url.split("#")[0];
|
|
1363
|
+
}
|
|
1364
|
+
if (this.options.excludeSearch) {
|
|
1365
|
+
const hash = this.options.excludeHash ? "" : url.split("#")[1] || "";
|
|
1366
|
+
url = url.split("#")[0].split("?")[0] + (hash ? `#${hash}` : "");
|
|
1367
|
+
}
|
|
1368
|
+
return url.slice(0, 4096);
|
|
1369
|
+
}
|
|
1370
|
+
post(event) {
|
|
1371
|
+
event.u = event.u || this.pageUrl();
|
|
1372
|
+
event.d = this.visitor();
|
|
1373
|
+
event.s = this.session();
|
|
1374
|
+
event.t = Date.now();
|
|
1375
|
+
event.w = innerWidth;
|
|
1376
|
+
event.h = innerHeight;
|
|
1377
|
+
event.sw = screen.width;
|
|
1378
|
+
event.sh = screen.height;
|
|
1379
|
+
const uid = this.read("rwa_uid");
|
|
1380
|
+
if (uid) {
|
|
1381
|
+
event.uid = uid;
|
|
1382
|
+
}
|
|
1383
|
+
const body = JSON.stringify({ site: this.siteId, events: [event] });
|
|
1384
|
+
if (!navigator.sendBeacon?.(this.endpoint, body)) {
|
|
1385
|
+
fetch(this.endpoint, {
|
|
1386
|
+
method: "POST",
|
|
1387
|
+
body,
|
|
1388
|
+
keepalive: true,
|
|
1389
|
+
mode: "no-cors"
|
|
1390
|
+
}).catch(() => {
|
|
1138
1391
|
});
|
|
1139
|
-
if (!this.isServerMode() && typeof window !== "undefined" && !signInOptions.skipBrowserRedirect) {
|
|
1140
|
-
window.location.href = response.authUrl;
|
|
1141
|
-
return { data: {}, error: null };
|
|
1142
|
-
}
|
|
1143
|
-
return {
|
|
1144
|
-
data: { url: response.authUrl, provider: providerKey, codeVerifier },
|
|
1145
|
-
error: null
|
|
1146
|
-
};
|
|
1147
|
-
} catch (error) {
|
|
1148
|
-
if (error instanceof InsForgeError) {
|
|
1149
|
-
return { data: {}, error };
|
|
1150
|
-
}
|
|
1151
|
-
return {
|
|
1152
|
-
data: {},
|
|
1153
|
-
error: new InsForgeError(
|
|
1154
|
-
"An unexpected error occurred during OAuth initialization",
|
|
1155
|
-
500,
|
|
1156
|
-
"UNEXPECTED_ERROR"
|
|
1157
|
-
)
|
|
1158
|
-
};
|
|
1159
1392
|
}
|
|
1160
1393
|
}
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
async exchangeOAuthCode(code, codeVerifier) {
|
|
1166
|
-
try {
|
|
1167
|
-
const verifier = codeVerifier ?? retrievePkceVerifier();
|
|
1168
|
-
if (!verifier) {
|
|
1169
|
-
return {
|
|
1170
|
-
data: null,
|
|
1171
|
-
error: new InsForgeError(
|
|
1172
|
-
"PKCE code verifier not found. Ensure signInWithOAuth was called in the same browser session.",
|
|
1173
|
-
400,
|
|
1174
|
-
"PKCE_VERIFIER_MISSING"
|
|
1175
|
-
)
|
|
1176
|
-
};
|
|
1177
|
-
}
|
|
1178
|
-
const request = {
|
|
1179
|
-
code,
|
|
1180
|
-
code_verifier: verifier
|
|
1181
|
-
};
|
|
1182
|
-
const response = await this.http.post(
|
|
1183
|
-
this.isServerMode() ? "/api/auth/oauth/exchange?client_type=mobile" : "/api/auth/oauth/exchange",
|
|
1184
|
-
request,
|
|
1185
|
-
{ credentials: "include", skipAuthRefresh: true }
|
|
1186
|
-
);
|
|
1187
|
-
this.saveSessionFromResponse(response);
|
|
1188
|
-
return {
|
|
1189
|
-
data: response,
|
|
1190
|
-
error: null
|
|
1191
|
-
};
|
|
1192
|
-
} catch (error) {
|
|
1193
|
-
return wrapError(error, "An unexpected error occurred during OAuth code exchange");
|
|
1394
|
+
pageview() {
|
|
1395
|
+
const url = this.pageUrl();
|
|
1396
|
+
if (url === this.lastUrl) {
|
|
1397
|
+
return;
|
|
1194
1398
|
}
|
|
1399
|
+
const from = this.lastUrl ? this.lastUrl : this.referrer;
|
|
1400
|
+
this.lastUrl = url;
|
|
1401
|
+
this.left = false;
|
|
1402
|
+
this.post({ e: "pv", u: url, r: from, dt: document.title });
|
|
1195
1403
|
}
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
*
|
|
1200
|
-
* @param credentials.provider - The identity provider (currently only 'google' is supported)
|
|
1201
|
-
* @param credentials.token - The ID token from the native SDK
|
|
1202
|
-
*/
|
|
1203
|
-
async signInWithIdToken(credentials) {
|
|
1204
|
-
try {
|
|
1205
|
-
const { provider, token } = credentials;
|
|
1206
|
-
const response = await this.http.post(
|
|
1207
|
-
"/api/auth/id-token?client_type=mobile",
|
|
1208
|
-
{ provider, token },
|
|
1209
|
-
{ credentials: "include", skipAuthRefresh: true }
|
|
1210
|
-
);
|
|
1211
|
-
this.saveSessionFromResponse(response);
|
|
1212
|
-
if (response.refreshToken) {
|
|
1213
|
-
this.http.setRefreshToken(response.refreshToken);
|
|
1214
|
-
}
|
|
1215
|
-
return {
|
|
1216
|
-
data: response,
|
|
1217
|
-
error: null
|
|
1218
|
-
};
|
|
1219
|
-
} catch (error) {
|
|
1220
|
-
return wrapError(error, "An unexpected error occurred during ID token sign in");
|
|
1404
|
+
pageleave() {
|
|
1405
|
+
if (this.left || !this.lastUrl) {
|
|
1406
|
+
return;
|
|
1221
1407
|
}
|
|
1408
|
+
this.left = true;
|
|
1409
|
+
this.post({ e: "pl", u: this.lastUrl });
|
|
1222
1410
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
"refreshToken is required when refreshing session in server mode",
|
|
1245
|
-
400,
|
|
1246
|
-
ERROR_CODES.AUTH_UNAUTHORIZED
|
|
1247
|
-
)
|
|
1248
|
-
};
|
|
1249
|
-
}
|
|
1250
|
-
const csrfToken = !this.isServerMode() ? getCsrfToken() : null;
|
|
1251
|
-
const response = await this.http.post(
|
|
1252
|
-
this.isServerMode() ? "/api/auth/refresh?client_type=mobile" : "/api/auth/refresh",
|
|
1253
|
-
this.isServerMode() ? { refresh_token: options?.refreshToken } : void 0,
|
|
1254
|
-
{
|
|
1255
|
-
headers: csrfToken ? { "X-CSRF-Token": csrfToken } : {},
|
|
1256
|
-
credentials: "include",
|
|
1257
|
-
skipAuthRefresh: true
|
|
1411
|
+
start() {
|
|
1412
|
+
const onNav = () => {
|
|
1413
|
+
setTimeout(() => this.pageview(), 0);
|
|
1414
|
+
};
|
|
1415
|
+
if (this.options.autoTrack !== false) {
|
|
1416
|
+
const push = history.pushState;
|
|
1417
|
+
const replace = history.replaceState;
|
|
1418
|
+
history.pushState = function(...args) {
|
|
1419
|
+
push.apply(this, args);
|
|
1420
|
+
onNav();
|
|
1421
|
+
};
|
|
1422
|
+
history.replaceState = function(...args) {
|
|
1423
|
+
replace.apply(this, args);
|
|
1424
|
+
onNav();
|
|
1425
|
+
};
|
|
1426
|
+
addEventListener("popstate", onNav);
|
|
1427
|
+
addEventListener("hashchange", onNav);
|
|
1428
|
+
addEventListener("pageshow", (event) => {
|
|
1429
|
+
if (event.persisted) {
|
|
1430
|
+
this.lastUrl = null;
|
|
1431
|
+
this.pageview();
|
|
1258
1432
|
}
|
|
1259
|
-
);
|
|
1260
|
-
|
|
1261
|
-
this.saveSessionFromResponse(response, AuthChangeEvent.TOKEN_REFRESHED);
|
|
1262
|
-
}
|
|
1263
|
-
return { data: response, error: null };
|
|
1264
|
-
} catch (error) {
|
|
1265
|
-
return wrapError(error, "An unexpected error occurred during session refresh");
|
|
1433
|
+
});
|
|
1434
|
+
this.pageview();
|
|
1266
1435
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
try {
|
|
1274
|
-
if (this.isServerMode()) {
|
|
1275
|
-
const accessToken = this.tokenManager.getAccessToken();
|
|
1276
|
-
if (!accessToken) {
|
|
1277
|
-
return { data: { user: null }, error: null };
|
|
1278
|
-
}
|
|
1279
|
-
this.http.setAuthToken(accessToken);
|
|
1280
|
-
const response = await this.http.get("/api/auth/sessions/current");
|
|
1281
|
-
const user = response.user ?? null;
|
|
1282
|
-
return { data: { user }, error: null };
|
|
1283
|
-
}
|
|
1284
|
-
const session = this.tokenManager.getSession();
|
|
1285
|
-
if (session) {
|
|
1286
|
-
this.http.setAuthToken(session.accessToken);
|
|
1287
|
-
return { data: { user: session.user }, error: null };
|
|
1436
|
+
addEventListener("pagehide", () => this.pageleave());
|
|
1437
|
+
document.addEventListener("visibilitychange", () => {
|
|
1438
|
+
if (document.visibilityState === "hidden") {
|
|
1439
|
+
this.pageleave();
|
|
1440
|
+
} else {
|
|
1441
|
+
this.left = false;
|
|
1288
1442
|
}
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1443
|
+
});
|
|
1444
|
+
document.addEventListener(
|
|
1445
|
+
"click",
|
|
1446
|
+
(clickEvent) => {
|
|
1447
|
+
const source = clickEvent.target;
|
|
1448
|
+
const target = source?.closest ? source.closest("[data-wa-event]") : null;
|
|
1449
|
+
if (!target) {
|
|
1450
|
+
return;
|
|
1293
1451
|
}
|
|
1294
|
-
|
|
1295
|
-
|
|
1452
|
+
const name = target.getAttribute("data-wa-event");
|
|
1453
|
+
if (!name) {
|
|
1454
|
+
return;
|
|
1296
1455
|
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1456
|
+
const props = {};
|
|
1457
|
+
let any = false;
|
|
1458
|
+
for (const attr of Array.from(target.attributes)) {
|
|
1459
|
+
if (attr.name.indexOf("data-wa-event-") === 0) {
|
|
1460
|
+
props[attr.name.slice(14)] = attr.value;
|
|
1461
|
+
any = true;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
this.track(name, any ? props : void 0);
|
|
1465
|
+
},
|
|
1466
|
+
true
|
|
1467
|
+
);
|
|
1468
|
+
}
|
|
1469
|
+
};
|
|
1470
|
+
function bytes() {
|
|
1471
|
+
const b = new Uint8Array(16);
|
|
1472
|
+
crypto.getRandomValues(b);
|
|
1473
|
+
return b;
|
|
1474
|
+
}
|
|
1475
|
+
function format(b, version) {
|
|
1476
|
+
b[6] = b[6] & 15 | version << 4;
|
|
1477
|
+
b[8] = b[8] & 63 | 128;
|
|
1478
|
+
let hex = "";
|
|
1479
|
+
for (let i = 0; i < 16; i++) {
|
|
1480
|
+
hex += (b[i] + 256).toString(16).slice(1);
|
|
1481
|
+
if (i === 3 || i === 5 || i === 7 || i === 9) hex += "-";
|
|
1482
|
+
}
|
|
1483
|
+
return hex;
|
|
1484
|
+
}
|
|
1485
|
+
function uuid4() {
|
|
1486
|
+
return format(bytes(), 4);
|
|
1487
|
+
}
|
|
1488
|
+
function uuid7() {
|
|
1489
|
+
const b = bytes();
|
|
1490
|
+
let t = Date.now();
|
|
1491
|
+
for (let i = 5; i >= 0; i--) {
|
|
1492
|
+
b[i] = t & 255;
|
|
1493
|
+
t = Math.floor(t / 256);
|
|
1494
|
+
}
|
|
1495
|
+
return format(b, 7);
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
// src/types/api.ts
|
|
1499
|
+
var ERROR_CODE_VALUES = [
|
|
1500
|
+
// Auth
|
|
1501
|
+
"AUTH_INVALID_EMAIL",
|
|
1502
|
+
"AUTH_WEAK_PASSWORD",
|
|
1503
|
+
"AUTH_INVALID_CREDENTIALS",
|
|
1504
|
+
"AUTH_INVALID_API_KEY",
|
|
1505
|
+
"AUTH_EMAIL_EXISTS",
|
|
1506
|
+
"AUTH_USER_NOT_FOUND",
|
|
1507
|
+
"AUTH_OAUTH_CONFIG_ALREADY_EXISTS",
|
|
1508
|
+
"AUTH_OAUTH_CONFIG_ERROR",
|
|
1509
|
+
"AUTH_OAUTH_CONFIG_NOT_FOUND",
|
|
1510
|
+
"AUTH_UNSUPPORTED_PROVIDER",
|
|
1511
|
+
"AUTH_TOKEN_EXPIRED",
|
|
1512
|
+
"AUTH_UNAUTHORIZED",
|
|
1513
|
+
"AUTH_NEED_VERIFICATION",
|
|
1514
|
+
"AUTH_SIGNUP_DISABLED",
|
|
1515
|
+
"AUTH_VERIFICATION_EMAIL_DELIVERY_FAILED",
|
|
1516
|
+
// Database
|
|
1517
|
+
"DATABASE_INVALID_PARAMETER",
|
|
1518
|
+
"DATABASE_VALIDATION_ERROR",
|
|
1519
|
+
"DATABASE_CONSTRAINT_VIOLATION",
|
|
1520
|
+
"DATABASE_NOT_FOUND",
|
|
1521
|
+
"DATABASE_DUPLICATE",
|
|
1522
|
+
"DATABASE_MIGRATION_ALREADY_EXISTS",
|
|
1523
|
+
"DATABASE_PERMISSION_DENIED",
|
|
1524
|
+
"DATABASE_INTERNAL_ERROR",
|
|
1525
|
+
"DATABASE_FORBIDDEN",
|
|
1526
|
+
// Storage
|
|
1527
|
+
"STORAGE_ALREADY_EXISTS",
|
|
1528
|
+
"STORAGE_INVALID_PARAMETER",
|
|
1529
|
+
"STORAGE_INVALID_FILE_TYPE",
|
|
1530
|
+
"STORAGE_INSUFFICIENT_QUOTA",
|
|
1531
|
+
"STORAGE_NOT_FOUND",
|
|
1532
|
+
"STORAGE_PERMISSION_DENIED",
|
|
1533
|
+
"S3_ACCESS_KEY_LIMIT_EXCEEDED",
|
|
1534
|
+
"S3_ACCESS_KEY_NOT_FOUND",
|
|
1535
|
+
"S3_PROTOCOL_UNAVAILABLE",
|
|
1536
|
+
// Realtime
|
|
1537
|
+
"REALTIME_CHANNEL_NOT_FOUND",
|
|
1538
|
+
"REALTIME_CONNECTION_FAILED",
|
|
1539
|
+
"REALTIME_INVALID_CHANNEL_REQUEST",
|
|
1540
|
+
"REALTIME_INVALID_CHANNEL_PATTERN",
|
|
1541
|
+
"REALTIME_INVALID_EVENT",
|
|
1542
|
+
"REALTIME_NOT_SUBSCRIBED",
|
|
1543
|
+
"REALTIME_UNAUTHORIZED",
|
|
1544
|
+
// AI
|
|
1545
|
+
"AI_INVALID_API_KEY",
|
|
1546
|
+
"AI_INVALID_MODEL",
|
|
1547
|
+
"AI_UPSTREAM_UNAVAILABLE",
|
|
1548
|
+
// Analytics
|
|
1549
|
+
"ANALYTICS_NOT_CONNECTED",
|
|
1550
|
+
"ANALYTICS_UNAVAILABLE",
|
|
1551
|
+
// Logs
|
|
1552
|
+
"LOGS_AWS_NOT_CONFIGURED",
|
|
1553
|
+
"LOG_NOT_FOUND",
|
|
1554
|
+
// Compute
|
|
1555
|
+
"COMPUTE_CLOUD_UNAVAILABLE",
|
|
1556
|
+
"COMPUTE_NOT_CONFIGURED",
|
|
1557
|
+
"COMPUTE_PROVIDER_ERROR",
|
|
1558
|
+
"COMPUTE_SERVICE_NOT_FOUND",
|
|
1559
|
+
"COMPUTE_MACHINE_NOT_FOUND",
|
|
1560
|
+
"COMPUTE_SERVICE_NOT_CONFIGURED",
|
|
1561
|
+
"COMPUTE_SERVICE_DEPLOY_FAILED",
|
|
1562
|
+
"COMPUTE_SERVICE_ALREADY_EXISTS",
|
|
1563
|
+
"COMPUTE_SERVICE_START_FAILED",
|
|
1564
|
+
"COMPUTE_SERVICE_STOP_FAILED",
|
|
1565
|
+
"COMPUTE_SERVICE_DELETE_FAILED",
|
|
1566
|
+
"COMPUTE_REGION_CHANGE_NOT_SUPPORTED",
|
|
1567
|
+
"COMPUTE_QUOTA_EXCEEDED",
|
|
1568
|
+
// Billing
|
|
1569
|
+
"BILLING_INSUFFICIENT_BALANCE",
|
|
1570
|
+
// Email
|
|
1571
|
+
"EMAIL_PROVIDER_NOT_CONFIGURED",
|
|
1572
|
+
"EMAIL_SMTP_CONNECTION_FAILED",
|
|
1573
|
+
"EMAIL_SMTP_SEND_FAILED",
|
|
1574
|
+
"EMAIL_TEMPLATE_NOT_FOUND",
|
|
1575
|
+
// Deployments
|
|
1576
|
+
"DEPLOYMENT_ALREADY_EXISTS",
|
|
1577
|
+
"DEPLOYMENT_INVALID_FILE",
|
|
1578
|
+
"DEPLOYMENT_NOT_FOUND",
|
|
1579
|
+
"DEPLOYMENT_UPLOAD_CANCELED",
|
|
1580
|
+
"DOMAIN_ALREADY_EXISTS",
|
|
1581
|
+
"DOMAIN_INVALID",
|
|
1582
|
+
"DOMAIN_NOT_FOUND",
|
|
1583
|
+
"ENVIRONMENT_VARIABLE_NOT_FOUND",
|
|
1584
|
+
// Docs
|
|
1585
|
+
"DOCS_NOT_FOUND",
|
|
1586
|
+
// Functions
|
|
1587
|
+
"FUNCTION_ALREADY_EXISTS",
|
|
1588
|
+
"FUNCTION_DEPLOYMENT_NOT_FOUND",
|
|
1589
|
+
"FUNCTION_NOT_FOUND",
|
|
1590
|
+
// Schedules
|
|
1591
|
+
"SCHEDULE_INVALID_CRON",
|
|
1592
|
+
"SCHEDULE_NOT_FOUND",
|
|
1593
|
+
// Payments
|
|
1594
|
+
"PAYMENT_CHECKOUT_ALREADY_EXISTS",
|
|
1595
|
+
"PAYMENT_CONFIG_INVALID",
|
|
1596
|
+
"PAYMENT_CONFIG_NOT_FOUND",
|
|
1597
|
+
"PAYMENT_NOT_FOUND",
|
|
1598
|
+
"PAYMENT_METHOD_DECLINED",
|
|
1599
|
+
"PAYMENT_PRICE_NOT_FOUND",
|
|
1600
|
+
"PAYMENT_PRODUCT_NOT_FOUND",
|
|
1601
|
+
// Secrets
|
|
1602
|
+
"SECRET_ALREADY_EXISTS",
|
|
1603
|
+
"SECRET_NOT_FOUND",
|
|
1604
|
+
// General
|
|
1605
|
+
"MISSING_FIELD",
|
|
1606
|
+
"ALREADY_EXISTS",
|
|
1607
|
+
"INVALID_INPUT",
|
|
1608
|
+
"NOT_FOUND",
|
|
1609
|
+
"UNKNOWN_ERROR",
|
|
1610
|
+
"INTERNAL_ERROR",
|
|
1611
|
+
"TOO_MANY_REQUESTS",
|
|
1612
|
+
"FORBIDDEN",
|
|
1613
|
+
"RATE_LIMITED",
|
|
1614
|
+
"NOT_IMPLEMENTED",
|
|
1615
|
+
"UPSTREAM_FAILURE"
|
|
1616
|
+
];
|
|
1617
|
+
var ERROR_CODES = Object.fromEntries(
|
|
1618
|
+
ERROR_CODE_VALUES.map((code) => [code, code])
|
|
1619
|
+
);
|
|
1620
|
+
var OAUTH_PROVIDERS = [
|
|
1621
|
+
"google",
|
|
1622
|
+
"github",
|
|
1623
|
+
"discord",
|
|
1624
|
+
"linkedin",
|
|
1625
|
+
"facebook",
|
|
1626
|
+
"instagram",
|
|
1627
|
+
"tiktok",
|
|
1628
|
+
"apple",
|
|
1629
|
+
"x",
|
|
1630
|
+
"spotify",
|
|
1631
|
+
"microsoft"
|
|
1632
|
+
];
|
|
1633
|
+
|
|
1634
|
+
// src/modules/auth/helpers.ts
|
|
1635
|
+
var PKCE_VERIFIER_KEY = "result_pkce_verifier";
|
|
1636
|
+
async function getWebCrypto() {
|
|
1637
|
+
const webCrypto = globalThis.crypto;
|
|
1638
|
+
if (typeof webCrypto?.getRandomValues === "function" && webCrypto.subtle) {
|
|
1639
|
+
return webCrypto;
|
|
1640
|
+
}
|
|
1641
|
+
if (typeof process !== "undefined" && process.versions?.node) {
|
|
1642
|
+
const { webcrypto } = await import("crypto");
|
|
1643
|
+
return webcrypto;
|
|
1644
|
+
}
|
|
1645
|
+
throw new Error("Web Crypto API is not available in this environment");
|
|
1646
|
+
}
|
|
1647
|
+
function base64UrlEncode(buffer) {
|
|
1648
|
+
const base64 = btoa(String.fromCharCode(...buffer));
|
|
1649
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1650
|
+
}
|
|
1651
|
+
async function generateCodeVerifier() {
|
|
1652
|
+
const webCrypto = await getWebCrypto();
|
|
1653
|
+
const array = new Uint8Array(32);
|
|
1654
|
+
webCrypto.getRandomValues(array);
|
|
1655
|
+
return base64UrlEncode(array);
|
|
1656
|
+
}
|
|
1657
|
+
async function generateCodeChallenge(verifier) {
|
|
1658
|
+
const webCrypto = await getWebCrypto();
|
|
1659
|
+
const encoder = new TextEncoder();
|
|
1660
|
+
const data = encoder.encode(verifier);
|
|
1661
|
+
const hash = await webCrypto.subtle.digest("SHA-256", data);
|
|
1662
|
+
return base64UrlEncode(new Uint8Array(hash));
|
|
1663
|
+
}
|
|
1664
|
+
function storePkceVerifier(verifier) {
|
|
1665
|
+
if (typeof sessionStorage !== "undefined") {
|
|
1666
|
+
sessionStorage.setItem(PKCE_VERIFIER_KEY, verifier);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
function retrievePkceVerifier() {
|
|
1670
|
+
if (typeof sessionStorage === "undefined") {
|
|
1671
|
+
return null;
|
|
1672
|
+
}
|
|
1673
|
+
const verifier = sessionStorage.getItem(PKCE_VERIFIER_KEY);
|
|
1674
|
+
if (verifier) {
|
|
1675
|
+
sessionStorage.removeItem(PKCE_VERIFIER_KEY);
|
|
1676
|
+
}
|
|
1677
|
+
return verifier;
|
|
1678
|
+
}
|
|
1679
|
+
function wrapError(error, fallbackMessage) {
|
|
1680
|
+
if (error instanceof ResultError) {
|
|
1681
|
+
return { data: null, error };
|
|
1682
|
+
}
|
|
1683
|
+
return {
|
|
1684
|
+
data: null,
|
|
1685
|
+
error: new ResultError(
|
|
1686
|
+
error instanceof Error ? error.message : fallbackMessage,
|
|
1687
|
+
500,
|
|
1688
|
+
"UNEXPECTED_ERROR"
|
|
1689
|
+
)
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1692
|
+
function cleanUrlParams(...params) {
|
|
1693
|
+
if (typeof window === "undefined") {
|
|
1694
|
+
return;
|
|
1695
|
+
}
|
|
1696
|
+
const url = new URL(window.location.href);
|
|
1697
|
+
for (const p of params) {
|
|
1698
|
+
url.searchParams.delete(p);
|
|
1699
|
+
}
|
|
1700
|
+
window.history.replaceState({}, document.title, url.toString());
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
// src/modules/auth/auth.ts
|
|
1704
|
+
var Auth = class {
|
|
1705
|
+
constructor(http, tokenManager, options = {}) {
|
|
1706
|
+
this.http = http;
|
|
1707
|
+
this.tokenManager = tokenManager;
|
|
1708
|
+
this.options = options;
|
|
1709
|
+
this.authCallbackHandled = options.detectOAuthCallback === false ? Promise.resolve() : this.detectAuthCallback();
|
|
1710
|
+
}
|
|
1711
|
+
http;
|
|
1712
|
+
tokenManager;
|
|
1713
|
+
options;
|
|
1714
|
+
authCallbackHandled;
|
|
1715
|
+
isServerMode() {
|
|
1716
|
+
return !!this.options.isServerMode;
|
|
1717
|
+
}
|
|
1718
|
+
/** Subscribe to SDK authentication state changes. */
|
|
1719
|
+
onAuthStateChange(callback) {
|
|
1720
|
+
return this.tokenManager.onAuthStateChange(callback);
|
|
1721
|
+
}
|
|
1722
|
+
/**
|
|
1723
|
+
* Save session from API response
|
|
1724
|
+
* Handles token storage, CSRF token, and HTTP auth header
|
|
1725
|
+
*/
|
|
1726
|
+
saveSessionFromResponse(response, event = AuthChangeEvent.SIGNED_IN) {
|
|
1727
|
+
if (!response.accessToken || !response.user) {
|
|
1728
|
+
return false;
|
|
1729
|
+
}
|
|
1730
|
+
const session = {
|
|
1731
|
+
accessToken: response.accessToken,
|
|
1732
|
+
user: response.user
|
|
1733
|
+
};
|
|
1734
|
+
if (!this.isServerMode() && response.csrfToken) {
|
|
1735
|
+
setCsrfToken(response.csrfToken);
|
|
1736
|
+
}
|
|
1737
|
+
if (!this.isServerMode()) {
|
|
1738
|
+
this.tokenManager.saveSession(session, event);
|
|
1311
1739
|
}
|
|
1740
|
+
this.http.setAuthToken(response.accessToken);
|
|
1741
|
+
this.http.setRefreshToken(response.refreshToken ?? null);
|
|
1742
|
+
return true;
|
|
1312
1743
|
}
|
|
1313
1744
|
// ============================================================================
|
|
1314
|
-
//
|
|
1745
|
+
// OAuth Callback Detection (runs on initialization)
|
|
1315
1746
|
// ============================================================================
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1747
|
+
/**
|
|
1748
|
+
* Detect and handle OAuth callback parameters in URL
|
|
1749
|
+
* Supports the PKCE flow (OAuth callback code in the URL)
|
|
1750
|
+
*/
|
|
1751
|
+
async detectAuthCallback() {
|
|
1752
|
+
if (this.isServerMode() || typeof window === "undefined") {
|
|
1753
|
+
return;
|
|
1322
1754
|
}
|
|
1323
|
-
}
|
|
1324
|
-
async setProfile(profile) {
|
|
1325
1755
|
try {
|
|
1326
|
-
const
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1756
|
+
const params = new URLSearchParams(window.location.search);
|
|
1757
|
+
const error = params.get("error");
|
|
1758
|
+
if (error) {
|
|
1759
|
+
cleanUrlParams("error");
|
|
1760
|
+
console.debug("OAuth callback error:", error);
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
|
+
const code = params.get("insforge_code");
|
|
1764
|
+
if (code) {
|
|
1765
|
+
cleanUrlParams("insforge_code");
|
|
1766
|
+
const { error: exchangeError } = await this.exchangeOAuthCode(code);
|
|
1767
|
+
if (exchangeError) {
|
|
1768
|
+
console.debug("OAuth code exchange failed:", exchangeError.message);
|
|
1769
|
+
}
|
|
1770
|
+
return;
|
|
1335
1771
|
}
|
|
1336
|
-
return { data: response, error: null };
|
|
1337
1772
|
} catch (error) {
|
|
1338
|
-
|
|
1773
|
+
console.debug("OAuth callback detection skipped:", error);
|
|
1339
1774
|
}
|
|
1340
1775
|
}
|
|
1341
1776
|
// ============================================================================
|
|
1342
|
-
//
|
|
1777
|
+
// Sign Up / Sign In / Sign Out
|
|
1343
1778
|
// ============================================================================
|
|
1344
|
-
async
|
|
1345
|
-
try {
|
|
1346
|
-
const response = await this.http.post("/api/auth/email/send-verification", request, {
|
|
1347
|
-
skipAuthRefresh: true
|
|
1348
|
-
});
|
|
1349
|
-
return { data: response, error: null };
|
|
1350
|
-
} catch (error) {
|
|
1351
|
-
return wrapError(error, "An unexpected error occurred while sending verification email");
|
|
1352
|
-
}
|
|
1353
|
-
}
|
|
1354
|
-
async verifyEmail(request) {
|
|
1779
|
+
async signUp(request) {
|
|
1355
1780
|
try {
|
|
1356
1781
|
const response = await this.http.post(
|
|
1357
|
-
this.isServerMode() ? "/api/auth/
|
|
1782
|
+
this.isServerMode() ? "/api/auth/users?client_type=mobile" : "/api/auth/users",
|
|
1358
1783
|
request,
|
|
1359
1784
|
{ credentials: "include", skipAuthRefresh: true }
|
|
1360
1785
|
);
|
|
1361
|
-
|
|
1786
|
+
if (response.accessToken && response.user) {
|
|
1787
|
+
this.saveSessionFromResponse(response);
|
|
1788
|
+
}
|
|
1362
1789
|
if (response.refreshToken) {
|
|
1363
1790
|
this.http.setRefreshToken(response.refreshToken);
|
|
1364
1791
|
}
|
|
1365
1792
|
return { data: response, error: null };
|
|
1366
1793
|
} catch (error) {
|
|
1367
|
-
return wrapError(error, "An unexpected error occurred
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
|
-
// ============================================================================
|
|
1371
|
-
// Password Reset
|
|
1372
|
-
// ============================================================================
|
|
1373
|
-
async sendResetPasswordEmail(request) {
|
|
1374
|
-
try {
|
|
1375
|
-
const response = await this.http.post("/api/auth/email/send-reset-password", request, {
|
|
1376
|
-
skipAuthRefresh: true
|
|
1377
|
-
});
|
|
1378
|
-
return { data: response, error: null };
|
|
1379
|
-
} catch (error) {
|
|
1380
|
-
return wrapError(error, "An unexpected error occurred while sending password reset email");
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
async exchangeResetPasswordToken(request) {
|
|
1384
|
-
try {
|
|
1385
|
-
const response = await this.http.post(
|
|
1386
|
-
"/api/auth/email/exchange-reset-password-token",
|
|
1387
|
-
request,
|
|
1388
|
-
{ skipAuthRefresh: true }
|
|
1389
|
-
);
|
|
1390
|
-
return { data: response, error: null };
|
|
1391
|
-
} catch (error) {
|
|
1392
|
-
return wrapError(error, "An unexpected error occurred while verifying reset code");
|
|
1794
|
+
return wrapError(error, "An unexpected error occurred during sign up");
|
|
1393
1795
|
}
|
|
1394
1796
|
}
|
|
1395
|
-
async
|
|
1797
|
+
async signInWithPassword(request) {
|
|
1396
1798
|
try {
|
|
1397
1799
|
const response = await this.http.post(
|
|
1398
|
-
"/api/auth/
|
|
1800
|
+
this.isServerMode() ? "/api/auth/sessions?client_type=mobile" : "/api/auth/sessions",
|
|
1399
1801
|
request,
|
|
1400
|
-
{ skipAuthRefresh: true }
|
|
1401
|
-
);
|
|
1402
|
-
return { data: response, error: null };
|
|
1403
|
-
} catch (error) {
|
|
1404
|
-
return wrapError(error, "An unexpected error occurred while resetting password");
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
// ============================================================================
|
|
1408
|
-
// Configuration
|
|
1409
|
-
// ============================================================================
|
|
1410
|
-
async getPublicAuthConfig() {
|
|
1411
|
-
try {
|
|
1412
|
-
const response = await this.http.get("/api/auth/public-config", {
|
|
1413
|
-
skipAuthRefresh: true
|
|
1414
|
-
});
|
|
1415
|
-
return { data: response, error: null };
|
|
1416
|
-
} catch (error) {
|
|
1417
|
-
return wrapError(error, "An unexpected error occurred while fetching auth configuration");
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
};
|
|
1421
|
-
function createInsForgePostgrestFetch(httpClient) {
|
|
1422
|
-
return async (input, init) => {
|
|
1423
|
-
const url = typeof input === "string" ? input : input.toString();
|
|
1424
|
-
const urlObj = new URL(url);
|
|
1425
|
-
const pathname = urlObj.pathname.slice(1);
|
|
1426
|
-
const rpcMatch = pathname.match(/^rpc\/(.+)$/);
|
|
1427
|
-
const endpoint = rpcMatch ? `/api/database/rpc/${rpcMatch[1]}` : `/api/database/records/${pathname}`;
|
|
1428
|
-
const insforgeUrl = `${httpClient.baseUrl}${endpoint}${urlObj.search}`;
|
|
1429
|
-
const headers = new Headers(httpClient.getHeaders());
|
|
1430
|
-
new Headers(init?.headers).forEach((value, key) => {
|
|
1431
|
-
headers.set(key, value);
|
|
1432
|
-
});
|
|
1433
|
-
const response = await httpClient.rawFetch(insforgeUrl, {
|
|
1434
|
-
...init,
|
|
1435
|
-
headers
|
|
1436
|
-
});
|
|
1437
|
-
return response;
|
|
1438
|
-
};
|
|
1439
|
-
}
|
|
1440
|
-
var Database = class {
|
|
1441
|
-
constructor(httpClient, defaultSchema) {
|
|
1442
|
-
this.postgrest = new PostgrestClient("http://dummy", {
|
|
1443
|
-
fetch: createInsForgePostgrestFetch(httpClient),
|
|
1444
|
-
headers: {},
|
|
1445
|
-
...defaultSchema ? { schema: defaultSchema } : {}
|
|
1446
|
-
});
|
|
1447
|
-
}
|
|
1448
|
-
/**
|
|
1449
|
-
* Select a non-default Postgres schema for the chained query. Maps to
|
|
1450
|
-
* PostgREST's `Accept-Profile` (reads) / `Content-Profile` (writes) header.
|
|
1451
|
-
* The schema must be exposed by the backend.
|
|
1452
|
-
*
|
|
1453
|
-
* @example
|
|
1454
|
-
* const { data } = await client.database
|
|
1455
|
-
* .schema('analytics')
|
|
1456
|
-
* .from('events')
|
|
1457
|
-
* .select('*');
|
|
1458
|
-
*
|
|
1459
|
-
* @example
|
|
1460
|
-
* await client.database.schema('analytics').rpc('rollup', { day: '2026-01-01' });
|
|
1461
|
-
*/
|
|
1462
|
-
schema(schemaName) {
|
|
1463
|
-
return this.postgrest.schema(schemaName);
|
|
1464
|
-
}
|
|
1465
|
-
/**
|
|
1466
|
-
* Create a query builder for a table
|
|
1467
|
-
*
|
|
1468
|
-
* @example
|
|
1469
|
-
* // Basic query
|
|
1470
|
-
* const { data, error } = await client.database
|
|
1471
|
-
* .from('posts')
|
|
1472
|
-
* .select('*')
|
|
1473
|
-
* .eq('user_id', userId);
|
|
1474
|
-
*
|
|
1475
|
-
* // With count (Supabase style!)
|
|
1476
|
-
* const { data, error, count } = await client.database
|
|
1477
|
-
* .from('posts')
|
|
1478
|
-
* .select('*', { count: 'exact' })
|
|
1479
|
-
* .range(0, 9);
|
|
1480
|
-
*
|
|
1481
|
-
* // Just get count, no data
|
|
1482
|
-
* const { count } = await client.database
|
|
1483
|
-
* .from('posts')
|
|
1484
|
-
* .select('*', { count: 'exact', head: true });
|
|
1485
|
-
*
|
|
1486
|
-
* // Complex queries with OR
|
|
1487
|
-
* const { data } = await client.database
|
|
1488
|
-
* .from('posts')
|
|
1489
|
-
* .select('*, users!inner(*)')
|
|
1490
|
-
* .or('status.eq.active,status.eq.pending');
|
|
1491
|
-
*
|
|
1492
|
-
* // All features work:
|
|
1493
|
-
* - Nested selects
|
|
1494
|
-
* - Foreign key expansion
|
|
1495
|
-
* - OR/AND/NOT conditions
|
|
1496
|
-
* - Count with head
|
|
1497
|
-
* - Range pagination
|
|
1498
|
-
* - Upserts
|
|
1499
|
-
*/
|
|
1500
|
-
from(table) {
|
|
1501
|
-
return this.postgrest.from(table);
|
|
1502
|
-
}
|
|
1503
|
-
/**
|
|
1504
|
-
* Call a PostgreSQL function (RPC)
|
|
1505
|
-
*
|
|
1506
|
-
* @example
|
|
1507
|
-
* // Call a function with parameters
|
|
1508
|
-
* const { data, error } = await client.database
|
|
1509
|
-
* .rpc('get_user_stats', { user_id: 123 });
|
|
1510
|
-
*
|
|
1511
|
-
* // Call a function with no parameters
|
|
1512
|
-
* const { data, error } = await client.database
|
|
1513
|
-
* .rpc('get_all_active_users');
|
|
1514
|
-
*
|
|
1515
|
-
* // With options (head, count, get)
|
|
1516
|
-
* const { data, count } = await client.database
|
|
1517
|
-
* .rpc('search_posts', { query: 'hello' }, { count: 'exact' });
|
|
1518
|
-
*/
|
|
1519
|
-
rpc(fn, args, options) {
|
|
1520
|
-
return this.postgrest.rpc(fn, args, options);
|
|
1521
|
-
}
|
|
1522
|
-
};
|
|
1523
|
-
var StorageBucket = class {
|
|
1524
|
-
constructor(bucketName, http) {
|
|
1525
|
-
this.bucketName = bucketName;
|
|
1526
|
-
this.http = http;
|
|
1527
|
-
}
|
|
1528
|
-
/**
|
|
1529
|
-
* Upload a file with a specific key
|
|
1530
|
-
* Uses the upload strategy from backend (direct or presigned)
|
|
1531
|
-
* @param path - The object key/path
|
|
1532
|
-
* @param file - File or Blob to upload
|
|
1533
|
-
*/
|
|
1534
|
-
async upload(path, file) {
|
|
1535
|
-
try {
|
|
1536
|
-
const strategyResponse = await this.http.post(
|
|
1537
|
-
`/api/storage/buckets/${this.bucketName}/upload-strategy`,
|
|
1538
|
-
{
|
|
1539
|
-
filename: path,
|
|
1540
|
-
contentType: file.type || "application/octet-stream",
|
|
1541
|
-
size: file.size
|
|
1542
|
-
}
|
|
1802
|
+
{ credentials: "include", skipAuthRefresh: true }
|
|
1543
1803
|
);
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
if (strategyResponse.method === "direct") {
|
|
1548
|
-
const formData = new FormData();
|
|
1549
|
-
formData.append("file", file);
|
|
1550
|
-
const response = await this.http.request(
|
|
1551
|
-
"PUT",
|
|
1552
|
-
`/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(path)}`,
|
|
1553
|
-
{
|
|
1554
|
-
body: formData,
|
|
1555
|
-
headers: {
|
|
1556
|
-
// Don't set Content-Type, let browser set multipart boundary
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1559
|
-
);
|
|
1560
|
-
return { data: response, error: null };
|
|
1804
|
+
this.saveSessionFromResponse(response);
|
|
1805
|
+
if (response.refreshToken) {
|
|
1806
|
+
this.http.setRefreshToken(response.refreshToken);
|
|
1561
1807
|
}
|
|
1562
|
-
|
|
1563
|
-
`Unsupported upload method: ${strategyResponse.method}`,
|
|
1564
|
-
500,
|
|
1565
|
-
"STORAGE_ERROR"
|
|
1566
|
-
);
|
|
1808
|
+
return { data: response, error: null };
|
|
1567
1809
|
} catch (error) {
|
|
1568
|
-
return
|
|
1569
|
-
data: null,
|
|
1570
|
-
error: error instanceof InsForgeError ? error : new InsForgeError("Upload failed", 500, "STORAGE_ERROR")
|
|
1571
|
-
};
|
|
1810
|
+
return wrapError(error, "An unexpected error occurred during sign in");
|
|
1572
1811
|
}
|
|
1573
1812
|
}
|
|
1574
|
-
|
|
1575
|
-
* Upload a file with auto-generated key
|
|
1576
|
-
* Uses the upload strategy from backend (direct or presigned)
|
|
1577
|
-
* @param file - File or Blob to upload
|
|
1578
|
-
*/
|
|
1579
|
-
async uploadAuto(file) {
|
|
1813
|
+
async signOut() {
|
|
1580
1814
|
try {
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
size: file.size
|
|
1588
|
-
}
|
|
1589
|
-
);
|
|
1590
|
-
if (strategyResponse.method === "presigned") {
|
|
1591
|
-
return await this.uploadWithPresignedUrl(strategyResponse, file);
|
|
1592
|
-
}
|
|
1593
|
-
if (strategyResponse.method === "direct") {
|
|
1594
|
-
const formData = new FormData();
|
|
1595
|
-
formData.append("file", file);
|
|
1596
|
-
const response = await this.http.request(
|
|
1597
|
-
"POST",
|
|
1598
|
-
`/api/storage/buckets/${this.bucketName}/objects`,
|
|
1815
|
+
try {
|
|
1816
|
+
const serverMode = this.isServerMode();
|
|
1817
|
+
const csrfToken = !serverMode ? getCsrfToken() : null;
|
|
1818
|
+
await this.http.post(
|
|
1819
|
+
serverMode ? "/api/auth/logout?client_type=mobile" : "/api/auth/logout",
|
|
1820
|
+
void 0,
|
|
1599
1821
|
{
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
}
|
|
1822
|
+
credentials: "include",
|
|
1823
|
+
skipAuthRefresh: true,
|
|
1824
|
+
...csrfToken ? { headers: { "X-CSRF-Token": csrfToken } } : {}
|
|
1604
1825
|
}
|
|
1605
1826
|
);
|
|
1606
|
-
|
|
1827
|
+
} catch {
|
|
1607
1828
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1829
|
+
this.tokenManager.clearSession();
|
|
1830
|
+
this.http.setAuthToken(null);
|
|
1831
|
+
this.http.setRefreshToken(null);
|
|
1832
|
+
if (!this.isServerMode()) {
|
|
1833
|
+
clearCsrfToken();
|
|
1834
|
+
}
|
|
1835
|
+
return { error: null };
|
|
1836
|
+
} catch {
|
|
1614
1837
|
return {
|
|
1615
|
-
|
|
1616
|
-
error: error instanceof InsForgeError ? error : new InsForgeError("Upload failed", 500, "STORAGE_ERROR")
|
|
1838
|
+
error: new ResultError("Failed to sign out", 500, "SIGNOUT_ERROR")
|
|
1617
1839
|
};
|
|
1618
1840
|
}
|
|
1619
1841
|
}
|
|
1620
|
-
|
|
1621
|
-
* Internal method to handle presigned URL uploads
|
|
1622
|
-
*/
|
|
1623
|
-
async uploadWithPresignedUrl(strategy, file) {
|
|
1842
|
+
async signInWithOAuth(providerOrOptions, options) {
|
|
1624
1843
|
try {
|
|
1625
|
-
|
|
1626
|
-
if (
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
}
|
|
1844
|
+
let signInOptions;
|
|
1845
|
+
if (typeof providerOrOptions === "object") {
|
|
1846
|
+
signInOptions = providerOrOptions;
|
|
1847
|
+
} else if (options) {
|
|
1848
|
+
signInOptions = { provider: providerOrOptions, ...options };
|
|
1849
|
+
} else {
|
|
1850
|
+
return {
|
|
1851
|
+
data: {},
|
|
1852
|
+
error: new ResultError(
|
|
1853
|
+
"OAuth sign-in options are required",
|
|
1854
|
+
400,
|
|
1855
|
+
ERROR_CODES.INVALID_INPUT
|
|
1856
|
+
)
|
|
1857
|
+
};
|
|
1630
1858
|
}
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
"STORAGE_ERROR"
|
|
1641
|
-
);
|
|
1859
|
+
if (!signInOptions || !signInOptions.redirectTo) {
|
|
1860
|
+
return {
|
|
1861
|
+
data: {},
|
|
1862
|
+
error: new ResultError(
|
|
1863
|
+
"Redirect URI is required",
|
|
1864
|
+
400,
|
|
1865
|
+
ERROR_CODES.INVALID_INPUT
|
|
1866
|
+
)
|
|
1867
|
+
};
|
|
1642
1868
|
}
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1869
|
+
const { provider } = signInOptions;
|
|
1870
|
+
const providerKey = encodeURIComponent(provider.toLowerCase());
|
|
1871
|
+
const codeVerifier = await generateCodeVerifier();
|
|
1872
|
+
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
1873
|
+
storePkceVerifier(codeVerifier);
|
|
1874
|
+
const params = {
|
|
1875
|
+
...signInOptions.additionalParams ?? {},
|
|
1876
|
+
redirect_uri: signInOptions.redirectTo,
|
|
1877
|
+
code_challenge: codeChallenge
|
|
1878
|
+
};
|
|
1879
|
+
const isBuiltInProvider = OAUTH_PROVIDERS.includes(
|
|
1880
|
+
providerKey
|
|
1881
|
+
);
|
|
1882
|
+
const oauthPath = isBuiltInProvider ? `/api/auth/oauth/${providerKey}` : `/api/auth/oauth/custom/${providerKey}`;
|
|
1883
|
+
const response = await this.http.get(oauthPath, {
|
|
1884
|
+
params,
|
|
1885
|
+
skipAuthRefresh: true
|
|
1886
|
+
});
|
|
1887
|
+
if (!this.isServerMode() && typeof window !== "undefined" && !signInOptions.skipBrowserRedirect) {
|
|
1888
|
+
window.location.href = response.authUrl;
|
|
1889
|
+
return { data: {}, error: null };
|
|
1649
1890
|
}
|
|
1650
1891
|
return {
|
|
1651
|
-
data: {
|
|
1652
|
-
key: strategy.key,
|
|
1653
|
-
bucket: this.bucketName,
|
|
1654
|
-
size: file.size,
|
|
1655
|
-
mimeType: file.type || "application/octet-stream",
|
|
1656
|
-
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1657
|
-
url: this.getPublicUrl(strategy.key).data.publicUrl
|
|
1658
|
-
},
|
|
1892
|
+
data: { url: response.authUrl, provider: providerKey, codeVerifier },
|
|
1659
1893
|
error: null
|
|
1660
1894
|
};
|
|
1661
1895
|
} catch (error) {
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
}
|
|
1665
|
-
/**
|
|
1666
|
-
* Download a file
|
|
1667
|
-
* Uses the download strategy from backend (direct or presigned)
|
|
1668
|
-
* @param path - The object key/path
|
|
1669
|
-
* Returns the file as a Blob
|
|
1670
|
-
*/
|
|
1671
|
-
async download(path) {
|
|
1672
|
-
try {
|
|
1673
|
-
const encodedKey = encodeURIComponent(path);
|
|
1674
|
-
let strategyResponse;
|
|
1675
|
-
try {
|
|
1676
|
-
strategyResponse = await this.http.get(
|
|
1677
|
-
`/api/storage/buckets/${this.bucketName}/download-strategy/objects/${encodedKey}`
|
|
1678
|
-
);
|
|
1679
|
-
} catch (err) {
|
|
1680
|
-
const status = err instanceof InsForgeError ? err.statusCode : void 0;
|
|
1681
|
-
if (status === 404 || status === 405) {
|
|
1682
|
-
strategyResponse = await this.http.post(
|
|
1683
|
-
`/api/storage/buckets/${this.bucketName}/objects/${encodedKey}/download-strategy`,
|
|
1684
|
-
{}
|
|
1685
|
-
);
|
|
1686
|
-
} else {
|
|
1687
|
-
throw err;
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
const downloadUrl = strategyResponse.url;
|
|
1691
|
-
const headers = {};
|
|
1692
|
-
if (strategyResponse.method === "direct") {
|
|
1693
|
-
Object.assign(headers, this.http.getHeaders());
|
|
1694
|
-
}
|
|
1695
|
-
const response = await fetch(downloadUrl, {
|
|
1696
|
-
method: "GET",
|
|
1697
|
-
headers
|
|
1698
|
-
});
|
|
1699
|
-
if (!response.ok) {
|
|
1700
|
-
try {
|
|
1701
|
-
const error = await response.json();
|
|
1702
|
-
throw InsForgeError.fromApiError(error);
|
|
1703
|
-
} catch {
|
|
1704
|
-
throw new InsForgeError(
|
|
1705
|
-
`Download failed: ${response.statusText}`,
|
|
1706
|
-
response.status,
|
|
1707
|
-
"STORAGE_ERROR"
|
|
1708
|
-
);
|
|
1709
|
-
}
|
|
1896
|
+
if (error instanceof ResultError) {
|
|
1897
|
+
return { data: {}, error };
|
|
1710
1898
|
}
|
|
1711
|
-
const blob = await response.blob();
|
|
1712
|
-
return { data: blob, error: null };
|
|
1713
|
-
} catch (error) {
|
|
1714
1899
|
return {
|
|
1715
|
-
data:
|
|
1716
|
-
error:
|
|
1900
|
+
data: {},
|
|
1901
|
+
error: new ResultError(
|
|
1902
|
+
"An unexpected error occurred during OAuth initialization",
|
|
1903
|
+
500,
|
|
1904
|
+
"UNEXPECTED_ERROR"
|
|
1905
|
+
)
|
|
1717
1906
|
};
|
|
1718
1907
|
}
|
|
1719
1908
|
}
|
|
1720
1909
|
/**
|
|
1721
|
-
*
|
|
1722
|
-
*
|
|
1723
|
-
* Pure string construction — no network call, no auth. The URL only resolves
|
|
1724
|
-
* if the bucket is public; for private objects use {@link createSignedUrl}.
|
|
1725
|
-
*
|
|
1726
|
-
* @param path - The object key/path
|
|
1727
|
-
* @returns `{ data: { publicUrl }, error }` — matches the external SDK pattern,
|
|
1728
|
-
* so `const { data } = getPublicUrl(path)` then `data.publicUrl`.
|
|
1729
|
-
*/
|
|
1730
|
-
getPublicUrl(path) {
|
|
1731
|
-
const publicUrl = `${this.http.baseUrl}/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(path)}`;
|
|
1732
|
-
return { data: { publicUrl }, error: null };
|
|
1733
|
-
}
|
|
1734
|
-
/**
|
|
1735
|
-
* Resolve a download strategy (signed or direct URL) for an object with a
|
|
1736
|
-
* caller-supplied TTL. Prefers the canonical GET route and falls back to the
|
|
1737
|
-
* legacy POST alias so signed-URL creation still works against older backends
|
|
1738
|
-
* that predate the GET route (they return 404/405 for it). A genuine
|
|
1739
|
-
* "object not found" (STORAGE_NOT_FOUND) is not retried.
|
|
1910
|
+
* Exchange OAuth authorization code for tokens (PKCE flow)
|
|
1911
|
+
* Called automatically on initialization when the OAuth callback code is in the URL
|
|
1740
1912
|
*/
|
|
1741
|
-
async
|
|
1742
|
-
const encoded = encodeURIComponent(path);
|
|
1913
|
+
async exchangeOAuthCode(code, codeVerifier) {
|
|
1743
1914
|
try {
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1915
|
+
const verifier = codeVerifier ?? retrievePkceVerifier();
|
|
1916
|
+
if (!verifier) {
|
|
1917
|
+
return {
|
|
1918
|
+
data: null,
|
|
1919
|
+
error: new ResultError(
|
|
1920
|
+
"PKCE code verifier not found. Ensure signInWithOAuth was called in the same browser session.",
|
|
1921
|
+
400,
|
|
1922
|
+
"PKCE_VERIFIER_MISSING"
|
|
1923
|
+
)
|
|
1924
|
+
};
|
|
1925
|
+
}
|
|
1926
|
+
const request = {
|
|
1927
|
+
code,
|
|
1928
|
+
code_verifier: verifier
|
|
1929
|
+
};
|
|
1930
|
+
const response = await this.http.post(
|
|
1931
|
+
this.isServerMode() ? "/api/auth/oauth/exchange?client_type=mobile" : "/api/auth/oauth/exchange",
|
|
1932
|
+
request,
|
|
1933
|
+
{ credentials: "include", skipAuthRefresh: true }
|
|
1747
1934
|
);
|
|
1935
|
+
this.saveSessionFromResponse(response);
|
|
1936
|
+
return {
|
|
1937
|
+
data: response,
|
|
1938
|
+
error: null
|
|
1939
|
+
};
|
|
1748
1940
|
} catch (error) {
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
throw error;
|
|
1753
|
-
}
|
|
1754
|
-
return await this.http.post(
|
|
1755
|
-
`/api/storage/buckets/${this.bucketName}/objects/${encoded}/download-strategy`,
|
|
1756
|
-
{ expiresIn }
|
|
1941
|
+
return wrapError(
|
|
1942
|
+
error,
|
|
1943
|
+
"An unexpected error occurred during OAuth code exchange"
|
|
1757
1944
|
);
|
|
1758
1945
|
}
|
|
1759
1946
|
}
|
|
1760
1947
|
/**
|
|
1761
|
-
*
|
|
1762
|
-
*
|
|
1763
|
-
* Returns a time-limited, credential-free URL that can be handed directly to
|
|
1764
|
-
* a browser (`<img src>`), an email, or a third party — no SDK or session is
|
|
1765
|
-
* needed to fetch it. Authorization is enforced when the URL is minted (the
|
|
1766
|
-
* caller must be allowed to read the object), so the resulting link is a
|
|
1767
|
-
* pre-authorized capability scoped to this one object until it expires.
|
|
1948
|
+
* Sign in with an ID token from a native SDK (Google One Tap, etc.)
|
|
1949
|
+
* Use this for native mobile apps or Google One Tap on web.
|
|
1768
1950
|
*
|
|
1769
|
-
* @param
|
|
1770
|
-
* @param
|
|
1771
|
-
* Honored for private buckets; public buckets return their long-lived URL.
|
|
1951
|
+
* @param credentials.provider - The identity provider (currently only 'google' is supported)
|
|
1952
|
+
* @param credentials.token - The ID token from the native SDK
|
|
1772
1953
|
*/
|
|
1773
|
-
async
|
|
1954
|
+
async signInWithIdToken(credentials) {
|
|
1774
1955
|
try {
|
|
1775
|
-
const
|
|
1956
|
+
const { provider, token } = credentials;
|
|
1957
|
+
const response = await this.http.post(
|
|
1958
|
+
"/api/auth/id-token?client_type=mobile",
|
|
1959
|
+
{ provider, token },
|
|
1960
|
+
{ credentials: "include", skipAuthRefresh: true }
|
|
1961
|
+
);
|
|
1962
|
+
this.saveSessionFromResponse(response);
|
|
1963
|
+
if (response.refreshToken) {
|
|
1964
|
+
this.http.setRefreshToken(response.refreshToken);
|
|
1965
|
+
}
|
|
1776
1966
|
return {
|
|
1777
|
-
data:
|
|
1778
|
-
signedUrl: strategy.url,
|
|
1779
|
-
expiresAt: strategy.expiresAt ? new Date(strategy.expiresAt).toISOString() : null
|
|
1780
|
-
},
|
|
1967
|
+
data: response,
|
|
1781
1968
|
error: null
|
|
1782
1969
|
};
|
|
1783
1970
|
} catch (error) {
|
|
1784
|
-
return
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1971
|
+
return wrapError(
|
|
1972
|
+
error,
|
|
1973
|
+
"An unexpected error occurred during ID token sign in"
|
|
1974
|
+
);
|
|
1788
1975
|
}
|
|
1789
1976
|
}
|
|
1977
|
+
// ============================================================================
|
|
1978
|
+
// Session Management
|
|
1979
|
+
// ============================================================================
|
|
1790
1980
|
/**
|
|
1791
|
-
*
|
|
1981
|
+
* Refresh the current auth session.
|
|
1792
1982
|
*
|
|
1793
|
-
*
|
|
1794
|
-
*
|
|
1983
|
+
* Browser mode:
|
|
1984
|
+
* - Uses httpOnly refresh cookie and optional CSRF header.
|
|
1795
1985
|
*
|
|
1796
|
-
*
|
|
1797
|
-
*
|
|
1986
|
+
* Legacy server mode (`isServerMode: true`):
|
|
1987
|
+
* - Uses mobile auth flow and requires `refreshToken` in request body.
|
|
1988
|
+
*
|
|
1989
|
+
* SSR apps should prefer `createRefreshAuthRouter()` / `refreshAuth()` from
|
|
1990
|
+
* `@resultdev/sdk/ssr`.
|
|
1798
1991
|
*/
|
|
1799
|
-
async
|
|
1992
|
+
async refreshSession(options) {
|
|
1800
1993
|
try {
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
}
|
|
1994
|
+
if (this.isServerMode() && !options?.refreshToken) {
|
|
1995
|
+
return {
|
|
1996
|
+
data: null,
|
|
1997
|
+
error: new ResultError(
|
|
1998
|
+
"refreshToken is required when refreshing session in server mode",
|
|
1999
|
+
400,
|
|
2000
|
+
ERROR_CODES.AUTH_UNAUTHORIZED
|
|
2001
|
+
)
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
2004
|
+
const csrfToken = !this.isServerMode() ? getCsrfToken() : null;
|
|
2005
|
+
const response = await this.http.post(
|
|
2006
|
+
this.isServerMode() ? "/api/auth/refresh?client_type=mobile" : "/api/auth/refresh",
|
|
2007
|
+
this.isServerMode() ? { refresh_token: options?.refreshToken } : void 0,
|
|
2008
|
+
{
|
|
2009
|
+
headers: csrfToken ? { "X-CSRF-Token": csrfToken } : {},
|
|
2010
|
+
credentials: "include",
|
|
2011
|
+
skipAuthRefresh: true
|
|
2012
|
+
}
|
|
1810
2013
|
);
|
|
1811
|
-
|
|
2014
|
+
if (response.accessToken) {
|
|
2015
|
+
this.saveSessionFromResponse(response, AuthChangeEvent.TOKEN_REFRESHED);
|
|
2016
|
+
}
|
|
2017
|
+
return { data: response, error: null };
|
|
1812
2018
|
} catch (error) {
|
|
1813
|
-
return
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
2019
|
+
return wrapError(
|
|
2020
|
+
error,
|
|
2021
|
+
"An unexpected error occurred during session refresh"
|
|
2022
|
+
);
|
|
1817
2023
|
}
|
|
1818
2024
|
}
|
|
1819
2025
|
/**
|
|
1820
|
-
*
|
|
1821
|
-
* @param prefix - Filter by key prefix
|
|
1822
|
-
* @param search - Search in file names
|
|
1823
|
-
* @param limit - Maximum number of results (default: 100, max: 1000)
|
|
1824
|
-
* @param offset - Number of results to skip
|
|
2026
|
+
* Get current user, automatically waits for pending OAuth callback
|
|
1825
2027
|
*/
|
|
1826
|
-
async
|
|
2028
|
+
async getCurrentUser() {
|
|
2029
|
+
await this.authCallbackHandled;
|
|
1827
2030
|
try {
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
2031
|
+
if (this.isServerMode()) {
|
|
2032
|
+
const accessToken = this.tokenManager.getAccessToken();
|
|
2033
|
+
if (!accessToken) {
|
|
2034
|
+
return { data: { user: null }, error: null };
|
|
2035
|
+
}
|
|
2036
|
+
this.http.setAuthToken(accessToken);
|
|
2037
|
+
const response = await this.http.get(
|
|
2038
|
+
"/api/auth/sessions/current"
|
|
2039
|
+
);
|
|
2040
|
+
const user = response.user ?? null;
|
|
2041
|
+
return { data: { user }, error: null };
|
|
1831
2042
|
}
|
|
1832
|
-
|
|
1833
|
-
|
|
2043
|
+
const session = this.tokenManager.getSession();
|
|
2044
|
+
if (session) {
|
|
2045
|
+
this.http.setAuthToken(session.accessToken);
|
|
2046
|
+
return { data: { user: session.user }, error: null };
|
|
1834
2047
|
}
|
|
1835
|
-
if (
|
|
1836
|
-
|
|
2048
|
+
if (typeof window !== "undefined") {
|
|
2049
|
+
const { data: refreshed, error: refreshError } = await this.refreshSession();
|
|
2050
|
+
if (refreshError) {
|
|
2051
|
+
return { data: { user: null }, error: refreshError };
|
|
2052
|
+
}
|
|
2053
|
+
if (refreshed?.accessToken) {
|
|
2054
|
+
return { data: { user: refreshed.user ?? null }, error: null };
|
|
2055
|
+
}
|
|
1837
2056
|
}
|
|
1838
|
-
|
|
1839
|
-
|
|
2057
|
+
return { data: { user: null }, error: null };
|
|
2058
|
+
} catch (error) {
|
|
2059
|
+
if (error instanceof ResultError) {
|
|
2060
|
+
return { data: { user: null }, error };
|
|
1840
2061
|
}
|
|
2062
|
+
return {
|
|
2063
|
+
data: { user: null },
|
|
2064
|
+
error: new ResultError(
|
|
2065
|
+
"An unexpected error occurred while getting user",
|
|
2066
|
+
500,
|
|
2067
|
+
"UNEXPECTED_ERROR"
|
|
2068
|
+
)
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
// ============================================================================
|
|
2073
|
+
// Profile Management
|
|
2074
|
+
// ============================================================================
|
|
2075
|
+
async getProfile(userId) {
|
|
2076
|
+
try {
|
|
1841
2077
|
const response = await this.http.get(
|
|
1842
|
-
`/api/
|
|
1843
|
-
{ params }
|
|
2078
|
+
`/api/auth/profiles/${userId}`
|
|
1844
2079
|
);
|
|
1845
2080
|
return { data: response, error: null };
|
|
1846
2081
|
} catch (error) {
|
|
1847
|
-
return
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
2082
|
+
return wrapError(
|
|
2083
|
+
error,
|
|
2084
|
+
"An unexpected error occurred while fetching user profile"
|
|
2085
|
+
);
|
|
1851
2086
|
}
|
|
1852
2087
|
}
|
|
1853
|
-
|
|
1854
|
-
* Delete a file
|
|
1855
|
-
* @param path - The object key/path
|
|
1856
|
-
*/
|
|
1857
|
-
async remove(path) {
|
|
2088
|
+
async setProfile(profile) {
|
|
1858
2089
|
try {
|
|
1859
|
-
const response = await this.http.
|
|
1860
|
-
|
|
2090
|
+
const response = await this.http.patch(
|
|
2091
|
+
"/api/auth/profiles/current",
|
|
2092
|
+
{
|
|
2093
|
+
profile
|
|
2094
|
+
}
|
|
1861
2095
|
);
|
|
2096
|
+
const currentUser = this.tokenManager.getUser();
|
|
2097
|
+
if (!this.isServerMode() && currentUser && response.profile !== void 0) {
|
|
2098
|
+
this.tokenManager.setUser({
|
|
2099
|
+
...currentUser,
|
|
2100
|
+
profile: response.profile
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
1862
2103
|
return { data: response, error: null };
|
|
1863
2104
|
} catch (error) {
|
|
1864
|
-
return
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
2105
|
+
return wrapError(
|
|
2106
|
+
error,
|
|
2107
|
+
"An unexpected error occurred while updating user profile"
|
|
2108
|
+
);
|
|
1868
2109
|
}
|
|
1869
2110
|
}
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
2111
|
+
// ============================================================================
|
|
2112
|
+
// Email Verification
|
|
2113
|
+
// ============================================================================
|
|
2114
|
+
async resendVerificationEmail(request) {
|
|
2115
|
+
try {
|
|
2116
|
+
const response = await this.http.post("/api/auth/email/send-verification", request, {
|
|
2117
|
+
skipAuthRefresh: true
|
|
2118
|
+
});
|
|
2119
|
+
return { data: response, error: null };
|
|
2120
|
+
} catch (error) {
|
|
2121
|
+
return wrapError(
|
|
2122
|
+
error,
|
|
2123
|
+
"An unexpected error occurred while sending verification email"
|
|
2124
|
+
);
|
|
2125
|
+
}
|
|
1874
2126
|
}
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
2127
|
+
async verifyEmail(request) {
|
|
2128
|
+
try {
|
|
2129
|
+
const response = await this.http.post(
|
|
2130
|
+
this.isServerMode() ? "/api/auth/email/verify?client_type=mobile" : "/api/auth/email/verify",
|
|
2131
|
+
request,
|
|
2132
|
+
{ credentials: "include", skipAuthRefresh: true }
|
|
2133
|
+
);
|
|
2134
|
+
this.saveSessionFromResponse(response);
|
|
2135
|
+
if (response.refreshToken) {
|
|
2136
|
+
this.http.setRefreshToken(response.refreshToken);
|
|
2137
|
+
}
|
|
2138
|
+
return { data: response, error: null };
|
|
2139
|
+
} catch (error) {
|
|
2140
|
+
return wrapError(
|
|
2141
|
+
error,
|
|
2142
|
+
"An unexpected error occurred while verifying email"
|
|
2143
|
+
);
|
|
2144
|
+
}
|
|
1881
2145
|
}
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
2146
|
+
// ============================================================================
|
|
2147
|
+
// Password Reset
|
|
2148
|
+
// ============================================================================
|
|
2149
|
+
async sendResetPasswordEmail(request) {
|
|
2150
|
+
try {
|
|
2151
|
+
const response = await this.http.post("/api/auth/email/send-reset-password", request, {
|
|
2152
|
+
skipAuthRefresh: true
|
|
2153
|
+
});
|
|
2154
|
+
return { data: response, error: null };
|
|
2155
|
+
} catch (error) {
|
|
2156
|
+
return wrapError(
|
|
2157
|
+
error,
|
|
2158
|
+
"An unexpected error occurred while sending password reset email"
|
|
2159
|
+
);
|
|
2160
|
+
}
|
|
1889
2161
|
}
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
2162
|
+
async exchangeResetPasswordToken(request) {
|
|
2163
|
+
try {
|
|
2164
|
+
const response = await this.http.post(
|
|
2165
|
+
"/api/auth/email/exchange-reset-password-token",
|
|
2166
|
+
request,
|
|
2167
|
+
{ skipAuthRefresh: true }
|
|
2168
|
+
);
|
|
2169
|
+
return { data: response, error: null };
|
|
2170
|
+
} catch (error) {
|
|
2171
|
+
return wrapError(
|
|
2172
|
+
error,
|
|
2173
|
+
"An unexpected error occurred while verifying reset code"
|
|
2174
|
+
);
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
async resetPassword(request) {
|
|
2178
|
+
try {
|
|
2179
|
+
const response = await this.http.post(
|
|
2180
|
+
"/api/auth/email/reset-password",
|
|
2181
|
+
request,
|
|
2182
|
+
{ skipAuthRefresh: true }
|
|
2183
|
+
);
|
|
2184
|
+
return { data: response, error: null };
|
|
2185
|
+
} catch (error) {
|
|
2186
|
+
return wrapError(
|
|
2187
|
+
error,
|
|
2188
|
+
"An unexpected error occurred while resetting password"
|
|
2189
|
+
);
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
// ============================================================================
|
|
2193
|
+
// Configuration
|
|
2194
|
+
// ============================================================================
|
|
2195
|
+
async getPublicAuthConfig() {
|
|
2196
|
+
try {
|
|
2197
|
+
const response = await this.http.get(
|
|
2198
|
+
"/api/auth/public-config",
|
|
2199
|
+
{
|
|
2200
|
+
skipAuthRefresh: true
|
|
2201
|
+
}
|
|
2202
|
+
);
|
|
2203
|
+
return { data: response, error: null };
|
|
2204
|
+
} catch (error) {
|
|
2205
|
+
return wrapError(
|
|
2206
|
+
error,
|
|
2207
|
+
"An unexpected error occurred while fetching auth configuration"
|
|
2208
|
+
);
|
|
2209
|
+
}
|
|
1894
2210
|
}
|
|
1895
2211
|
};
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
2212
|
+
|
|
2213
|
+
// src/modules/database-postgrest.ts
|
|
2214
|
+
import { PostgrestClient } from "@supabase/postgrest-js";
|
|
2215
|
+
function createResultPostgrestFetch(httpClient) {
|
|
2216
|
+
return async (input, init) => {
|
|
2217
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
2218
|
+
const urlObj = new URL(url);
|
|
2219
|
+
const pathname = urlObj.pathname.slice(1);
|
|
2220
|
+
const rpcMatch = pathname.match(/^rpc\/(.+)$/);
|
|
2221
|
+
const endpoint = rpcMatch ? `/api/database/rpc/${rpcMatch[1]}` : `/api/database/records/${pathname}`;
|
|
2222
|
+
const backendUrl = `${httpClient.baseUrl}${endpoint}${urlObj.search}`;
|
|
2223
|
+
const headers = new Headers(httpClient.getHeaders());
|
|
2224
|
+
new Headers(init?.headers).forEach((value, key) => {
|
|
2225
|
+
headers.set(key, value);
|
|
2226
|
+
});
|
|
2227
|
+
const response = await httpClient.rawFetch(backendUrl, {
|
|
2228
|
+
...init,
|
|
2229
|
+
headers
|
|
2230
|
+
});
|
|
2231
|
+
return response;
|
|
2232
|
+
};
|
|
2233
|
+
}
|
|
2234
|
+
var Database = class {
|
|
2235
|
+
postgrest;
|
|
2236
|
+
constructor(httpClient, defaultSchema) {
|
|
2237
|
+
this.postgrest = new PostgrestClient("http://dummy", {
|
|
2238
|
+
fetch: createResultPostgrestFetch(httpClient),
|
|
2239
|
+
headers: {},
|
|
2240
|
+
...defaultSchema ? { schema: defaultSchema } : {}
|
|
2241
|
+
});
|
|
1899
2242
|
}
|
|
1900
2243
|
/**
|
|
1901
|
-
*
|
|
2244
|
+
* Select a non-default Postgres schema for the chained query. Maps to
|
|
2245
|
+
* PostgREST's `Accept-Profile` (reads) / `Content-Profile` (writes) header.
|
|
2246
|
+
* The schema must be exposed by the backend.
|
|
1902
2247
|
*
|
|
1903
2248
|
* @example
|
|
1904
|
-
*
|
|
1905
|
-
*
|
|
1906
|
-
*
|
|
1907
|
-
*
|
|
1908
|
-
* messages: [{ role: 'user', content: 'Hello!' }]
|
|
1909
|
-
* });
|
|
1910
|
-
* console.log(completion.choices[0].message.content);
|
|
2249
|
+
* const { data } = await client.database
|
|
2250
|
+
* .schema('analytics')
|
|
2251
|
+
* .from('events')
|
|
2252
|
+
* .select('*');
|
|
1911
2253
|
*
|
|
1912
|
-
*
|
|
1913
|
-
*
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
*
|
|
1920
|
-
* ]
|
|
1921
|
-
* }]
|
|
1922
|
-
* });
|
|
2254
|
+
* @example
|
|
2255
|
+
* await client.database.schema('analytics').rpc('rollup', { day: '2026-01-01' });
|
|
2256
|
+
*/
|
|
2257
|
+
schema(schemaName) {
|
|
2258
|
+
return this.postgrest.schema(schemaName);
|
|
2259
|
+
}
|
|
2260
|
+
/**
|
|
2261
|
+
* Create a query builder for a table
|
|
1923
2262
|
*
|
|
1924
|
-
*
|
|
1925
|
-
*
|
|
1926
|
-
*
|
|
1927
|
-
*
|
|
1928
|
-
*
|
|
1929
|
-
*
|
|
1930
|
-
* { type: 'text', text: 'Summarize this document' },
|
|
1931
|
-
* { type: 'file', file: { filename: 'doc.pdf', file_data: 'https://example.com/doc.pdf' } }
|
|
1932
|
-
* ]
|
|
1933
|
-
* }],
|
|
1934
|
-
* fileParser: { enabled: true, pdf: { engine: 'mistral-ocr' } }
|
|
1935
|
-
* });
|
|
2263
|
+
* @example
|
|
2264
|
+
* // Basic query
|
|
2265
|
+
* const { data, error } = await client.database
|
|
2266
|
+
* .from('posts')
|
|
2267
|
+
* .select('*')
|
|
2268
|
+
* .eq('user_id', userId);
|
|
1936
2269
|
*
|
|
1937
|
-
* // With
|
|
1938
|
-
* const
|
|
1939
|
-
*
|
|
1940
|
-
*
|
|
1941
|
-
*
|
|
1942
|
-
* });
|
|
1943
|
-
* // Access citations from response.choices[0].message.annotations
|
|
2270
|
+
* // With count (Supabase style!)
|
|
2271
|
+
* const { data, error, count } = await client.database
|
|
2272
|
+
* .from('posts')
|
|
2273
|
+
* .select('*', { count: 'exact' })
|
|
2274
|
+
* .range(0, 9);
|
|
1944
2275
|
*
|
|
1945
|
-
* //
|
|
1946
|
-
* const
|
|
1947
|
-
*
|
|
1948
|
-
*
|
|
1949
|
-
* thinking: true
|
|
1950
|
-
* });
|
|
2276
|
+
* // Just get count, no data
|
|
2277
|
+
* const { count } = await client.database
|
|
2278
|
+
* .from('posts')
|
|
2279
|
+
* .select('*', { count: 'exact', head: true });
|
|
1951
2280
|
*
|
|
1952
|
-
* //
|
|
1953
|
-
* const
|
|
1954
|
-
*
|
|
1955
|
-
*
|
|
1956
|
-
*
|
|
1957
|
-
* });
|
|
2281
|
+
* // Complex queries with OR
|
|
2282
|
+
* const { data } = await client.database
|
|
2283
|
+
* .from('posts')
|
|
2284
|
+
* .select('*, users!inner(*)')
|
|
2285
|
+
* .or('status.eq.active,status.eq.pending');
|
|
1958
2286
|
*
|
|
1959
|
-
*
|
|
1960
|
-
*
|
|
1961
|
-
*
|
|
1962
|
-
*
|
|
1963
|
-
*
|
|
1964
|
-
*
|
|
1965
|
-
|
|
1966
|
-
async create(params) {
|
|
1967
|
-
const backendParams = {
|
|
1968
|
-
model: params.model,
|
|
1969
|
-
messages: params.messages,
|
|
1970
|
-
temperature: params.temperature,
|
|
1971
|
-
maxTokens: params.maxTokens,
|
|
1972
|
-
topP: params.topP,
|
|
1973
|
-
stream: params.stream,
|
|
1974
|
-
// New plugin options
|
|
1975
|
-
webSearch: params.webSearch,
|
|
1976
|
-
fileParser: params.fileParser,
|
|
1977
|
-
thinking: params.thinking,
|
|
1978
|
-
// Tool calling options
|
|
1979
|
-
tools: params.tools,
|
|
1980
|
-
toolChoice: params.toolChoice,
|
|
1981
|
-
parallelToolCalls: params.parallelToolCalls
|
|
1982
|
-
};
|
|
1983
|
-
if (params.stream) {
|
|
1984
|
-
const headers = this.http.getHeaders();
|
|
1985
|
-
headers["Content-Type"] = "application/json";
|
|
1986
|
-
const response2 = await this.http.fetch(`${this.http.baseUrl}/api/ai/chat/completion`, {
|
|
1987
|
-
method: "POST",
|
|
1988
|
-
headers,
|
|
1989
|
-
body: JSON.stringify(backendParams)
|
|
1990
|
-
});
|
|
1991
|
-
if (!response2.ok) {
|
|
1992
|
-
const error = await response2.json();
|
|
1993
|
-
throw new Error(error.error || "Stream request failed");
|
|
1994
|
-
}
|
|
1995
|
-
return this.parseSSEStream(response2, params.model);
|
|
1996
|
-
}
|
|
1997
|
-
const response = await this.http.post(
|
|
1998
|
-
"/api/ai/chat/completion",
|
|
1999
|
-
backendParams
|
|
2000
|
-
);
|
|
2001
|
-
const content = response.text || "";
|
|
2002
|
-
return {
|
|
2003
|
-
id: `chatcmpl-${Date.now()}`,
|
|
2004
|
-
object: "chat.completion",
|
|
2005
|
-
created: Math.floor(Date.now() / 1e3),
|
|
2006
|
-
model: response.metadata?.model,
|
|
2007
|
-
choices: [
|
|
2008
|
-
{
|
|
2009
|
-
index: 0,
|
|
2010
|
-
message: {
|
|
2011
|
-
role: "assistant",
|
|
2012
|
-
content,
|
|
2013
|
-
// Include tool_calls if present (from tool calling)
|
|
2014
|
-
...response.tool_calls?.length && { tool_calls: response.tool_calls },
|
|
2015
|
-
// Include annotations if present (from web search or file parsing)
|
|
2016
|
-
...response.annotations?.length && { annotations: response.annotations }
|
|
2017
|
-
},
|
|
2018
|
-
finish_reason: response.tool_calls?.length ? "tool_calls" : "stop"
|
|
2019
|
-
}
|
|
2020
|
-
],
|
|
2021
|
-
usage: response.metadata?.usage || {
|
|
2022
|
-
prompt_tokens: 0,
|
|
2023
|
-
completion_tokens: 0,
|
|
2024
|
-
total_tokens: 0
|
|
2025
|
-
}
|
|
2026
|
-
};
|
|
2027
|
-
}
|
|
2028
|
-
/**
|
|
2029
|
-
* Parse SSE stream into async iterable of OpenAI-like chunks
|
|
2287
|
+
* // All features work:
|
|
2288
|
+
* - Nested selects
|
|
2289
|
+
* - Foreign key expansion
|
|
2290
|
+
* - OR/AND/NOT conditions
|
|
2291
|
+
* - Count with head
|
|
2292
|
+
* - Range pagination
|
|
2293
|
+
* - Upserts
|
|
2030
2294
|
*/
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
const decoder = new TextDecoder();
|
|
2034
|
-
let buffer = "";
|
|
2035
|
-
try {
|
|
2036
|
-
while (true) {
|
|
2037
|
-
const { done, value } = await reader.read();
|
|
2038
|
-
if (done) {
|
|
2039
|
-
break;
|
|
2040
|
-
}
|
|
2041
|
-
buffer += decoder.decode(value, { stream: true });
|
|
2042
|
-
const lines = buffer.split("\n");
|
|
2043
|
-
buffer = lines.pop() || "";
|
|
2044
|
-
for (const line of lines) {
|
|
2045
|
-
if (line.startsWith("data: ")) {
|
|
2046
|
-
const dataStr = line.slice(6).trim();
|
|
2047
|
-
if (dataStr) {
|
|
2048
|
-
try {
|
|
2049
|
-
const data = JSON.parse(dataStr);
|
|
2050
|
-
if (data.chunk || data.content) {
|
|
2051
|
-
yield {
|
|
2052
|
-
id: `chatcmpl-${Date.now()}`,
|
|
2053
|
-
object: "chat.completion.chunk",
|
|
2054
|
-
created: Math.floor(Date.now() / 1e3),
|
|
2055
|
-
model,
|
|
2056
|
-
choices: [
|
|
2057
|
-
{
|
|
2058
|
-
index: 0,
|
|
2059
|
-
delta: {
|
|
2060
|
-
content: data.chunk || data.content
|
|
2061
|
-
},
|
|
2062
|
-
finish_reason: null
|
|
2063
|
-
}
|
|
2064
|
-
]
|
|
2065
|
-
};
|
|
2066
|
-
}
|
|
2067
|
-
if (data.tool_calls?.length) {
|
|
2068
|
-
yield {
|
|
2069
|
-
id: `chatcmpl-${Date.now()}`,
|
|
2070
|
-
object: "chat.completion.chunk",
|
|
2071
|
-
created: Math.floor(Date.now() / 1e3),
|
|
2072
|
-
model,
|
|
2073
|
-
choices: [
|
|
2074
|
-
{
|
|
2075
|
-
index: 0,
|
|
2076
|
-
delta: {
|
|
2077
|
-
tool_calls: data.tool_calls
|
|
2078
|
-
},
|
|
2079
|
-
finish_reason: "tool_calls"
|
|
2080
|
-
}
|
|
2081
|
-
]
|
|
2082
|
-
};
|
|
2083
|
-
}
|
|
2084
|
-
if (data.done) {
|
|
2085
|
-
reader.releaseLock();
|
|
2086
|
-
return;
|
|
2087
|
-
}
|
|
2088
|
-
} catch {
|
|
2089
|
-
console.warn("Failed to parse SSE data:", dataStr);
|
|
2090
|
-
}
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2095
|
-
} finally {
|
|
2096
|
-
reader.releaseLock();
|
|
2097
|
-
}
|
|
2098
|
-
}
|
|
2099
|
-
};
|
|
2100
|
-
var Embeddings = class {
|
|
2101
|
-
constructor(http) {
|
|
2102
|
-
this.http = http;
|
|
2295
|
+
from(table) {
|
|
2296
|
+
return this.postgrest.from(table);
|
|
2103
2297
|
}
|
|
2104
2298
|
/**
|
|
2105
|
-
*
|
|
2299
|
+
* Call a PostgreSQL function (RPC)
|
|
2106
2300
|
*
|
|
2107
2301
|
* @example
|
|
2108
|
-
*
|
|
2109
|
-
*
|
|
2110
|
-
*
|
|
2111
|
-
* model: 'openai/text-embedding-3-small',
|
|
2112
|
-
* input: 'Hello world'
|
|
2113
|
-
* });
|
|
2114
|
-
* console.log(response.data[0].embedding); // number[]
|
|
2115
|
-
*
|
|
2116
|
-
* // Multiple text inputs
|
|
2117
|
-
* const response = await client.ai.embeddings.create({
|
|
2118
|
-
* model: 'openai/text-embedding-3-small',
|
|
2119
|
-
* input: ['Hello world', 'Goodbye world']
|
|
2120
|
-
* });
|
|
2121
|
-
* response.data.forEach((item, i) => {
|
|
2122
|
-
* console.log(`Embedding ${i}:`, item.embedding.slice(0, 5)); // First 5 dimensions
|
|
2123
|
-
* });
|
|
2124
|
-
*
|
|
2125
|
-
* // With custom dimensions (if supported by model)
|
|
2126
|
-
* const response = await client.ai.embeddings.create({
|
|
2127
|
-
* model: 'openai/text-embedding-3-small',
|
|
2128
|
-
* input: 'Hello world',
|
|
2129
|
-
* dimensions: 256
|
|
2130
|
-
* });
|
|
2302
|
+
* // Call a function with parameters
|
|
2303
|
+
* const { data, error } = await client.database
|
|
2304
|
+
* .rpc('get_user_stats', { user_id: 123 });
|
|
2131
2305
|
*
|
|
2132
|
-
* //
|
|
2133
|
-
* const
|
|
2134
|
-
*
|
|
2135
|
-
*
|
|
2136
|
-
*
|
|
2137
|
-
* }
|
|
2138
|
-
*
|
|
2306
|
+
* // Call a function with no parameters
|
|
2307
|
+
* const { data, error } = await client.database
|
|
2308
|
+
* .rpc('get_all_active_users');
|
|
2309
|
+
*
|
|
2310
|
+
* // With options (head, count, get)
|
|
2311
|
+
* const { data, count } = await client.database
|
|
2312
|
+
* .rpc('search_posts', { query: 'hello' }, { count: 'exact' });
|
|
2139
2313
|
*/
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
return {
|
|
2143
|
-
object: response.object,
|
|
2144
|
-
data: response.data,
|
|
2145
|
-
model: response.metadata?.model,
|
|
2146
|
-
usage: response.metadata?.usage ? {
|
|
2147
|
-
prompt_tokens: response.metadata.usage.promptTokens || 0,
|
|
2148
|
-
total_tokens: response.metadata.usage.totalTokens || 0
|
|
2149
|
-
} : {
|
|
2150
|
-
prompt_tokens: 0,
|
|
2151
|
-
total_tokens: 0
|
|
2152
|
-
}
|
|
2153
|
-
};
|
|
2314
|
+
rpc(fn, args, options) {
|
|
2315
|
+
return this.postgrest.rpc(fn, args, options);
|
|
2154
2316
|
}
|
|
2155
2317
|
};
|
|
2156
|
-
|
|
2318
|
+
|
|
2319
|
+
// src/modules/email.ts
|
|
2320
|
+
var Emails = class {
|
|
2321
|
+
http;
|
|
2157
2322
|
constructor(http) {
|
|
2158
2323
|
this.http = http;
|
|
2159
2324
|
}
|
|
2160
2325
|
/**
|
|
2161
|
-
*
|
|
2162
|
-
*
|
|
2163
|
-
* @example
|
|
2164
|
-
* ```typescript
|
|
2165
|
-
* // Text-to-image
|
|
2166
|
-
* const response = await client.ai.images.generate({
|
|
2167
|
-
* model: 'dall-e-3',
|
|
2168
|
-
* prompt: 'A sunset over mountains',
|
|
2169
|
-
* });
|
|
2170
|
-
* console.log(response.data[0].b64_json);
|
|
2171
|
-
*
|
|
2172
|
-
* // Image-to-image (with input images)
|
|
2173
|
-
* const response = await client.ai.images.generate({
|
|
2174
|
-
* model: 'stable-diffusion-xl',
|
|
2175
|
-
* prompt: 'Transform this into a watercolor painting',
|
|
2176
|
-
* images: [
|
|
2177
|
-
* { url: 'https://example.com/input.jpg' },
|
|
2178
|
-
* // or base64-encoded Data URI:
|
|
2179
|
-
* { url: 'data:image/jpeg;base64,/9j/4AAQ...' }
|
|
2180
|
-
* ]
|
|
2181
|
-
* });
|
|
2182
|
-
* ```
|
|
2326
|
+
* Send a custom HTML email
|
|
2327
|
+
* @param options Email options including recipients, subject, and HTML content
|
|
2183
2328
|
*/
|
|
2184
|
-
async
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
}));
|
|
2195
|
-
} else if (response.text) {
|
|
2196
|
-
data = [{ content: response.text }];
|
|
2197
|
-
}
|
|
2198
|
-
return {
|
|
2199
|
-
created: Math.floor(Date.now() / 1e3),
|
|
2200
|
-
data,
|
|
2201
|
-
...response.metadata?.usage && {
|
|
2202
|
-
usage: {
|
|
2203
|
-
total_tokens: response.metadata.usage.totalTokens || 0,
|
|
2204
|
-
input_tokens: response.metadata.usage.promptTokens || 0,
|
|
2205
|
-
output_tokens: response.metadata.usage.completionTokens || 0
|
|
2206
|
-
}
|
|
2329
|
+
async send(options) {
|
|
2330
|
+
try {
|
|
2331
|
+
const data = await this.http.post(
|
|
2332
|
+
"/api/email/send-raw",
|
|
2333
|
+
options
|
|
2334
|
+
);
|
|
2335
|
+
return { data, error: null };
|
|
2336
|
+
} catch (error) {
|
|
2337
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2338
|
+
throw error;
|
|
2207
2339
|
}
|
|
2208
|
-
|
|
2340
|
+
return {
|
|
2341
|
+
data: null,
|
|
2342
|
+
error: error instanceof ResultError ? error : new ResultError(
|
|
2343
|
+
error instanceof Error ? error.message : "Email send failed",
|
|
2344
|
+
500,
|
|
2345
|
+
"EMAIL_ERROR"
|
|
2346
|
+
)
|
|
2347
|
+
};
|
|
2348
|
+
}
|
|
2209
2349
|
}
|
|
2210
2350
|
};
|
|
2351
|
+
|
|
2352
|
+
// src/modules/functions.ts
|
|
2211
2353
|
var Functions = class _Functions {
|
|
2354
|
+
http;
|
|
2355
|
+
functionsUrl;
|
|
2212
2356
|
constructor(http, functionsUrl) {
|
|
2213
2357
|
this.http = http;
|
|
2214
2358
|
this.functionsUrl = functionsUrl || _Functions.deriveSubhostingUrl(http.baseUrl);
|
|
2215
2359
|
}
|
|
2216
2360
|
/**
|
|
2217
2361
|
* Derive the subhosting URL from the base URL.
|
|
2218
|
-
*
|
|
2219
|
-
*
|
|
2220
|
-
* Only applies to
|
|
2362
|
+
* Rewrites the backend base URL to its functions subhosting URL
|
|
2363
|
+
* ({appKey}.{region}.<domain> -> {appKey}.functions.<domain>).
|
|
2364
|
+
* Only applies to platform-hosted backends.
|
|
2221
2365
|
*/
|
|
2222
2366
|
static deriveSubhostingUrl(baseUrl) {
|
|
2223
2367
|
try {
|
|
@@ -2236,7 +2380,7 @@ var Functions = class _Functions {
|
|
|
2236
2380
|
* placeholder; the router only reads pathname.
|
|
2237
2381
|
*/
|
|
2238
2382
|
buildInProcessRequest(slug, method, body, callerHeaders) {
|
|
2239
|
-
const url = new URL(
|
|
2383
|
+
const url = new URL(`/${slug}`, "http://backend.local").toString();
|
|
2240
2384
|
const headers = { ...this.http.getHeaders() };
|
|
2241
2385
|
const reqBody = serializeBody(method, body, headers);
|
|
2242
2386
|
Object.assign(headers, callerHeaders);
|
|
@@ -2250,7 +2394,7 @@ var Functions = class _Functions {
|
|
|
2250
2394
|
* Invoke an Edge Function.
|
|
2251
2395
|
*
|
|
2252
2396
|
* Dispatch order:
|
|
2253
|
-
* 1. If
|
|
2397
|
+
* 1. If the platform's in-process dispatch hook is present, call it directly.
|
|
2254
2398
|
* This avoids Deno Subhosting's 508 Loop Detected when one bundled
|
|
2255
2399
|
* function invokes another inside the same deployment.
|
|
2256
2400
|
* 2. Otherwise, try the configured subhosting URL.
|
|
@@ -2275,7 +2419,7 @@ var Functions = class _Functions {
|
|
|
2275
2419
|
}
|
|
2276
2420
|
return {
|
|
2277
2421
|
data: null,
|
|
2278
|
-
error: error instanceof
|
|
2422
|
+
error: error instanceof ResultError ? error : new ResultError(
|
|
2279
2423
|
error instanceof Error ? error.message : "Function invocation failed",
|
|
2280
2424
|
500,
|
|
2281
2425
|
"FUNCTION_ERROR"
|
|
@@ -2285,20 +2429,24 @@ var Functions = class _Functions {
|
|
|
2285
2429
|
}
|
|
2286
2430
|
if (this.functionsUrl) {
|
|
2287
2431
|
try {
|
|
2288
|
-
const data = await this.http.request(
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2432
|
+
const data = await this.http.request(
|
|
2433
|
+
method,
|
|
2434
|
+
`${this.functionsUrl}/${slug}`,
|
|
2435
|
+
{
|
|
2436
|
+
body,
|
|
2437
|
+
headers
|
|
2438
|
+
}
|
|
2439
|
+
);
|
|
2292
2440
|
return { data, error: null };
|
|
2293
2441
|
} catch (error) {
|
|
2294
2442
|
if (error instanceof Error && error.name === "AbortError") {
|
|
2295
2443
|
throw error;
|
|
2296
2444
|
}
|
|
2297
|
-
if (error instanceof
|
|
2445
|
+
if (error instanceof ResultError && error.statusCode === 404) {
|
|
2298
2446
|
} else {
|
|
2299
2447
|
return {
|
|
2300
2448
|
data: null,
|
|
2301
|
-
error: error instanceof
|
|
2449
|
+
error: error instanceof ResultError ? error : new ResultError(
|
|
2302
2450
|
error instanceof Error ? error.message : "Function invocation failed",
|
|
2303
2451
|
500,
|
|
2304
2452
|
"FUNCTION_ERROR"
|
|
@@ -2317,7 +2465,7 @@ var Functions = class _Functions {
|
|
|
2317
2465
|
}
|
|
2318
2466
|
return {
|
|
2319
2467
|
data: null,
|
|
2320
|
-
error: error instanceof
|
|
2468
|
+
error: error instanceof ResultError ? error : new ResultError(
|
|
2321
2469
|
error instanceof Error ? error.message : "Function invocation failed",
|
|
2322
2470
|
500,
|
|
2323
2471
|
"FUNCTION_ERROR"
|
|
@@ -2326,6 +2474,8 @@ var Functions = class _Functions {
|
|
|
2326
2474
|
}
|
|
2327
2475
|
}
|
|
2328
2476
|
};
|
|
2477
|
+
|
|
2478
|
+
// src/modules/realtime.ts
|
|
2329
2479
|
var CONNECT_TIMEOUT = 1e4;
|
|
2330
2480
|
var SUBSCRIBE_TIMEOUT = 1e4;
|
|
2331
2481
|
var Realtime = class {
|
|
@@ -2334,18 +2484,22 @@ var Realtime = class {
|
|
|
2334
2484
|
this.tokenManager = tokenManager;
|
|
2335
2485
|
this.anonKey = anonKey;
|
|
2336
2486
|
this.getValidAccessToken = getValidAccessToken;
|
|
2337
|
-
this.socket = null;
|
|
2338
|
-
this.connectPromise = null;
|
|
2339
|
-
this.connectionAttempt = null;
|
|
2340
|
-
this.nextConnectionAttemptId = 0;
|
|
2341
|
-
this.subscriptions = /* @__PURE__ */ new Map();
|
|
2342
|
-
this.eventListeners = /* @__PURE__ */ new Map();
|
|
2343
2487
|
this.tokenManager.onAuthStateChange((event) => {
|
|
2344
2488
|
if (event !== AuthChangeEvent.TOKEN_REFRESHED) {
|
|
2345
2489
|
this.reconnectForAuthChange();
|
|
2346
2490
|
}
|
|
2347
2491
|
});
|
|
2348
2492
|
}
|
|
2493
|
+
baseUrl;
|
|
2494
|
+
tokenManager;
|
|
2495
|
+
anonKey;
|
|
2496
|
+
getValidAccessToken;
|
|
2497
|
+
socket = null;
|
|
2498
|
+
connectPromise = null;
|
|
2499
|
+
connectionAttempt = null;
|
|
2500
|
+
nextConnectionAttemptId = 0;
|
|
2501
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
2502
|
+
eventListeners = /* @__PURE__ */ new Map();
|
|
2349
2503
|
notifyListeners(event, payload) {
|
|
2350
2504
|
for (const callback of this.eventListeners.get(event) ?? []) {
|
|
2351
2505
|
try {
|
|
@@ -2510,7 +2664,10 @@ var Realtime = class {
|
|
|
2510
2664
|
{
|
|
2511
2665
|
ok: false,
|
|
2512
2666
|
channel: subscription.channel,
|
|
2513
|
-
error: {
|
|
2667
|
+
error: {
|
|
2668
|
+
code: "DISCONNECTED",
|
|
2669
|
+
message: "Connection lost before subscription completed"
|
|
2670
|
+
}
|
|
2514
2671
|
},
|
|
2515
2672
|
true
|
|
2516
2673
|
);
|
|
@@ -2533,7 +2690,10 @@ var Realtime = class {
|
|
|
2533
2690
|
return Promise.resolve({
|
|
2534
2691
|
ok: false,
|
|
2535
2692
|
channel,
|
|
2536
|
-
error: {
|
|
2693
|
+
error: {
|
|
2694
|
+
code: "CONNECTION_FAILED",
|
|
2695
|
+
message: "Not connected to realtime server"
|
|
2696
|
+
}
|
|
2537
2697
|
});
|
|
2538
2698
|
}
|
|
2539
2699
|
subscription.status = "pending";
|
|
@@ -2564,21 +2724,28 @@ var Realtime = class {
|
|
|
2564
2724
|
);
|
|
2565
2725
|
}
|
|
2566
2726
|
}, SUBSCRIBE_TIMEOUT);
|
|
2567
|
-
socket.emit(
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2727
|
+
socket.emit(
|
|
2728
|
+
"realtime:subscribe",
|
|
2729
|
+
{ channel },
|
|
2730
|
+
(response) => {
|
|
2731
|
+
if (this.subscriptions.get(channel) !== subscription || subscription.epoch !== epoch) {
|
|
2732
|
+
return;
|
|
2733
|
+
}
|
|
2734
|
+
if (response.ok) {
|
|
2735
|
+
subscription.status = "subscribed";
|
|
2736
|
+
subscription.members = new Map(
|
|
2737
|
+
response.presence.members.map((member) => [
|
|
2738
|
+
member.presenceId,
|
|
2739
|
+
member
|
|
2740
|
+
])
|
|
2741
|
+
);
|
|
2742
|
+
} else {
|
|
2743
|
+
subscription.status = "rejected";
|
|
2744
|
+
subscription.members.clear();
|
|
2745
|
+
}
|
|
2746
|
+
this.settleSubscription(subscription, response, false);
|
|
2579
2747
|
}
|
|
2580
|
-
|
|
2581
|
-
});
|
|
2748
|
+
);
|
|
2582
2749
|
});
|
|
2583
2750
|
return subscription.pending;
|
|
2584
2751
|
}
|
|
@@ -2627,10 +2794,19 @@ var Realtime = class {
|
|
|
2627
2794
|
return subscription.pending;
|
|
2628
2795
|
}
|
|
2629
2796
|
if (subscription.status === "subscribed") {
|
|
2630
|
-
return {
|
|
2797
|
+
return {
|
|
2798
|
+
ok: true,
|
|
2799
|
+
channel,
|
|
2800
|
+
presence: { members: [...subscription.members.values()] }
|
|
2801
|
+
};
|
|
2631
2802
|
}
|
|
2632
2803
|
} else {
|
|
2633
|
-
subscription = {
|
|
2804
|
+
subscription = {
|
|
2805
|
+
channel,
|
|
2806
|
+
epoch: 0,
|
|
2807
|
+
status: "pending",
|
|
2808
|
+
members: /* @__PURE__ */ new Map()
|
|
2809
|
+
};
|
|
2634
2810
|
this.subscriptions.set(channel, subscription);
|
|
2635
2811
|
}
|
|
2636
2812
|
if (!this.socket?.connected) {
|
|
@@ -2641,7 +2817,11 @@ var Realtime = class {
|
|
|
2641
2817
|
this.subscriptions.delete(channel);
|
|
2642
2818
|
}
|
|
2643
2819
|
const message = error instanceof Error ? error.message : "Connection failed";
|
|
2644
|
-
return {
|
|
2820
|
+
return {
|
|
2821
|
+
ok: false,
|
|
2822
|
+
channel,
|
|
2823
|
+
error: { code: "CONNECTION_FAILED", message }
|
|
2824
|
+
};
|
|
2645
2825
|
}
|
|
2646
2826
|
}
|
|
2647
2827
|
return subscription.pending ?? this.requestSubscription(channel, subscription);
|
|
@@ -2657,7 +2837,10 @@ var Realtime = class {
|
|
|
2657
2837
|
{
|
|
2658
2838
|
ok: false,
|
|
2659
2839
|
channel,
|
|
2660
|
-
error: {
|
|
2840
|
+
error: {
|
|
2841
|
+
code: "SUBSCRIPTION_CANCELLED",
|
|
2842
|
+
message: "Subscription cancelled"
|
|
2843
|
+
}
|
|
2661
2844
|
},
|
|
2662
2845
|
true
|
|
2663
2846
|
);
|
|
@@ -2667,7 +2850,9 @@ var Realtime = class {
|
|
|
2667
2850
|
}
|
|
2668
2851
|
async publish(channel, event, payload) {
|
|
2669
2852
|
if (!this.socket?.connected) {
|
|
2670
|
-
throw new Error(
|
|
2853
|
+
throw new Error(
|
|
2854
|
+
"Not connected to realtime server. Call connect() first."
|
|
2855
|
+
);
|
|
2671
2856
|
}
|
|
2672
2857
|
this.socket.emit("realtime:publish", { channel, event, payload });
|
|
2673
2858
|
}
|
|
@@ -2696,201 +2881,545 @@ var Realtime = class {
|
|
|
2696
2881
|
getPresenceState(channel) {
|
|
2697
2882
|
return [...this.subscriptions.get(channel)?.members.values() ?? []];
|
|
2698
2883
|
}
|
|
2699
|
-
};
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2884
|
+
};
|
|
2885
|
+
|
|
2886
|
+
// src/modules/storage.ts
|
|
2887
|
+
function generateObjectKey(filename) {
|
|
2888
|
+
const dotIndex = filename.lastIndexOf(".");
|
|
2889
|
+
const hasExt = dotIndex > 0;
|
|
2890
|
+
const ext = hasExt ? filename.slice(dotIndex) : "";
|
|
2891
|
+
const base = hasExt ? filename.slice(0, dotIndex) : filename;
|
|
2892
|
+
const sanitizedBase = base.replace(/[^a-zA-Z0-9-_]/g, "-").slice(0, 32) || "file";
|
|
2893
|
+
const timestamp = Date.now();
|
|
2894
|
+
const random = Math.random().toString(36).slice(2, 8);
|
|
2895
|
+
return `${sanitizedBase}-${timestamp}-${random}${ext}`;
|
|
2896
|
+
}
|
|
2897
|
+
var StorageBucket = class {
|
|
2898
|
+
constructor(bucketName, http) {
|
|
2899
|
+
this.bucketName = bucketName;
|
|
2900
|
+
this.http = http;
|
|
2901
|
+
}
|
|
2902
|
+
bucketName;
|
|
2903
|
+
http;
|
|
2904
|
+
/**
|
|
2905
|
+
* Upload a file to a specific key.
|
|
2906
|
+
* Uses the upload strategy from the backend (direct or presigned).
|
|
2907
|
+
* Standard PUT semantics: uploading to an existing key replaces the
|
|
2908
|
+
* current object in place.
|
|
2909
|
+
* @param path - The object key/path
|
|
2910
|
+
* @param file - File or Blob to upload
|
|
2911
|
+
*/
|
|
2912
|
+
async upload(path, file) {
|
|
2913
|
+
try {
|
|
2914
|
+
const strategyResponse = await this.http.post(
|
|
2915
|
+
`/api/storage/buckets/${this.bucketName}/upload-strategy`,
|
|
2916
|
+
{
|
|
2917
|
+
filename: path,
|
|
2918
|
+
contentType: file.type || "application/octet-stream",
|
|
2919
|
+
size: file.size
|
|
2920
|
+
}
|
|
2921
|
+
);
|
|
2922
|
+
if (strategyResponse.method === "presigned") {
|
|
2923
|
+
return await this.uploadWithPresignedUrl(strategyResponse, file);
|
|
2924
|
+
}
|
|
2925
|
+
if (strategyResponse.method === "direct") {
|
|
2926
|
+
const formData = new FormData();
|
|
2927
|
+
formData.append("file", file);
|
|
2928
|
+
const response = await this.http.request(
|
|
2929
|
+
"PUT",
|
|
2930
|
+
`/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(path)}`,
|
|
2931
|
+
{
|
|
2932
|
+
body: formData,
|
|
2933
|
+
headers: {
|
|
2934
|
+
// Don't set Content-Type, let browser set multipart boundary
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
);
|
|
2938
|
+
return { data: response, error: null };
|
|
2939
|
+
}
|
|
2940
|
+
throw new ResultError(
|
|
2941
|
+
`Unsupported upload method: ${strategyResponse.method}`,
|
|
2942
|
+
500,
|
|
2943
|
+
"STORAGE_ERROR"
|
|
2944
|
+
);
|
|
2945
|
+
} catch (error) {
|
|
2946
|
+
return {
|
|
2947
|
+
data: null,
|
|
2948
|
+
error: error instanceof ResultError ? error : new ResultError("Upload failed", 500, "STORAGE_ERROR")
|
|
2949
|
+
};
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
/**
|
|
2953
|
+
* Upload a file under an automatically generated, collision-free key.
|
|
2954
|
+
* The key is derived client-side from the filename (sanitized base +
|
|
2955
|
+
* timestamp + random suffix) and uploaded through the standard
|
|
2956
|
+
* {@link upload} path, so repeated uploads of the same file never
|
|
2957
|
+
* overwrite each other. Reads the filename structurally to avoid assuming
|
|
2958
|
+
* a global `File` (which Node 18 does not expose).
|
|
2959
|
+
* @param file - File or Blob to upload
|
|
2960
|
+
*/
|
|
2961
|
+
async uploadAuto(file) {
|
|
2962
|
+
const filename = "name" in file && typeof file.name === "string" ? file.name : "file";
|
|
2963
|
+
return this.upload(generateObjectKey(filename), file);
|
|
2964
|
+
}
|
|
2965
|
+
/**
|
|
2966
|
+
* Internal method to handle presigned URL uploads
|
|
2967
|
+
*/
|
|
2968
|
+
async uploadWithPresignedUrl(strategy, file) {
|
|
2969
|
+
try {
|
|
2970
|
+
const formData = new FormData();
|
|
2971
|
+
if (strategy.fields) {
|
|
2972
|
+
Object.entries(strategy.fields).forEach(([key, value]) => {
|
|
2973
|
+
formData.append(key, value);
|
|
2974
|
+
});
|
|
2975
|
+
}
|
|
2976
|
+
formData.append("file", file);
|
|
2977
|
+
const uploadResponse = await fetch(strategy.uploadUrl, {
|
|
2978
|
+
method: "POST",
|
|
2979
|
+
body: formData
|
|
2980
|
+
});
|
|
2981
|
+
if (!uploadResponse.ok) {
|
|
2982
|
+
throw new ResultError(
|
|
2983
|
+
`Upload to storage failed: ${uploadResponse.statusText}`,
|
|
2984
|
+
uploadResponse.status,
|
|
2985
|
+
"STORAGE_ERROR"
|
|
2986
|
+
);
|
|
2987
|
+
}
|
|
2988
|
+
if (strategy.confirmRequired && strategy.confirmUrl) {
|
|
2989
|
+
const confirmResponse = await this.http.post(
|
|
2990
|
+
strategy.confirmUrl,
|
|
2991
|
+
{
|
|
2992
|
+
size: file.size,
|
|
2993
|
+
contentType: file.type || "application/octet-stream"
|
|
2994
|
+
}
|
|
2995
|
+
);
|
|
2996
|
+
return { data: confirmResponse, error: null };
|
|
2997
|
+
}
|
|
2998
|
+
return {
|
|
2999
|
+
data: {
|
|
3000
|
+
key: strategy.key,
|
|
3001
|
+
bucket: this.bucketName,
|
|
3002
|
+
size: file.size,
|
|
3003
|
+
mimeType: file.type || "application/octet-stream",
|
|
3004
|
+
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3005
|
+
url: this.getPublicUrl(strategy.key).data?.publicUrl
|
|
3006
|
+
},
|
|
3007
|
+
error: null
|
|
3008
|
+
};
|
|
3009
|
+
} catch (error) {
|
|
3010
|
+
throw error instanceof ResultError ? error : new ResultError("Presigned upload failed", 500, "STORAGE_ERROR");
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
/**
|
|
3014
|
+
* Download a file
|
|
3015
|
+
* Uses the download strategy from backend (direct or presigned)
|
|
3016
|
+
* @param path - The object key/path
|
|
3017
|
+
* Returns the file as a Blob
|
|
3018
|
+
*/
|
|
3019
|
+
async download(path) {
|
|
3020
|
+
try {
|
|
3021
|
+
const encodedKey = encodeURIComponent(path);
|
|
3022
|
+
let strategyResponse;
|
|
3023
|
+
try {
|
|
3024
|
+
strategyResponse = await this.http.get(
|
|
3025
|
+
`/api/storage/buckets/${this.bucketName}/download-strategy/objects/${encodedKey}`
|
|
3026
|
+
);
|
|
3027
|
+
} catch (err) {
|
|
3028
|
+
const status = err instanceof ResultError ? err.statusCode : void 0;
|
|
3029
|
+
if (status === 404 || status === 405) {
|
|
3030
|
+
strategyResponse = await this.http.post(
|
|
3031
|
+
`/api/storage/buckets/${this.bucketName}/objects/${encodedKey}/download-strategy`,
|
|
3032
|
+
{}
|
|
3033
|
+
);
|
|
3034
|
+
} else {
|
|
3035
|
+
throw err;
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
const downloadUrl = strategyResponse.url;
|
|
3039
|
+
const headers = {};
|
|
3040
|
+
if (strategyResponse.method === "direct") {
|
|
3041
|
+
Object.assign(headers, this.http.getHeaders());
|
|
3042
|
+
}
|
|
3043
|
+
const response = await fetch(downloadUrl, {
|
|
3044
|
+
method: "GET",
|
|
3045
|
+
headers
|
|
3046
|
+
});
|
|
3047
|
+
if (!response.ok) {
|
|
3048
|
+
try {
|
|
3049
|
+
const error = await response.json();
|
|
3050
|
+
throw ResultError.fromApiError(error);
|
|
3051
|
+
} catch {
|
|
3052
|
+
throw new ResultError(
|
|
3053
|
+
`Download failed: ${response.statusText}`,
|
|
3054
|
+
response.status,
|
|
3055
|
+
"STORAGE_ERROR"
|
|
3056
|
+
);
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
const blob = await response.blob();
|
|
3060
|
+
return { data: blob, error: null };
|
|
3061
|
+
} catch (error) {
|
|
3062
|
+
return {
|
|
3063
|
+
data: null,
|
|
3064
|
+
error: error instanceof ResultError ? error : new ResultError("Download failed", 500, "STORAGE_ERROR")
|
|
3065
|
+
};
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
/**
|
|
3069
|
+
* Get the public URL for an object in a public bucket.
|
|
3070
|
+
*
|
|
3071
|
+
* Pure string construction — no network call, no auth. The URL only resolves
|
|
3072
|
+
* if the bucket is public; for private objects use {@link createSignedUrl}.
|
|
3073
|
+
*
|
|
3074
|
+
* @param path - The object key/path
|
|
3075
|
+
* @returns `{ data: { publicUrl }, error }` — matches the external SDK pattern,
|
|
3076
|
+
* so `const { data } = getPublicUrl(path)` then `data.publicUrl`.
|
|
3077
|
+
*/
|
|
3078
|
+
getPublicUrl(path) {
|
|
3079
|
+
const publicUrl = `${this.http.baseUrl}/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(path)}`;
|
|
3080
|
+
return { data: { publicUrl }, error: null };
|
|
2703
3081
|
}
|
|
2704
3082
|
/**
|
|
2705
|
-
*
|
|
2706
|
-
*
|
|
3083
|
+
* Resolve a download strategy (signed or direct URL) for an object with a
|
|
3084
|
+
* caller-supplied TTL. Prefers the canonical GET route and falls back to the
|
|
3085
|
+
* legacy POST alias so signed-URL creation still works against older backends
|
|
3086
|
+
* that predate the GET route (they return 404/405 for it). A genuine
|
|
3087
|
+
* "object not found" (STORAGE_NOT_FOUND) is not retried.
|
|
2707
3088
|
*/
|
|
2708
|
-
async
|
|
3089
|
+
async requestDownloadStrategy(path, expiresIn) {
|
|
3090
|
+
const encoded = encodeURIComponent(path);
|
|
2709
3091
|
try {
|
|
2710
|
-
|
|
2711
|
-
|
|
3092
|
+
return await this.http.get(
|
|
3093
|
+
`/api/storage/buckets/${this.bucketName}/download-strategy/objects/${encoded}`,
|
|
3094
|
+
{ params: { expiresIn: expiresIn.toString() } }
|
|
3095
|
+
);
|
|
2712
3096
|
} catch (error) {
|
|
2713
|
-
|
|
3097
|
+
const status = error instanceof ResultError ? error.statusCode : void 0;
|
|
3098
|
+
const isMissingRoute = (status === 404 || status === 405) && !(error instanceof ResultError && error.error === "STORAGE_NOT_FOUND");
|
|
3099
|
+
if (!isMissingRoute) {
|
|
2714
3100
|
throw error;
|
|
2715
3101
|
}
|
|
3102
|
+
return await this.http.post(
|
|
3103
|
+
`/api/storage/buckets/${this.bucketName}/objects/${encoded}/download-strategy`,
|
|
3104
|
+
{ expiresIn }
|
|
3105
|
+
);
|
|
3106
|
+
}
|
|
3107
|
+
}
|
|
3108
|
+
/**
|
|
3109
|
+
* Create a signed URL for an object.
|
|
3110
|
+
*
|
|
3111
|
+
* Returns a time-limited, credential-free URL that can be handed directly to
|
|
3112
|
+
* a browser (`<img src>`), an email, or a third party — no SDK or session is
|
|
3113
|
+
* needed to fetch it. Authorization is enforced when the URL is minted (the
|
|
3114
|
+
* caller must be allowed to read the object), so the resulting link is a
|
|
3115
|
+
* pre-authorized capability scoped to this one object until it expires.
|
|
3116
|
+
*
|
|
3117
|
+
* @param path - The object key/path
|
|
3118
|
+
* @param expiresIn - Lifetime in seconds (default 3600 = 1h, max 604800 = 7d).
|
|
3119
|
+
* Honored for private buckets; public buckets return their long-lived URL.
|
|
3120
|
+
*/
|
|
3121
|
+
async createSignedUrl(path, expiresIn = 3600) {
|
|
3122
|
+
try {
|
|
3123
|
+
const strategy = await this.requestDownloadStrategy(path, expiresIn);
|
|
3124
|
+
return {
|
|
3125
|
+
data: {
|
|
3126
|
+
signedUrl: strategy.url,
|
|
3127
|
+
expiresAt: strategy.expiresAt ? new Date(strategy.expiresAt).toISOString() : null
|
|
3128
|
+
},
|
|
3129
|
+
error: null
|
|
3130
|
+
};
|
|
3131
|
+
} catch (error) {
|
|
2716
3132
|
return {
|
|
2717
3133
|
data: null,
|
|
2718
|
-
error: error instanceof
|
|
2719
|
-
|
|
3134
|
+
error: error instanceof ResultError ? error : new ResultError(
|
|
3135
|
+
"Failed to create signed URL",
|
|
2720
3136
|
500,
|
|
2721
|
-
"
|
|
3137
|
+
"STORAGE_ERROR"
|
|
2722
3138
|
)
|
|
2723
3139
|
};
|
|
2724
3140
|
}
|
|
2725
3141
|
}
|
|
2726
|
-
};
|
|
2727
|
-
function providerEnvironmentPath(provider, environment) {
|
|
2728
|
-
return `/api/payments/${provider}/${encodeURIComponent(environment)}`;
|
|
2729
|
-
}
|
|
2730
|
-
var StripePayments = class {
|
|
2731
|
-
constructor(http) {
|
|
2732
|
-
this.http = http;
|
|
2733
|
-
}
|
|
2734
3142
|
/**
|
|
2735
|
-
* Create
|
|
3143
|
+
* Create signed URLs for multiple objects in a single call.
|
|
2736
3144
|
*
|
|
2737
|
-
*
|
|
2738
|
-
*
|
|
2739
|
-
* const { data, error } = await client.payments.stripe.createCheckoutSession('test', {
|
|
2740
|
-
* mode: 'payment',
|
|
2741
|
-
* lineItems: [{ priceId: 'price_123', quantity: 1 }],
|
|
2742
|
-
* successUrl: `${window.location.origin}/success`,
|
|
2743
|
-
* cancelUrl: `${window.location.origin}/pricing`
|
|
2744
|
-
* });
|
|
3145
|
+
* Each entry resolves independently: a failure on one key (not found / not
|
|
3146
|
+
* permitted) is reported on that entry's `error` without failing the rest.
|
|
2745
3147
|
*
|
|
2746
|
-
*
|
|
2747
|
-
*
|
|
2748
|
-
* }
|
|
2749
|
-
* ```
|
|
3148
|
+
* @param paths - The object keys/paths
|
|
3149
|
+
* @param expiresIn - Lifetime in seconds (default 3600 = 1h, max 604800 = 7d)
|
|
2750
3150
|
*/
|
|
2751
|
-
async
|
|
3151
|
+
async createSignedUrls(paths, expiresIn = 3600) {
|
|
2752
3152
|
try {
|
|
2753
|
-
const data = await
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
3153
|
+
const data = await Promise.all(
|
|
3154
|
+
paths.map(async (path) => {
|
|
3155
|
+
const { data: signed, error } = await this.createSignedUrl(
|
|
3156
|
+
path,
|
|
3157
|
+
expiresIn
|
|
3158
|
+
);
|
|
3159
|
+
return {
|
|
3160
|
+
path,
|
|
3161
|
+
signedUrl: signed?.signedUrl ?? null,
|
|
3162
|
+
error: error ? error.message : null
|
|
3163
|
+
};
|
|
3164
|
+
})
|
|
2757
3165
|
);
|
|
2758
3166
|
return { data, error: null };
|
|
2759
3167
|
} catch (error) {
|
|
2760
|
-
return
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
3168
|
+
return {
|
|
3169
|
+
data: null,
|
|
3170
|
+
error: error instanceof ResultError ? error : new ResultError(
|
|
3171
|
+
"Failed to create signed URLs",
|
|
3172
|
+
500,
|
|
3173
|
+
"STORAGE_ERROR"
|
|
3174
|
+
)
|
|
3175
|
+
};
|
|
2764
3176
|
}
|
|
2765
3177
|
}
|
|
2766
3178
|
/**
|
|
2767
|
-
*
|
|
3179
|
+
* List objects in the bucket
|
|
3180
|
+
* @param prefix - Filter by key prefix
|
|
3181
|
+
* @param search - Search in file names
|
|
3182
|
+
* @param limit - Maximum number of results (default: 100, max: 1000)
|
|
3183
|
+
* @param offset - Number of results to skip
|
|
2768
3184
|
*/
|
|
2769
|
-
async
|
|
3185
|
+
async list(options) {
|
|
2770
3186
|
try {
|
|
2771
|
-
const
|
|
2772
|
-
|
|
2773
|
-
|
|
3187
|
+
const params = {};
|
|
3188
|
+
if (options?.prefix) {
|
|
3189
|
+
params.prefix = options.prefix;
|
|
3190
|
+
}
|
|
3191
|
+
if (options?.search) {
|
|
3192
|
+
params.search = options.search;
|
|
3193
|
+
}
|
|
3194
|
+
if (options?.limit) {
|
|
3195
|
+
params.limit = options.limit.toString();
|
|
3196
|
+
}
|
|
3197
|
+
if (options?.offset) {
|
|
3198
|
+
params.offset = options.offset.toString();
|
|
3199
|
+
}
|
|
3200
|
+
const response = await this.http.get(
|
|
3201
|
+
`/api/storage/buckets/${this.bucketName}/objects`,
|
|
3202
|
+
{ params }
|
|
2774
3203
|
);
|
|
2775
|
-
return { data, error: null };
|
|
3204
|
+
return { data: response, error: null };
|
|
2776
3205
|
} catch (error) {
|
|
2777
|
-
return
|
|
2778
|
-
|
|
2779
|
-
|
|
3206
|
+
return {
|
|
3207
|
+
data: null,
|
|
3208
|
+
error: error instanceof ResultError ? error : new ResultError("List failed", 500, "STORAGE_ERROR")
|
|
3209
|
+
};
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
/**
|
|
3213
|
+
* Delete one or more files.
|
|
3214
|
+
*
|
|
3215
|
+
* A string uses the single-object endpoint. An array uses the batch endpoint,
|
|
3216
|
+
* which accepts at most 1000 keys and returns one result per key.
|
|
3217
|
+
*
|
|
3218
|
+
* @param pathOrPaths - One object key/path or an array of object keys/paths
|
|
3219
|
+
*/
|
|
3220
|
+
async remove(pathOrPaths) {
|
|
3221
|
+
try {
|
|
3222
|
+
const response = Array.isArray(pathOrPaths) ? await this.http.delete(
|
|
3223
|
+
`/api/storage/buckets/${this.bucketName}/objects`,
|
|
3224
|
+
{ body: { keys: pathOrPaths } }
|
|
3225
|
+
) : await this.http.delete(
|
|
3226
|
+
`/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(pathOrPaths)}`
|
|
2780
3227
|
);
|
|
3228
|
+
return { data: response, error: null };
|
|
3229
|
+
} catch (error) {
|
|
3230
|
+
return {
|
|
3231
|
+
data: null,
|
|
3232
|
+
error: error instanceof ResultError ? error : new ResultError("Delete failed", 500, "STORAGE_ERROR")
|
|
3233
|
+
};
|
|
2781
3234
|
}
|
|
2782
3235
|
}
|
|
2783
3236
|
};
|
|
2784
|
-
var
|
|
3237
|
+
var Storage = class {
|
|
2785
3238
|
constructor(http) {
|
|
2786
3239
|
this.http = http;
|
|
2787
3240
|
}
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
} catch (error) {
|
|
2796
|
-
return wrapError(error, "Razorpay order creation failed");
|
|
2797
|
-
}
|
|
3241
|
+
http;
|
|
3242
|
+
/**
|
|
3243
|
+
* Get a bucket instance for operations
|
|
3244
|
+
* @param bucketName - Name of the bucket
|
|
3245
|
+
*/
|
|
3246
|
+
from(bucketName) {
|
|
3247
|
+
return new StorageBucket(bucketName, this.http);
|
|
2798
3248
|
}
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
3249
|
+
};
|
|
3250
|
+
|
|
3251
|
+
// src/modules/support.ts
|
|
3252
|
+
var DEFAULT_ENDPOINT2 = "https://beta.result.dev";
|
|
3253
|
+
var Support = class {
|
|
3254
|
+
handle;
|
|
3255
|
+
endpoint;
|
|
3256
|
+
// Fallback for non-browser runtimes and private mode.
|
|
3257
|
+
mem = { threads: [] };
|
|
3258
|
+
constructor(options) {
|
|
3259
|
+
this.handle = options?.handle ?? "";
|
|
3260
|
+
this.endpoint = (options?.endpoint || DEFAULT_ENDPOINT2).replace(/\/$/, "");
|
|
2809
3261
|
}
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
}
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
3262
|
+
/**
|
|
3263
|
+
* Start a new support conversation. The returned access key is stored
|
|
3264
|
+
* locally so `getThread`/`sendMessage` work without passing it around.
|
|
3265
|
+
*/
|
|
3266
|
+
async startThread(options) {
|
|
3267
|
+
const guard = this.requireHandle();
|
|
3268
|
+
if (guard) return { data: null, error: guard };
|
|
3269
|
+
const { data, error } = await this.request(
|
|
3270
|
+
"POST",
|
|
3271
|
+
"/api/support/threads",
|
|
3272
|
+
{
|
|
3273
|
+
username: this.handle,
|
|
3274
|
+
email: options.email,
|
|
3275
|
+
name: options.name,
|
|
3276
|
+
message: options.message
|
|
3277
|
+
}
|
|
3278
|
+
);
|
|
3279
|
+
if (data) {
|
|
3280
|
+
this.remember(data.id, data.accessKey, options);
|
|
3281
|
+
}
|
|
3282
|
+
return { data, error };
|
|
3283
|
+
}
|
|
3284
|
+
/** Fetch a thread (status + full message history) with its stored access key. */
|
|
3285
|
+
async getThread(threadId, accessKey) {
|
|
3286
|
+
const guard = this.requireHandle();
|
|
3287
|
+
if (guard) return { data: null, error: guard };
|
|
3288
|
+
const key = accessKey ?? this.keyFor(threadId);
|
|
3289
|
+
if (!key) {
|
|
3290
|
+
return { data: null, error: this.missingKey(threadId) };
|
|
3291
|
+
}
|
|
3292
|
+
const { data, error } = await this.request(
|
|
3293
|
+
"GET",
|
|
3294
|
+
`/api/support/threads/${encodeURIComponent(threadId)}?key=${encodeURIComponent(key)}`
|
|
3295
|
+
);
|
|
3296
|
+
return { data: data?.thread ?? null, error };
|
|
3297
|
+
}
|
|
3298
|
+
/** Send a visitor reply on an existing thread (reopens it if closed). */
|
|
3299
|
+
async sendMessage(threadId, message, accessKey) {
|
|
3300
|
+
const guard = this.requireHandle();
|
|
3301
|
+
if (guard) return { data: null, error: guard };
|
|
3302
|
+
const key = accessKey ?? this.keyFor(threadId);
|
|
3303
|
+
if (!key) {
|
|
3304
|
+
return { data: null, error: this.missingKey(threadId) };
|
|
3305
|
+
}
|
|
3306
|
+
const { data, error } = await this.request(
|
|
3307
|
+
"POST",
|
|
3308
|
+
`/api/support/threads/${encodeURIComponent(threadId)}/messages`,
|
|
3309
|
+
{ message, key }
|
|
3310
|
+
);
|
|
3311
|
+
return { data, error };
|
|
3312
|
+
}
|
|
3313
|
+
/** Threads started from this browser (ids only; fetch each with getThread). */
|
|
3314
|
+
listThreads() {
|
|
3315
|
+
return this.load().threads.map(({ id }) => ({ id }));
|
|
3316
|
+
}
|
|
3317
|
+
/** The contact details used when this browser last started a thread. */
|
|
3318
|
+
getContact() {
|
|
3319
|
+
return this.load().contact;
|
|
3320
|
+
}
|
|
3321
|
+
// -- internals -------------------------------------------------------------
|
|
3322
|
+
requireHandle() {
|
|
3323
|
+
if (this.handle) return null;
|
|
3324
|
+
return new ResultError(
|
|
3325
|
+
"Missing support handle. Pass support: { handle } to createClient().",
|
|
3326
|
+
400,
|
|
3327
|
+
"MISSING_FIELD"
|
|
3328
|
+
);
|
|
2823
3329
|
}
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
return { data, error: null };
|
|
2831
|
-
} catch (error) {
|
|
2832
|
-
return wrapError(
|
|
2833
|
-
error,
|
|
2834
|
-
"Razorpay subscription verification failed"
|
|
2835
|
-
);
|
|
2836
|
-
}
|
|
3330
|
+
missingKey(threadId) {
|
|
3331
|
+
return new ResultError(
|
|
3332
|
+
`No access key stored for thread ${threadId}. Pass the accessKey returned by startThread().`,
|
|
3333
|
+
401,
|
|
3334
|
+
"AUTH_UNAUTHORIZED"
|
|
3335
|
+
);
|
|
2837
3336
|
}
|
|
2838
|
-
async
|
|
3337
|
+
async request(method, path, body) {
|
|
2839
3338
|
try {
|
|
2840
|
-
const
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
)
|
|
2844
|
-
|
|
2845
|
-
);
|
|
2846
|
-
|
|
3339
|
+
const response = await fetch(`${this.endpoint}${path}`, {
|
|
3340
|
+
method,
|
|
3341
|
+
headers: body ? { "Content-Type": "application/json" } : void 0,
|
|
3342
|
+
body: body ? JSON.stringify(body) : void 0
|
|
3343
|
+
});
|
|
3344
|
+
const payload = await response.json().catch(() => null);
|
|
3345
|
+
if (!response.ok) {
|
|
3346
|
+
return {
|
|
3347
|
+
data: null,
|
|
3348
|
+
error: new ResultError(
|
|
3349
|
+
payload?.error?.message ?? `Support request failed (${response.status})`,
|
|
3350
|
+
response.status,
|
|
3351
|
+
payload?.error?.code ?? "UNKNOWN_ERROR"
|
|
3352
|
+
)
|
|
3353
|
+
};
|
|
3354
|
+
}
|
|
3355
|
+
return { data: payload, error: null };
|
|
2847
3356
|
} catch (error) {
|
|
2848
|
-
|
|
2849
|
-
error
|
|
2850
|
-
|
|
2851
|
-
|
|
3357
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
3358
|
+
throw error;
|
|
3359
|
+
}
|
|
3360
|
+
return {
|
|
3361
|
+
data: null,
|
|
3362
|
+
error: new ResultError(
|
|
3363
|
+
error instanceof Error ? error.message : "Support request failed",
|
|
3364
|
+
500,
|
|
3365
|
+
"UNKNOWN_ERROR"
|
|
3366
|
+
)
|
|
3367
|
+
};
|
|
2852
3368
|
}
|
|
2853
3369
|
}
|
|
2854
|
-
|
|
3370
|
+
storageKey() {
|
|
3371
|
+
return `rsup_${this.handle}`;
|
|
3372
|
+
}
|
|
3373
|
+
load() {
|
|
2855
3374
|
try {
|
|
2856
|
-
const
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
)
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
} catch
|
|
2864
|
-
return wrapError(
|
|
2865
|
-
error,
|
|
2866
|
-
"Razorpay subscription pause failed"
|
|
2867
|
-
);
|
|
3375
|
+
const raw = localStorage.getItem(this.storageKey());
|
|
3376
|
+
if (raw) {
|
|
3377
|
+
const parsed = JSON.parse(raw);
|
|
3378
|
+
if (parsed && Array.isArray(parsed.threads)) {
|
|
3379
|
+
return parsed;
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
} catch {
|
|
2868
3383
|
}
|
|
3384
|
+
return this.mem;
|
|
2869
3385
|
}
|
|
2870
|
-
|
|
3386
|
+
save(state) {
|
|
3387
|
+
this.mem = state;
|
|
2871
3388
|
try {
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
subscriptionId
|
|
2875
|
-
)}/resume`,
|
|
2876
|
-
{}
|
|
2877
|
-
);
|
|
2878
|
-
return { data, error: null };
|
|
2879
|
-
} catch (error) {
|
|
2880
|
-
return wrapError(
|
|
2881
|
-
error,
|
|
2882
|
-
"Razorpay subscription resume failed"
|
|
2883
|
-
);
|
|
3389
|
+
localStorage.setItem(this.storageKey(), JSON.stringify(state));
|
|
3390
|
+
} catch {
|
|
2884
3391
|
}
|
|
2885
3392
|
}
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
3393
|
+
keyFor(threadId) {
|
|
3394
|
+
return this.load().threads.find((thread) => thread.id === threadId)?.key;
|
|
3395
|
+
}
|
|
3396
|
+
remember(id, key, options) {
|
|
3397
|
+
const state = this.load();
|
|
3398
|
+
const threads = state.threads.filter((thread) => thread.id !== id);
|
|
3399
|
+
threads.unshift({ id, key });
|
|
3400
|
+
this.save({
|
|
3401
|
+
contact: {
|
|
3402
|
+
email: options.email,
|
|
3403
|
+
name: options.name ?? state.contact?.name
|
|
3404
|
+
},
|
|
3405
|
+
threads
|
|
3406
|
+
});
|
|
2891
3407
|
}
|
|
2892
3408
|
};
|
|
2893
|
-
|
|
3409
|
+
|
|
3410
|
+
// src/client.ts
|
|
3411
|
+
var ResultClient = class {
|
|
3412
|
+
http;
|
|
3413
|
+
tokenManager;
|
|
3414
|
+
auth;
|
|
3415
|
+
database;
|
|
3416
|
+
storage;
|
|
3417
|
+
ai;
|
|
3418
|
+
functions;
|
|
3419
|
+
realtime;
|
|
3420
|
+
emails;
|
|
3421
|
+
analytics;
|
|
3422
|
+
support;
|
|
2894
3423
|
constructor(config = {}) {
|
|
2895
3424
|
const logger = new Logger(config.debug);
|
|
2896
3425
|
this.tokenManager = new TokenManager();
|
|
@@ -2915,7 +3444,8 @@ var InsForgeClient = class {
|
|
|
2915
3444
|
() => this.http.getValidAccessToken()
|
|
2916
3445
|
);
|
|
2917
3446
|
this.emails = new Emails(this.http);
|
|
2918
|
-
this.
|
|
3447
|
+
this.analytics = new Analytics(config.analytics);
|
|
3448
|
+
this.support = new Support(config.support);
|
|
2919
3449
|
}
|
|
2920
3450
|
/**
|
|
2921
3451
|
* Get the underlying HTTP client for custom requests
|
|
@@ -2939,16 +3469,16 @@ var InsForgeClient = class {
|
|
|
2939
3469
|
*
|
|
2940
3470
|
* Use this when an external auth provider (Better Auth, Clerk, Auth0,
|
|
2941
3471
|
* WorkOS, Kinde, Stytch, …) issues the JWT and you need to keep the
|
|
2942
|
-
* long-lived
|
|
3472
|
+
* long-lived Result client in sync. Without this, you'd have to call
|
|
2943
3473
|
* `client.getHttpClient().setAuthToken(token)` AND reach into the private
|
|
2944
3474
|
* realtime token manager separately.
|
|
2945
3475
|
*
|
|
2946
3476
|
* @example
|
|
2947
3477
|
* ```typescript
|
|
2948
|
-
* import { AuthChangeEvent } from '@
|
|
3478
|
+
* import { AuthChangeEvent } from '@resultdev/sdk';
|
|
2949
3479
|
*
|
|
2950
3480
|
* // Refresh a third-party-issued JWT periodically
|
|
2951
|
-
* const { token } = await fetch('/api/
|
|
3481
|
+
* const { token } = await fetch('/api/backend-token').then((r) => r.json());
|
|
2952
3482
|
* client.setAccessToken(token, AuthChangeEvent.TOKEN_REFRESHED);
|
|
2953
3483
|
*
|
|
2954
3484
|
* // Sign-out
|
|
@@ -2963,17 +3493,11 @@ var InsForgeClient = class {
|
|
|
2963
3493
|
this.tokenManager.setAccessToken(token, event);
|
|
2964
3494
|
}
|
|
2965
3495
|
}
|
|
2966
|
-
/**
|
|
2967
|
-
* Future modules will be added here:
|
|
2968
|
-
* - database: Database operations
|
|
2969
|
-
* - storage: File storage operations
|
|
2970
|
-
* - functions: Serverless functions
|
|
2971
|
-
* - tables: Table management
|
|
2972
|
-
* - metadata: Backend metadata
|
|
2973
|
-
*/
|
|
2974
3496
|
};
|
|
3497
|
+
|
|
3498
|
+
// src/index.ts
|
|
2975
3499
|
function createClient(config = {}) {
|
|
2976
|
-
return new
|
|
3500
|
+
return new ResultClient(config);
|
|
2977
3501
|
}
|
|
2978
3502
|
function createAdminClient(config) {
|
|
2979
3503
|
const { apiKey: rawApiKey, ...clientConfig } = config ?? {};
|
|
@@ -2981,27 +3505,32 @@ function createAdminClient(config) {
|
|
|
2981
3505
|
if (!apiKey) {
|
|
2982
3506
|
throw new Error("Missing apiKey. Pass apiKey to createAdminClient().");
|
|
2983
3507
|
}
|
|
2984
|
-
return new
|
|
3508
|
+
return new ResultClient({
|
|
2985
3509
|
...clientConfig,
|
|
2986
3510
|
accessToken: apiKey,
|
|
2987
3511
|
isServerMode: true
|
|
2988
3512
|
});
|
|
2989
3513
|
}
|
|
3514
|
+
var src_default = ResultClient;
|
|
2990
3515
|
export {
|
|
2991
3516
|
AI,
|
|
3517
|
+
Analytics,
|
|
2992
3518
|
Auth,
|
|
2993
3519
|
AuthChangeEvent,
|
|
2994
3520
|
Database,
|
|
3521
|
+
ERROR_CODES,
|
|
2995
3522
|
Emails,
|
|
2996
3523
|
Functions,
|
|
2997
3524
|
HttpClient,
|
|
2998
|
-
InsForgeClient,
|
|
2999
|
-
InsForgeError,
|
|
3000
3525
|
Logger,
|
|
3001
|
-
|
|
3526
|
+
OAUTH_PROVIDERS,
|
|
3002
3527
|
Realtime,
|
|
3528
|
+
ResultClient,
|
|
3529
|
+
ResultError,
|
|
3003
3530
|
Storage,
|
|
3004
3531
|
StorageBucket,
|
|
3532
|
+
Support,
|
|
3005
3533
|
createAdminClient,
|
|
3006
|
-
createClient
|
|
3534
|
+
createClient,
|
|
3535
|
+
src_default as default
|
|
3007
3536
|
};
|