@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/ssr.js
CHANGED
|
@@ -1,20 +1,250 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
// src/lib/jwt.ts
|
|
2
|
+
function decodeBase64Url(input) {
|
|
3
|
+
const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
4
|
+
const padded = normalized.padEnd(
|
|
5
|
+
normalized.length + (4 - normalized.length % 4) % 4,
|
|
6
|
+
"="
|
|
7
|
+
);
|
|
8
|
+
const binary = atob(padded);
|
|
9
|
+
const bytes2 = Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
10
|
+
return new TextDecoder().decode(bytes2);
|
|
11
|
+
}
|
|
12
|
+
function getJwtExpiration(token) {
|
|
13
|
+
if (!token) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const [, payload] = token.split(".");
|
|
17
|
+
if (!payload) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const parsed = JSON.parse(decodeBase64Url(payload));
|
|
22
|
+
if (typeof parsed.exp !== "number" || !Number.isFinite(parsed.exp)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
return new Date(parsed.exp * 1e3);
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function isJwtExpiredOrExpiring(token, leewaySeconds = 60) {
|
|
31
|
+
if (!token) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const expires = getJwtExpiration(token);
|
|
35
|
+
if (!expires) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
return expires.getTime() <= Date.now() + leewaySeconds * 1e3;
|
|
39
|
+
}
|
|
5
40
|
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
var
|
|
41
|
+
// src/ssr/cookies.ts
|
|
42
|
+
var DEFAULT_ACCESS_TOKEN_COOKIE = "result_access_token";
|
|
43
|
+
var DEFAULT_REFRESH_TOKEN_COOKIE = "result_refresh_token";
|
|
44
|
+
var EXPIRED_DATE = /* @__PURE__ */ new Date(0);
|
|
45
|
+
function getAccessTokenCookieName(names) {
|
|
46
|
+
return names?.accessToken ?? DEFAULT_ACCESS_TOKEN_COOKIE;
|
|
47
|
+
}
|
|
48
|
+
function getRefreshTokenCookieName(names) {
|
|
49
|
+
return names?.refreshToken ?? DEFAULT_REFRESH_TOKEN_COOKIE;
|
|
50
|
+
}
|
|
51
|
+
function getCookieValue(cookies, name) {
|
|
52
|
+
if (!cookies) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const value = cookies.get(name);
|
|
56
|
+
if (typeof value === "string") {
|
|
57
|
+
return value || null;
|
|
58
|
+
}
|
|
59
|
+
if (value && typeof value.value === "string") {
|
|
60
|
+
return value.value || null;
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
function getCookieValueFromHeader(cookieHeader, name) {
|
|
65
|
+
if (!cookieHeader) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const parts = cookieHeader.split(";");
|
|
69
|
+
for (const part of parts) {
|
|
70
|
+
const [rawName, ...rawValue] = part.trim().split("=");
|
|
71
|
+
if (rawName !== name) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
return decodeURIComponent(rawValue.join("="));
|
|
76
|
+
} catch {
|
|
77
|
+
return rawValue.join("=");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
function getBrowserCookie(name) {
|
|
83
|
+
if (typeof document === "undefined") {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
return getCookieValueFromHeader(document.cookie, name);
|
|
87
|
+
}
|
|
88
|
+
function defaultCookieOptions() {
|
|
89
|
+
const secure = typeof process !== "undefined" ? process.env.NODE_ENV === "production" : typeof location !== "undefined" && location.protocol === "https:";
|
|
90
|
+
return {
|
|
91
|
+
path: "/",
|
|
92
|
+
sameSite: "lax",
|
|
93
|
+
secure
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function accessTokenCookieOptions(token, overrides) {
|
|
97
|
+
return {
|
|
98
|
+
...defaultCookieOptions(),
|
|
99
|
+
httpOnly: false,
|
|
100
|
+
expires: getJwtExpiration(token) ?? void 0,
|
|
101
|
+
...overrides
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function refreshTokenCookieOptions(token, overrides) {
|
|
105
|
+
return {
|
|
106
|
+
...defaultCookieOptions(),
|
|
107
|
+
httpOnly: true,
|
|
108
|
+
expires: getJwtExpiration(token) ?? void 0,
|
|
109
|
+
...overrides
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function expiredCookieOptions(overrides) {
|
|
113
|
+
const {
|
|
114
|
+
expires: _expires,
|
|
115
|
+
maxAge: _maxAge,
|
|
116
|
+
...safeOverrides
|
|
117
|
+
} = overrides ?? {};
|
|
118
|
+
return {
|
|
119
|
+
...defaultCookieOptions(),
|
|
120
|
+
...safeOverrides,
|
|
121
|
+
expires: EXPIRED_DATE,
|
|
122
|
+
maxAge: 0
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function setCookie(cookies, name, value, options) {
|
|
126
|
+
if (!cookies?.set) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
cookies.set(name, value, options);
|
|
130
|
+
}
|
|
131
|
+
function deleteCookie(cookies, name, options) {
|
|
132
|
+
if (!cookies) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (cookies.set) {
|
|
136
|
+
cookies.set(name, "", expiredCookieOptions(options));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
cookies.delete?.(name);
|
|
140
|
+
}
|
|
141
|
+
function serializeCookie(name, value, options = {}) {
|
|
142
|
+
const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
|
|
143
|
+
if (options.maxAge !== void 0) {
|
|
144
|
+
parts.push(`Max-Age=${options.maxAge}`);
|
|
145
|
+
}
|
|
146
|
+
if (options.domain) {
|
|
147
|
+
parts.push(`Domain=${options.domain}`);
|
|
148
|
+
}
|
|
149
|
+
if (options.path) {
|
|
150
|
+
parts.push(`Path=${options.path}`);
|
|
151
|
+
}
|
|
152
|
+
if (options.expires) {
|
|
153
|
+
parts.push(`Expires=${options.expires.toUTCString()}`);
|
|
154
|
+
}
|
|
155
|
+
if (options.httpOnly) {
|
|
156
|
+
parts.push("HttpOnly");
|
|
157
|
+
}
|
|
158
|
+
if (options.secure) {
|
|
159
|
+
parts.push("Secure");
|
|
160
|
+
}
|
|
161
|
+
if (options.sameSite) {
|
|
162
|
+
const sameSite = options.sameSite.charAt(0).toUpperCase() + options.sameSite.slice(1);
|
|
163
|
+
parts.push(`SameSite=${sameSite}`);
|
|
164
|
+
}
|
|
165
|
+
return parts.join("; ");
|
|
166
|
+
}
|
|
167
|
+
function appendSetCookie(headers, name, value, options) {
|
|
168
|
+
headers.append("Set-Cookie", serializeCookie(name, value, options));
|
|
169
|
+
}
|
|
170
|
+
function setAuthCookies(cookies, tokens, settings = {}) {
|
|
171
|
+
const accessName = getAccessTokenCookieName(settings.names);
|
|
172
|
+
const refreshName = getRefreshTokenCookieName(settings.names);
|
|
173
|
+
const accessOptions = accessTokenCookieOptions(
|
|
174
|
+
tokens.accessToken,
|
|
175
|
+
settings.options?.accessToken
|
|
176
|
+
);
|
|
177
|
+
setCookie(cookies, accessName, tokens.accessToken, accessOptions);
|
|
178
|
+
if (tokens.refreshToken) {
|
|
179
|
+
setCookie(
|
|
180
|
+
cookies,
|
|
181
|
+
refreshName,
|
|
182
|
+
tokens.refreshToken,
|
|
183
|
+
refreshTokenCookieOptions(
|
|
184
|
+
tokens.refreshToken,
|
|
185
|
+
settings.options?.refreshToken
|
|
186
|
+
)
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function clearAuthCookies(cookies, settings = {}) {
|
|
191
|
+
const accessName = getAccessTokenCookieName(settings.names);
|
|
192
|
+
const refreshName = getRefreshTokenCookieName(settings.names);
|
|
193
|
+
const accessOptions = expiredCookieOptions(settings.options?.accessToken);
|
|
194
|
+
const refreshOptions = expiredCookieOptions(settings.options?.refreshToken);
|
|
195
|
+
deleteCookie(cookies, accessName, accessOptions);
|
|
196
|
+
deleteCookie(cookies, refreshName, refreshOptions);
|
|
197
|
+
}
|
|
198
|
+
function setAuthCookieHeaders(headers, tokens, settings = {}) {
|
|
199
|
+
const accessName = getAccessTokenCookieName(settings.names);
|
|
200
|
+
const refreshName = getRefreshTokenCookieName(settings.names);
|
|
201
|
+
appendSetCookie(
|
|
202
|
+
headers,
|
|
203
|
+
accessName,
|
|
204
|
+
tokens.accessToken,
|
|
205
|
+
accessTokenCookieOptions(tokens.accessToken, settings.options?.accessToken)
|
|
206
|
+
);
|
|
207
|
+
if (tokens.refreshToken) {
|
|
208
|
+
appendSetCookie(
|
|
209
|
+
headers,
|
|
210
|
+
refreshName,
|
|
211
|
+
tokens.refreshToken,
|
|
212
|
+
refreshTokenCookieOptions(
|
|
213
|
+
tokens.refreshToken,
|
|
214
|
+
settings.options?.refreshToken
|
|
215
|
+
)
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function clearAuthCookieHeaders(headers, settings = {}) {
|
|
220
|
+
appendSetCookie(
|
|
221
|
+
headers,
|
|
222
|
+
getAccessTokenCookieName(settings.names),
|
|
223
|
+
"",
|
|
224
|
+
expiredCookieOptions(settings.options?.accessToken)
|
|
225
|
+
);
|
|
226
|
+
appendSetCookie(
|
|
227
|
+
headers,
|
|
228
|
+
getRefreshTokenCookieName(settings.names),
|
|
229
|
+
"",
|
|
230
|
+
expiredCookieOptions(settings.options?.refreshToken)
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/types.ts
|
|
235
|
+
var ResultError = class _ResultError extends Error {
|
|
236
|
+
statusCode;
|
|
237
|
+
error;
|
|
238
|
+
nextActions;
|
|
9
239
|
constructor(message, statusCode, error, nextActions) {
|
|
10
240
|
super(message);
|
|
11
|
-
this.name = "
|
|
241
|
+
this.name = "ResultError";
|
|
12
242
|
this.statusCode = statusCode;
|
|
13
243
|
this.error = error;
|
|
14
244
|
this.nextActions = nextActions;
|
|
15
245
|
}
|
|
16
246
|
static fromApiError(apiError) {
|
|
17
|
-
return new
|
|
247
|
+
return new _ResultError(
|
|
18
248
|
apiError.message,
|
|
19
249
|
apiError.statusCode,
|
|
20
250
|
apiError.error,
|
|
@@ -22,7 +252,14 @@ var InsForgeError = class _InsForgeError extends Error {
|
|
|
22
252
|
);
|
|
23
253
|
}
|
|
24
254
|
};
|
|
25
|
-
|
|
255
|
+
|
|
256
|
+
// src/lib/logger.ts
|
|
257
|
+
var SENSITIVE_HEADERS = [
|
|
258
|
+
"authorization",
|
|
259
|
+
"x-api-key",
|
|
260
|
+
"cookie",
|
|
261
|
+
"set-cookie"
|
|
262
|
+
];
|
|
26
263
|
var SENSITIVE_BODY_KEYS = [
|
|
27
264
|
"password",
|
|
28
265
|
"token",
|
|
@@ -97,6 +334,9 @@ function formatBody(body) {
|
|
|
97
334
|
}
|
|
98
335
|
}
|
|
99
336
|
var Logger = class {
|
|
337
|
+
/** Whether debug logging is currently enabled */
|
|
338
|
+
enabled;
|
|
339
|
+
customLog;
|
|
100
340
|
/**
|
|
101
341
|
* Creates a new Logger instance.
|
|
102
342
|
* @param debug - Set to true to enable console logging, or pass a custom log function
|
|
@@ -119,7 +359,7 @@ var Logger = class {
|
|
|
119
359
|
if (!this.enabled) {
|
|
120
360
|
return;
|
|
121
361
|
}
|
|
122
|
-
const formatted = `[
|
|
362
|
+
const formatted = `[Result Debug] ${message}`;
|
|
123
363
|
if (this.customLog) {
|
|
124
364
|
this.customLog(formatted, ...args);
|
|
125
365
|
} else {
|
|
@@ -135,7 +375,7 @@ var Logger = class {
|
|
|
135
375
|
if (!this.enabled) {
|
|
136
376
|
return;
|
|
137
377
|
}
|
|
138
|
-
const formatted = `[
|
|
378
|
+
const formatted = `[Result Debug] ${message}`;
|
|
139
379
|
if (this.customLog) {
|
|
140
380
|
this.customLog(formatted, ...args);
|
|
141
381
|
} else {
|
|
@@ -151,7 +391,7 @@ var Logger = class {
|
|
|
151
391
|
if (!this.enabled) {
|
|
152
392
|
return;
|
|
153
393
|
}
|
|
154
|
-
const formatted = `[
|
|
394
|
+
const formatted = `[Result Debug] ${message}`;
|
|
155
395
|
if (this.customLog) {
|
|
156
396
|
this.customLog(formatted, ...args);
|
|
157
397
|
} else {
|
|
@@ -176,7 +416,7 @@ var Logger = class {
|
|
|
176
416
|
}
|
|
177
417
|
const formattedBody = formatBody(sanitizeBody(body));
|
|
178
418
|
if (formattedBody) {
|
|
179
|
-
const truncated = formattedBody.length > 1e3 ? formattedBody.slice(0, 1e3)
|
|
419
|
+
const truncated = formattedBody.length > 1e3 ? `${formattedBody.slice(0, 1e3)}... [truncated]` : formattedBody;
|
|
180
420
|
parts.push(` Body: ${truncated}`);
|
|
181
421
|
}
|
|
182
422
|
this.log(parts.join("\n"));
|
|
@@ -197,7 +437,7 @@ var Logger = class {
|
|
|
197
437
|
const parts = [`\u2190 ${method} ${url} ${status} (${durationMs}ms)`];
|
|
198
438
|
const formattedBody = formatBody(sanitizeBody(body));
|
|
199
439
|
if (formattedBody) {
|
|
200
|
-
const truncated = formattedBody.length > 1e3 ? formattedBody.slice(0, 1e3)
|
|
440
|
+
const truncated = formattedBody.length > 1e3 ? `${formattedBody.slice(0, 1e3)}... [truncated]` : formattedBody;
|
|
201
441
|
parts.push(` Body: ${truncated}`);
|
|
202
442
|
}
|
|
203
443
|
if (status >= 400) {
|
|
@@ -207,12 +447,14 @@ var Logger = class {
|
|
|
207
447
|
}
|
|
208
448
|
}
|
|
209
449
|
};
|
|
450
|
+
|
|
451
|
+
// src/lib/token-manager.ts
|
|
210
452
|
var AuthChangeEvent = {
|
|
211
453
|
SIGNED_IN: "signedIn",
|
|
212
454
|
SIGNED_OUT: "signedOut",
|
|
213
455
|
TOKEN_REFRESHED: "tokenRefreshed"
|
|
214
456
|
};
|
|
215
|
-
var CSRF_TOKEN_COOKIE = "
|
|
457
|
+
var CSRF_TOKEN_COOKIE = "result_csrf_token";
|
|
216
458
|
function getCsrfToken() {
|
|
217
459
|
if (typeof document === "undefined") {
|
|
218
460
|
return null;
|
|
@@ -239,11 +481,10 @@ function clearCsrfToken() {
|
|
|
239
481
|
document.cookie = `${CSRF_TOKEN_COOKIE}=; path=/; max-age=0; SameSite=Lax${secure}`;
|
|
240
482
|
}
|
|
241
483
|
var TokenManager = class {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
484
|
+
// In-memory storage
|
|
485
|
+
accessToken = null;
|
|
486
|
+
user = null;
|
|
487
|
+
authStateChangeCallbacks = /* @__PURE__ */ new Map();
|
|
247
488
|
/**
|
|
248
489
|
* Save session in memory
|
|
249
490
|
*/
|
|
@@ -321,41 +562,8 @@ var TokenManager = class {
|
|
|
321
562
|
}
|
|
322
563
|
}
|
|
323
564
|
};
|
|
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
|
-
}
|
|
565
|
+
|
|
566
|
+
// src/lib/http-client.ts
|
|
359
567
|
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([500, 502, 503, 504]);
|
|
360
568
|
var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
361
569
|
var REFRESHABLE_AUTH_ERROR_CODES = /* @__PURE__ */ new Set(["AUTH_UNAUTHORIZED", "PGRST301"]);
|
|
@@ -385,7 +593,7 @@ async function parseResponse(response) {
|
|
|
385
593
|
data = await response.text();
|
|
386
594
|
}
|
|
387
595
|
} catch (parseErr) {
|
|
388
|
-
throw new
|
|
596
|
+
throw new ResultError(
|
|
389
597
|
`Failed to parse response body: ${parseErr?.message || "Unknown error"}`,
|
|
390
598
|
response.status,
|
|
391
599
|
response.ok ? "PARSE_ERROR" : "REQUEST_FAILED"
|
|
@@ -393,8 +601,8 @@ async function parseResponse(response) {
|
|
|
393
601
|
}
|
|
394
602
|
if (!response.ok) {
|
|
395
603
|
if (data && typeof data === "object" && "error" in data) {
|
|
396
|
-
data.statusCode
|
|
397
|
-
const error =
|
|
604
|
+
data.statusCode ??= data.status ?? response.status;
|
|
605
|
+
const error = ResultError.fromApiError(data);
|
|
398
606
|
Object.keys(data).forEach((key) => {
|
|
399
607
|
if (key !== "error" && key !== "message" && key !== "statusCode") {
|
|
400
608
|
error[key] = data[key];
|
|
@@ -402,7 +610,7 @@ async function parseResponse(response) {
|
|
|
402
610
|
});
|
|
403
611
|
throw error;
|
|
404
612
|
}
|
|
405
|
-
throw new
|
|
613
|
+
throw new ResultError(
|
|
406
614
|
`Request failed: ${response.statusText}`,
|
|
407
615
|
response.status,
|
|
408
616
|
"REQUEST_FAILED"
|
|
@@ -411,6 +619,20 @@ async function parseResponse(response) {
|
|
|
411
619
|
return data;
|
|
412
620
|
}
|
|
413
621
|
var HttpClient = class {
|
|
622
|
+
baseUrl;
|
|
623
|
+
fetch;
|
|
624
|
+
config;
|
|
625
|
+
defaultHeaders;
|
|
626
|
+
anonKey;
|
|
627
|
+
userToken = null;
|
|
628
|
+
logger;
|
|
629
|
+
isRefreshing = false;
|
|
630
|
+
refreshPromise = null;
|
|
631
|
+
tokenManager;
|
|
632
|
+
refreshToken = null;
|
|
633
|
+
timeout;
|
|
634
|
+
retryCount;
|
|
635
|
+
retryDelay;
|
|
414
636
|
/**
|
|
415
637
|
* Creates a new HttpClient instance.
|
|
416
638
|
* @param config - SDK configuration including baseUrl, timeout, retry settings, and fetch implementation.
|
|
@@ -418,10 +640,6 @@ var HttpClient = class {
|
|
|
418
640
|
* @param logger - Optional logger instance for request/response debugging.
|
|
419
641
|
*/
|
|
420
642
|
constructor(config, tokenManager, logger) {
|
|
421
|
-
this.userToken = null;
|
|
422
|
-
this.isRefreshing = false;
|
|
423
|
-
this.refreshPromise = null;
|
|
424
|
-
this.refreshToken = null;
|
|
425
643
|
this.config = config;
|
|
426
644
|
this.baseUrl = config.baseUrl || "http://localhost:7130";
|
|
427
645
|
this.fetch = config.fetch || (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
|
|
@@ -469,7 +687,7 @@ var HttpClient = class {
|
|
|
469
687
|
* @returns Delay in milliseconds.
|
|
470
688
|
*/
|
|
471
689
|
computeRetryDelay(attempt) {
|
|
472
|
-
const base = this.retryDelay *
|
|
690
|
+
const base = this.retryDelay * 2 ** (attempt - 1);
|
|
473
691
|
const jitter = base * (0.85 + Math.random() * 0.3);
|
|
474
692
|
return Math.round(jitter);
|
|
475
693
|
}
|
|
@@ -477,19 +695,29 @@ var HttpClient = class {
|
|
|
477
695
|
return statusCode === 401 && REFRESHABLE_AUTH_ERROR_CODES.has(errorCode ?? "") && !this.config.isServerMode && !this.config.accessToken && !this.config.edgeFunctionToken && !options.skipAuthRefresh && authToken !== null;
|
|
478
696
|
}
|
|
479
697
|
async fetchWithRetry(args) {
|
|
480
|
-
const {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
698
|
+
const {
|
|
699
|
+
method,
|
|
700
|
+
url,
|
|
701
|
+
headers,
|
|
702
|
+
body,
|
|
703
|
+
fetchOptions,
|
|
704
|
+
callerSignal,
|
|
705
|
+
maxAttempts
|
|
706
|
+
} = args;
|
|
707
|
+
let lastError;
|
|
708
|
+
for (let attempt = 0; attempt <= maxAttempts; attempt++) {
|
|
709
|
+
if (attempt > 0) {
|
|
710
|
+
const delay = this.computeRetryDelay(attempt);
|
|
711
|
+
this.logger.warn(
|
|
712
|
+
`Retry ${attempt}/${maxAttempts} for ${method} ${url} in ${delay}ms`
|
|
713
|
+
);
|
|
486
714
|
if (callerSignal?.aborted) {
|
|
487
715
|
throw callerSignal.reason;
|
|
488
716
|
}
|
|
489
717
|
await new Promise((resolve, reject) => {
|
|
490
718
|
const onAbort = () => {
|
|
491
719
|
clearTimeout(timer2);
|
|
492
|
-
reject(callerSignal
|
|
720
|
+
reject(callerSignal?.reason);
|
|
493
721
|
};
|
|
494
722
|
const timer2 = setTimeout(() => {
|
|
495
723
|
if (callerSignal) {
|
|
@@ -507,13 +735,13 @@ var HttpClient = class {
|
|
|
507
735
|
if (this.timeout > 0 || callerSignal) {
|
|
508
736
|
controller = new AbortController();
|
|
509
737
|
if (this.timeout > 0) {
|
|
510
|
-
timer = setTimeout(() => controller
|
|
738
|
+
timer = setTimeout(() => controller?.abort(), this.timeout);
|
|
511
739
|
}
|
|
512
740
|
if (callerSignal) {
|
|
513
741
|
if (callerSignal.aborted) {
|
|
514
742
|
controller.abort(callerSignal.reason);
|
|
515
743
|
} else {
|
|
516
|
-
const onCallerAbort = () => controller
|
|
744
|
+
const onCallerAbort = () => controller?.abort(callerSignal.reason);
|
|
517
745
|
callerSignal.addEventListener("abort", onCallerAbort, {
|
|
518
746
|
once: true
|
|
519
747
|
});
|
|
@@ -540,7 +768,7 @@ var HttpClient = class {
|
|
|
540
768
|
clearTimeout(timer);
|
|
541
769
|
}
|
|
542
770
|
await response.body?.cancel();
|
|
543
|
-
lastError = new
|
|
771
|
+
lastError = new ResultError(
|
|
544
772
|
`Server error: ${response.status} ${response.statusText}`,
|
|
545
773
|
response.status,
|
|
546
774
|
"SERVER_ERROR"
|
|
@@ -556,8 +784,8 @@ var HttpClient = class {
|
|
|
556
784
|
clearTimeout(timer);
|
|
557
785
|
}
|
|
558
786
|
if (err?.name === "AbortError") {
|
|
559
|
-
if (controller
|
|
560
|
-
throw new
|
|
787
|
+
if (controller?.signal.aborted && this.timeout > 0 && !callerSignal?.aborted) {
|
|
788
|
+
throw new ResultError(
|
|
561
789
|
`Request timed out after ${this.timeout}ms`,
|
|
562
790
|
408,
|
|
563
791
|
"REQUEST_TIMEOUT"
|
|
@@ -569,14 +797,18 @@ var HttpClient = class {
|
|
|
569
797
|
lastError = err;
|
|
570
798
|
continue;
|
|
571
799
|
}
|
|
572
|
-
throw new
|
|
800
|
+
throw new ResultError(
|
|
573
801
|
`Network request failed: ${err?.message || "Unknown error"}`,
|
|
574
802
|
0,
|
|
575
803
|
"NETWORK_ERROR"
|
|
576
804
|
);
|
|
577
805
|
}
|
|
578
806
|
}
|
|
579
|
-
throw lastError || new
|
|
807
|
+
throw lastError || new ResultError(
|
|
808
|
+
"Request failed after all retry attempts",
|
|
809
|
+
0,
|
|
810
|
+
"NETWORK_ERROR"
|
|
811
|
+
);
|
|
580
812
|
}
|
|
581
813
|
/**
|
|
582
814
|
* Performs an HTTP request with automatic retry and timeout handling.
|
|
@@ -586,7 +818,7 @@ var HttpClient = class {
|
|
|
586
818
|
* @param path - API path relative to the base URL.
|
|
587
819
|
* @param options - Optional request configuration including headers, body, and query params.
|
|
588
820
|
* @returns Parsed response data.
|
|
589
|
-
* @throws {
|
|
821
|
+
* @throws {ResultError} On timeout, network failure, or HTTP error responses.
|
|
590
822
|
*/
|
|
591
823
|
async handleRequest(method, path, options = {}, tokenOverride) {
|
|
592
824
|
const {
|
|
@@ -606,14 +838,14 @@ var HttpClient = class {
|
|
|
606
838
|
};
|
|
607
839
|
const authToken = tokenOverride ?? this.userToken ?? this.anonKey;
|
|
608
840
|
if (authToken) {
|
|
609
|
-
requestHeaders
|
|
841
|
+
requestHeaders.Authorization = `Bearer ${authToken}`;
|
|
610
842
|
}
|
|
611
843
|
const processedBody = serializeBody(method, body, requestHeaders);
|
|
612
844
|
const setRequestHeader = (key, value) => {
|
|
613
845
|
if (key.toLowerCase() === "authorization") {
|
|
614
|
-
delete requestHeaders
|
|
615
|
-
delete requestHeaders
|
|
616
|
-
requestHeaders
|
|
846
|
+
delete requestHeaders.Authorization;
|
|
847
|
+
delete requestHeaders.authorization;
|
|
848
|
+
requestHeaders.Authorization = value;
|
|
617
849
|
return;
|
|
618
850
|
}
|
|
619
851
|
requestHeaders[key] = value;
|
|
@@ -645,7 +877,7 @@ var HttpClient = class {
|
|
|
645
877
|
try {
|
|
646
878
|
data = await parseResponse(response);
|
|
647
879
|
} catch (err) {
|
|
648
|
-
if (err instanceof
|
|
880
|
+
if (err instanceof ResultError) {
|
|
649
881
|
this.logger.logResponse(
|
|
650
882
|
method,
|
|
651
883
|
url,
|
|
@@ -656,15 +888,31 @@ var HttpClient = class {
|
|
|
656
888
|
}
|
|
657
889
|
throw err;
|
|
658
890
|
}
|
|
659
|
-
this.logger.logResponse(
|
|
891
|
+
this.logger.logResponse(
|
|
892
|
+
method,
|
|
893
|
+
url,
|
|
894
|
+
response.status,
|
|
895
|
+
Date.now() - startTime,
|
|
896
|
+
data
|
|
897
|
+
);
|
|
660
898
|
return data;
|
|
661
899
|
}
|
|
662
900
|
async request(method, path, options = {}) {
|
|
663
901
|
const tokenUsed = this.userToken;
|
|
664
902
|
try {
|
|
665
|
-
return await this.handleRequest(
|
|
903
|
+
return await this.handleRequest(
|
|
904
|
+
method,
|
|
905
|
+
path,
|
|
906
|
+
{ ...options },
|
|
907
|
+
tokenUsed
|
|
908
|
+
);
|
|
666
909
|
} catch (error) {
|
|
667
|
-
if (!(error instanceof
|
|
910
|
+
if (!(error instanceof ResultError) || !this.shouldRefreshAccessToken(
|
|
911
|
+
error.statusCode,
|
|
912
|
+
error.error,
|
|
913
|
+
tokenUsed,
|
|
914
|
+
options
|
|
915
|
+
)) {
|
|
668
916
|
throw error;
|
|
669
917
|
}
|
|
670
918
|
if (tokenUsed !== this.userToken) {
|
|
@@ -684,7 +932,7 @@ var HttpClient = class {
|
|
|
684
932
|
try {
|
|
685
933
|
await this.refreshAndSaveSession();
|
|
686
934
|
} catch (error2) {
|
|
687
|
-
if (error2 instanceof
|
|
935
|
+
if (error2 instanceof ResultError && (error2.statusCode === 401 || error2.statusCode === 403)) {
|
|
688
936
|
this.clearAuthSession();
|
|
689
937
|
}
|
|
690
938
|
throw error2;
|
|
@@ -749,7 +997,12 @@ var HttpClient = class {
|
|
|
749
997
|
callerSignal,
|
|
750
998
|
maxAttempts
|
|
751
999
|
});
|
|
752
|
-
this.logger.logResponse(
|
|
1000
|
+
this.logger.logResponse(
|
|
1001
|
+
method,
|
|
1002
|
+
url,
|
|
1003
|
+
response.status,
|
|
1004
|
+
Date.now() - startTime
|
|
1005
|
+
);
|
|
753
1006
|
let errorCode = null;
|
|
754
1007
|
if (response.status === 401) {
|
|
755
1008
|
try {
|
|
@@ -763,7 +1016,12 @@ var HttpClient = class {
|
|
|
763
1016
|
} catch {
|
|
764
1017
|
}
|
|
765
1018
|
}
|
|
766
|
-
if (!this.shouldRefreshAccessToken(
|
|
1019
|
+
if (!this.shouldRefreshAccessToken(
|
|
1020
|
+
response.status,
|
|
1021
|
+
errorCode,
|
|
1022
|
+
tokenUsed,
|
|
1023
|
+
options
|
|
1024
|
+
)) {
|
|
767
1025
|
return response;
|
|
768
1026
|
}
|
|
769
1027
|
if (tokenUsed !== this.userToken) {
|
|
@@ -782,7 +1040,7 @@ var HttpClient = class {
|
|
|
782
1040
|
try {
|
|
783
1041
|
newTokenData = await this.refreshAndSaveSession();
|
|
784
1042
|
} catch (error) {
|
|
785
|
-
if (error instanceof
|
|
1043
|
+
if (error instanceof ResultError && (error.statusCode === 401 || error.statusCode === 403)) {
|
|
786
1044
|
this.clearAuthSession();
|
|
787
1045
|
}
|
|
788
1046
|
throw error;
|
|
@@ -827,7 +1085,7 @@ var HttpClient = class {
|
|
|
827
1085
|
const headers = { ...this.defaultHeaders };
|
|
828
1086
|
const authToken = this.userToken || this.anonKey;
|
|
829
1087
|
if (authToken) {
|
|
830
|
-
headers
|
|
1088
|
+
headers.Authorization = `Bearer ${authToken}`;
|
|
831
1089
|
}
|
|
832
1090
|
return headers;
|
|
833
1091
|
}
|
|
@@ -871,7 +1129,7 @@ var HttpClient = class {
|
|
|
871
1129
|
const refreshed = await this.refreshAndSaveSession();
|
|
872
1130
|
return refreshed.accessToken;
|
|
873
1131
|
} catch (error) {
|
|
874
|
-
if (error instanceof
|
|
1132
|
+
if (error instanceof ResultError && (error.statusCode === 401 || error.statusCode === 403) && this.userToken === accessToken) {
|
|
875
1133
|
this.clearAuthSession();
|
|
876
1134
|
}
|
|
877
1135
|
throw error;
|
|
@@ -880,7 +1138,10 @@ var HttpClient = class {
|
|
|
880
1138
|
async refreshAndSaveSession() {
|
|
881
1139
|
const newTokenData = await this.refreshAccessToken();
|
|
882
1140
|
this.setAuthToken(newTokenData.accessToken);
|
|
883
|
-
this.tokenManager.saveSession(
|
|
1141
|
+
this.tokenManager.saveSession(
|
|
1142
|
+
newTokenData,
|
|
1143
|
+
AuthChangeEvent.TOKEN_REFRESHED
|
|
1144
|
+
);
|
|
884
1145
|
if (newTokenData.csrfToken) {
|
|
885
1146
|
setCsrfToken(newTokenData.csrfToken);
|
|
886
1147
|
}
|
|
@@ -896,1328 +1157,1404 @@ var HttpClient = class {
|
|
|
896
1157
|
clearCsrfToken();
|
|
897
1158
|
}
|
|
898
1159
|
};
|
|
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 };
|
|
1160
|
+
|
|
1161
|
+
// src/modules/ai.ts
|
|
1162
|
+
var AI = class {
|
|
1163
|
+
chat;
|
|
1164
|
+
images;
|
|
1165
|
+
embeddings;
|
|
1166
|
+
constructor(http) {
|
|
1167
|
+
this.chat = new Chat(http);
|
|
1168
|
+
this.images = new Images(http);
|
|
1169
|
+
this.embeddings = new Embeddings(http);
|
|
946
1170
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
"UNEXPECTED_ERROR"
|
|
953
|
-
)
|
|
954
|
-
};
|
|
955
|
-
}
|
|
956
|
-
function cleanUrlParams(...params) {
|
|
957
|
-
if (typeof window === "undefined") {
|
|
958
|
-
return;
|
|
1171
|
+
};
|
|
1172
|
+
var Chat = class {
|
|
1173
|
+
completions;
|
|
1174
|
+
constructor(http) {
|
|
1175
|
+
this.completions = new ChatCompletions(http);
|
|
959
1176
|
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
}
|
|
964
|
-
var Auth = class {
|
|
965
|
-
constructor(http, tokenManager, options = {}) {
|
|
1177
|
+
};
|
|
1178
|
+
var ChatCompletions = class {
|
|
1179
|
+
constructor(http) {
|
|
966
1180
|
this.http = http;
|
|
967
|
-
this.tokenManager = tokenManager;
|
|
968
|
-
this.options = options;
|
|
969
|
-
this.authCallbackHandled = options.detectOAuthCallback === false ? Promise.resolve() : this.detectAuthCallback();
|
|
970
|
-
}
|
|
971
|
-
isServerMode() {
|
|
972
|
-
return !!this.options.isServerMode;
|
|
973
|
-
}
|
|
974
|
-
/** Subscribe to SDK authentication state changes. */
|
|
975
|
-
onAuthStateChange(callback) {
|
|
976
|
-
return this.tokenManager.onAuthStateChange(callback);
|
|
977
1181
|
}
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1182
|
+
http;
|
|
1183
|
+
async create(params) {
|
|
1184
|
+
const backendParams = {
|
|
1185
|
+
model: params.model,
|
|
1186
|
+
messages: params.messages,
|
|
1187
|
+
temperature: params.temperature,
|
|
1188
|
+
maxTokens: params.maxTokens,
|
|
1189
|
+
topP: params.topP,
|
|
1190
|
+
stream: params.stream,
|
|
1191
|
+
// New plugin options
|
|
1192
|
+
webSearch: params.webSearch,
|
|
1193
|
+
fileParser: params.fileParser,
|
|
1194
|
+
thinking: params.thinking,
|
|
1195
|
+
// Tool calling options
|
|
1196
|
+
tools: params.tools,
|
|
1197
|
+
toolChoice: params.toolChoice,
|
|
1198
|
+
parallelToolCalls: params.parallelToolCalls
|
|
989
1199
|
};
|
|
990
|
-
if (
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
}
|
|
1000
|
-
// ============================================================================
|
|
1001
|
-
// OAuth Callback Detection (runs on initialization)
|
|
1002
|
-
// ============================================================================
|
|
1003
|
-
/**
|
|
1004
|
-
* Detect and handle OAuth callback parameters in URL
|
|
1005
|
-
* Supports PKCE flow (insforge_code)
|
|
1006
|
-
*/
|
|
1007
|
-
async detectAuthCallback() {
|
|
1008
|
-
if (this.isServerMode() || typeof window === "undefined") {
|
|
1009
|
-
return;
|
|
1010
|
-
}
|
|
1011
|
-
try {
|
|
1012
|
-
const params = new URLSearchParams(window.location.search);
|
|
1013
|
-
const error = params.get("error");
|
|
1014
|
-
if (error) {
|
|
1015
|
-
cleanUrlParams("error");
|
|
1016
|
-
console.debug("OAuth callback error:", error);
|
|
1017
|
-
return;
|
|
1018
|
-
}
|
|
1019
|
-
const code = params.get("insforge_code");
|
|
1020
|
-
if (code) {
|
|
1021
|
-
cleanUrlParams("insforge_code");
|
|
1022
|
-
const { error: exchangeError } = await this.exchangeOAuthCode(code);
|
|
1023
|
-
if (exchangeError) {
|
|
1024
|
-
console.debug("OAuth code exchange failed:", exchangeError.message);
|
|
1200
|
+
if (params.stream) {
|
|
1201
|
+
const headers = this.http.getHeaders();
|
|
1202
|
+
headers["Content-Type"] = "application/json";
|
|
1203
|
+
const response2 = await this.http.fetch(
|
|
1204
|
+
`${this.http.baseUrl}/api/ai/chat/completion`,
|
|
1205
|
+
{
|
|
1206
|
+
method: "POST",
|
|
1207
|
+
headers,
|
|
1208
|
+
body: JSON.stringify(backendParams)
|
|
1025
1209
|
}
|
|
1026
|
-
return;
|
|
1027
|
-
}
|
|
1028
|
-
} catch (error) {
|
|
1029
|
-
console.debug("OAuth callback detection skipped:", error);
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
// ============================================================================
|
|
1033
|
-
// Sign Up / Sign In / Sign Out
|
|
1034
|
-
// ============================================================================
|
|
1035
|
-
async signUp(request) {
|
|
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
1210
|
);
|
|
1042
|
-
if (
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
if (response.refreshToken) {
|
|
1046
|
-
this.http.setRefreshToken(response.refreshToken);
|
|
1211
|
+
if (!response2.ok) {
|
|
1212
|
+
const error = await response2.json();
|
|
1213
|
+
throw new Error(error.error || "Stream request failed");
|
|
1047
1214
|
}
|
|
1048
|
-
return
|
|
1049
|
-
} catch (error) {
|
|
1050
|
-
return wrapError(error, "An unexpected error occurred during sign up");
|
|
1215
|
+
return this.parseSSEStream(response2, params.model);
|
|
1051
1216
|
}
|
|
1217
|
+
const response = await this.http.post(
|
|
1218
|
+
"/api/ai/chat/completion",
|
|
1219
|
+
backendParams
|
|
1220
|
+
);
|
|
1221
|
+
const content = response.text || "";
|
|
1222
|
+
return {
|
|
1223
|
+
id: `chatcmpl-${Date.now()}`,
|
|
1224
|
+
object: "chat.completion",
|
|
1225
|
+
created: Math.floor(Date.now() / 1e3),
|
|
1226
|
+
model: response.metadata?.model,
|
|
1227
|
+
choices: [
|
|
1228
|
+
{
|
|
1229
|
+
index: 0,
|
|
1230
|
+
message: {
|
|
1231
|
+
role: "assistant",
|
|
1232
|
+
content,
|
|
1233
|
+
// Include tool_calls if present (from tool calling)
|
|
1234
|
+
...response.tool_calls?.length && {
|
|
1235
|
+
tool_calls: response.tool_calls
|
|
1236
|
+
},
|
|
1237
|
+
// Include annotations if present (from web search or file parsing)
|
|
1238
|
+
...response.annotations?.length && {
|
|
1239
|
+
annotations: response.annotations
|
|
1240
|
+
}
|
|
1241
|
+
},
|
|
1242
|
+
finish_reason: response.tool_calls?.length ? "tool_calls" : "stop"
|
|
1243
|
+
}
|
|
1244
|
+
],
|
|
1245
|
+
usage: {
|
|
1246
|
+
prompt_tokens: response.metadata?.usage?.promptTokens || 0,
|
|
1247
|
+
completion_tokens: response.metadata?.usage?.completionTokens || 0,
|
|
1248
|
+
total_tokens: response.metadata?.usage?.totalTokens || 0
|
|
1249
|
+
}
|
|
1250
|
+
};
|
|
1052
1251
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1252
|
+
/**
|
|
1253
|
+
* Parse SSE stream into async iterable of OpenAI-like chunks
|
|
1254
|
+
*/
|
|
1255
|
+
async *parseSSEStream(response, model) {
|
|
1256
|
+
const reader = response.body?.getReader();
|
|
1257
|
+
if (!reader) {
|
|
1258
|
+
throw new ResultError(
|
|
1259
|
+
"Streaming response has no body",
|
|
1260
|
+
500,
|
|
1261
|
+
"AI_UPSTREAM_UNAVAILABLE"
|
|
1059
1262
|
);
|
|
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");
|
|
1067
1263
|
}
|
|
1068
|
-
|
|
1069
|
-
|
|
1264
|
+
const decoder = new TextDecoder();
|
|
1265
|
+
let buffer = "";
|
|
1070
1266
|
try {
|
|
1071
|
-
|
|
1072
|
-
const
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1267
|
+
while (true) {
|
|
1268
|
+
const { done, value } = await reader.read();
|
|
1269
|
+
if (done) {
|
|
1270
|
+
break;
|
|
1271
|
+
}
|
|
1272
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1273
|
+
const lines = buffer.split("\n");
|
|
1274
|
+
buffer = lines.pop() || "";
|
|
1275
|
+
for (const line of lines) {
|
|
1276
|
+
if (line.startsWith("data: ")) {
|
|
1277
|
+
const dataStr = line.slice(6).trim();
|
|
1278
|
+
if (dataStr) {
|
|
1279
|
+
try {
|
|
1280
|
+
const data = JSON.parse(dataStr);
|
|
1281
|
+
if (data.chunk || data.content) {
|
|
1282
|
+
yield {
|
|
1283
|
+
id: `chatcmpl-${Date.now()}`,
|
|
1284
|
+
object: "chat.completion.chunk",
|
|
1285
|
+
created: Math.floor(Date.now() / 1e3),
|
|
1286
|
+
model,
|
|
1287
|
+
choices: [
|
|
1288
|
+
{
|
|
1289
|
+
index: 0,
|
|
1290
|
+
delta: {
|
|
1291
|
+
content: data.chunk || data.content
|
|
1292
|
+
},
|
|
1293
|
+
finish_reason: null
|
|
1294
|
+
}
|
|
1295
|
+
]
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
if (data.tool_calls?.length) {
|
|
1299
|
+
yield {
|
|
1300
|
+
id: `chatcmpl-${Date.now()}`,
|
|
1301
|
+
object: "chat.completion.chunk",
|
|
1302
|
+
created: Math.floor(Date.now() / 1e3),
|
|
1303
|
+
model,
|
|
1304
|
+
choices: [
|
|
1305
|
+
{
|
|
1306
|
+
index: 0,
|
|
1307
|
+
delta: {
|
|
1308
|
+
tool_calls: data.tool_calls
|
|
1309
|
+
},
|
|
1310
|
+
finish_reason: "tool_calls"
|
|
1311
|
+
}
|
|
1312
|
+
]
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
if (data.done) {
|
|
1316
|
+
reader.releaseLock();
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
} catch {
|
|
1320
|
+
console.warn("Failed to parse SSE data:", dataStr);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1081
1323
|
}
|
|
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 };
|
|
1092
|
-
} catch {
|
|
1093
|
-
return {
|
|
1094
|
-
error: new InsForgeError("Failed to sign out", 500, "SIGNOUT_ERROR")
|
|
1095
|
-
};
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
async signInWithOAuth(providerOrOptions, options) {
|
|
1099
|
-
try {
|
|
1100
|
-
let signInOptions;
|
|
1101
|
-
if (typeof providerOrOptions === "object") {
|
|
1102
|
-
signInOptions = providerOrOptions;
|
|
1103
|
-
} else if (options) {
|
|
1104
|
-
signInOptions = { provider: providerOrOptions, ...options };
|
|
1105
|
-
} else {
|
|
1106
|
-
return {
|
|
1107
|
-
data: {},
|
|
1108
|
-
error: new InsForgeError(
|
|
1109
|
-
"OAuth sign-in options are required",
|
|
1110
|
-
400,
|
|
1111
|
-
ERROR_CODES.INVALID_INPUT
|
|
1112
|
-
)
|
|
1113
|
-
};
|
|
1114
|
-
}
|
|
1115
|
-
if (!signInOptions || !signInOptions.redirectTo) {
|
|
1116
|
-
return {
|
|
1117
|
-
data: {},
|
|
1118
|
-
error: new InsForgeError("Redirect URI is required", 400, ERROR_CODES.INVALID_INPUT)
|
|
1119
|
-
};
|
|
1120
|
-
}
|
|
1121
|
-
const { provider } = signInOptions;
|
|
1122
|
-
const providerKey = encodeURIComponent(provider.toLowerCase());
|
|
1123
|
-
const codeVerifier = await generateCodeVerifier();
|
|
1124
|
-
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
1125
|
-
storePkceVerifier(codeVerifier);
|
|
1126
|
-
const params = {
|
|
1127
|
-
...signInOptions.additionalParams ?? {},
|
|
1128
|
-
redirect_uri: signInOptions.redirectTo,
|
|
1129
|
-
code_challenge: codeChallenge
|
|
1130
|
-
};
|
|
1131
|
-
const isBuiltInProvider = oAuthProvidersSchema.options.includes(
|
|
1132
|
-
providerKey
|
|
1133
|
-
);
|
|
1134
|
-
const oauthPath = isBuiltInProvider ? `/api/auth/oauth/${providerKey}` : `/api/auth/oauth/custom/${providerKey}`;
|
|
1135
|
-
const response = await this.http.get(oauthPath, {
|
|
1136
|
-
params,
|
|
1137
|
-
skipAuthRefresh: true
|
|
1138
|
-
});
|
|
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 };
|
|
1324
|
+
}
|
|
1150
1325
|
}
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
error: new InsForgeError(
|
|
1154
|
-
"An unexpected error occurred during OAuth initialization",
|
|
1155
|
-
500,
|
|
1156
|
-
"UNEXPECTED_ERROR"
|
|
1157
|
-
)
|
|
1158
|
-
};
|
|
1326
|
+
} finally {
|
|
1327
|
+
reader.releaseLock();
|
|
1159
1328
|
}
|
|
1160
1329
|
}
|
|
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");
|
|
1194
|
-
}
|
|
1330
|
+
};
|
|
1331
|
+
var Embeddings = class {
|
|
1332
|
+
constructor(http) {
|
|
1333
|
+
this.http = http;
|
|
1195
1334
|
}
|
|
1335
|
+
http;
|
|
1196
1336
|
/**
|
|
1197
|
-
*
|
|
1198
|
-
* Use this for native mobile apps or Google One Tap on web.
|
|
1337
|
+
* Create embeddings for text input - OpenAI-like response format
|
|
1199
1338
|
*
|
|
1200
|
-
* @
|
|
1201
|
-
*
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
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");
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
// ============================================================================
|
|
1224
|
-
// Session Management
|
|
1225
|
-
// ============================================================================
|
|
1226
|
-
/**
|
|
1227
|
-
* Refresh the current auth session.
|
|
1339
|
+
* @example
|
|
1340
|
+
* ```typescript
|
|
1341
|
+
* // Single text input
|
|
1342
|
+
* const response = await client.ai.embeddings.create({
|
|
1343
|
+
* model: 'openai/text-embedding-3-small',
|
|
1344
|
+
* input: 'Hello world'
|
|
1345
|
+
* });
|
|
1346
|
+
* console.log(response.data[0].embedding); // number[]
|
|
1228
1347
|
*
|
|
1229
|
-
*
|
|
1230
|
-
*
|
|
1348
|
+
* // Multiple text inputs
|
|
1349
|
+
* const response = await client.ai.embeddings.create({
|
|
1350
|
+
* model: 'openai/text-embedding-3-small',
|
|
1351
|
+
* input: ['Hello world', 'Goodbye world']
|
|
1352
|
+
* });
|
|
1353
|
+
* response.data.forEach((item, i) => {
|
|
1354
|
+
* console.log(`Embedding ${i}:`, item.embedding.slice(0, 5)); // First 5 dimensions
|
|
1355
|
+
* });
|
|
1231
1356
|
*
|
|
1232
|
-
*
|
|
1233
|
-
*
|
|
1357
|
+
* // With custom dimensions (if supported by model)
|
|
1358
|
+
* const response = await client.ai.embeddings.create({
|
|
1359
|
+
* model: 'openai/text-embedding-3-small',
|
|
1360
|
+
* input: 'Hello world',
|
|
1361
|
+
* dimensions: 256
|
|
1362
|
+
* });
|
|
1234
1363
|
*
|
|
1235
|
-
*
|
|
1236
|
-
*
|
|
1364
|
+
* // With base64 encoding format
|
|
1365
|
+
* const response = await client.ai.embeddings.create({
|
|
1366
|
+
* model: 'openai/text-embedding-3-small',
|
|
1367
|
+
* input: 'Hello world',
|
|
1368
|
+
* encoding_format: 'base64'
|
|
1369
|
+
* });
|
|
1370
|
+
* ```
|
|
1237
1371
|
*/
|
|
1238
|
-
async
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
this.isServerMode() ? { refresh_token: options?.refreshToken } : void 0,
|
|
1254
|
-
{
|
|
1255
|
-
headers: csrfToken ? { "X-CSRF-Token": csrfToken } : {},
|
|
1256
|
-
credentials: "include",
|
|
1257
|
-
skipAuthRefresh: true
|
|
1258
|
-
}
|
|
1259
|
-
);
|
|
1260
|
-
if (response.accessToken) {
|
|
1261
|
-
this.saveSessionFromResponse(response, AuthChangeEvent.TOKEN_REFRESHED);
|
|
1372
|
+
async create(params) {
|
|
1373
|
+
const response = await this.http.post(
|
|
1374
|
+
"/api/ai/embeddings",
|
|
1375
|
+
params
|
|
1376
|
+
);
|
|
1377
|
+
return {
|
|
1378
|
+
object: response.object,
|
|
1379
|
+
data: response.data,
|
|
1380
|
+
model: response.metadata?.model,
|
|
1381
|
+
usage: response.metadata?.usage ? {
|
|
1382
|
+
prompt_tokens: response.metadata.usage.promptTokens || 0,
|
|
1383
|
+
total_tokens: response.metadata.usage.totalTokens || 0
|
|
1384
|
+
} : {
|
|
1385
|
+
prompt_tokens: 0,
|
|
1386
|
+
total_tokens: 0
|
|
1262
1387
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
};
|
|
1391
|
+
var Images = class {
|
|
1392
|
+
constructor(http) {
|
|
1393
|
+
this.http = http;
|
|
1267
1394
|
}
|
|
1395
|
+
http;
|
|
1268
1396
|
/**
|
|
1269
|
-
*
|
|
1397
|
+
* Generate images - OpenAI-like response format
|
|
1398
|
+
*
|
|
1399
|
+
* @example
|
|
1400
|
+
* ```typescript
|
|
1401
|
+
* // Text-to-image
|
|
1402
|
+
* const response = await client.ai.images.generate({
|
|
1403
|
+
* model: 'dall-e-3',
|
|
1404
|
+
* prompt: 'A sunset over mountains',
|
|
1405
|
+
* });
|
|
1406
|
+
* console.log(response.data[0].b64_json);
|
|
1407
|
+
*
|
|
1408
|
+
* // Image-to-image (with input images)
|
|
1409
|
+
* const response = await client.ai.images.generate({
|
|
1410
|
+
* model: 'stable-diffusion-xl',
|
|
1411
|
+
* prompt: 'Transform this into a watercolor painting',
|
|
1412
|
+
* images: [
|
|
1413
|
+
* { url: 'https://example.com/input.jpg' },
|
|
1414
|
+
* // or base64-encoded Data URI:
|
|
1415
|
+
* { url: 'data:image/jpeg;base64,/9j/4AAQ...' }
|
|
1416
|
+
* ]
|
|
1417
|
+
* });
|
|
1418
|
+
* ```
|
|
1270
1419
|
*/
|
|
1271
|
-
async
|
|
1272
|
-
await this.
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
}
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
}
|
|
1294
|
-
if (refreshed?.accessToken) {
|
|
1295
|
-
return { data: { user: refreshed.user ?? null }, error: null };
|
|
1420
|
+
async generate(params) {
|
|
1421
|
+
const response = await this.http.post(
|
|
1422
|
+
"/api/ai/image/generation",
|
|
1423
|
+
params
|
|
1424
|
+
);
|
|
1425
|
+
let data = [];
|
|
1426
|
+
if (response.images && response.images.length > 0) {
|
|
1427
|
+
data = response.images.map((img) => ({
|
|
1428
|
+
b64_json: img.imageUrl.replace(/^data:image\/\w+;base64,/, ""),
|
|
1429
|
+
content: response.text
|
|
1430
|
+
}));
|
|
1431
|
+
} else if (response.text) {
|
|
1432
|
+
data = [{ content: response.text }];
|
|
1433
|
+
}
|
|
1434
|
+
return {
|
|
1435
|
+
created: Math.floor(Date.now() / 1e3),
|
|
1436
|
+
data,
|
|
1437
|
+
...response.metadata?.usage && {
|
|
1438
|
+
usage: {
|
|
1439
|
+
total_tokens: response.metadata.usage.totalTokens || 0,
|
|
1440
|
+
input_tokens: response.metadata.usage.promptTokens || 0,
|
|
1441
|
+
output_tokens: response.metadata.usage.completionTokens || 0
|
|
1296
1442
|
}
|
|
1297
1443
|
}
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
};
|
|
1447
|
+
|
|
1448
|
+
// src/modules/analytics.ts
|
|
1449
|
+
var DEFAULT_ENDPOINT = "https://beta.result.dev/api/wa";
|
|
1450
|
+
var IDLE_MS = 30 * 60 * 1e3;
|
|
1451
|
+
var Analytics = class {
|
|
1452
|
+
siteId;
|
|
1453
|
+
endpoint;
|
|
1454
|
+
options;
|
|
1455
|
+
active = false;
|
|
1456
|
+
lastUrl = null;
|
|
1457
|
+
left = false;
|
|
1458
|
+
referrer = "";
|
|
1459
|
+
// localStorage with an in-memory fallback for private-mode browsers.
|
|
1460
|
+
mem = {};
|
|
1461
|
+
constructor(options) {
|
|
1462
|
+
this.options = options ?? { siteId: "" };
|
|
1463
|
+
this.siteId = this.options.siteId ?? "";
|
|
1464
|
+
this.endpoint = this.options.endpoint || DEFAULT_ENDPOINT;
|
|
1465
|
+
if (!this.siteId || typeof window === "undefined" || navigator.webdriver) {
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
if (window.rwa) {
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
if (this.read("rwa_disable") === "1") {
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1474
|
+
const domains = (this.options.domains ?? []).map(
|
|
1475
|
+
(domain) => domain.trim().toLowerCase().replace(/^www\./, "")
|
|
1476
|
+
).filter(Boolean);
|
|
1477
|
+
const here = location.hostname.toLowerCase().replace(/^www\./, "");
|
|
1478
|
+
if (domains.length && domains.indexOf(here) === -1) {
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
this.active = true;
|
|
1482
|
+
this.referrer = document.referrer || "";
|
|
1483
|
+
window.rwa = {
|
|
1484
|
+
track: (name, props) => this.track(name, props),
|
|
1485
|
+
identify: (id) => this.identify(id)
|
|
1486
|
+
};
|
|
1487
|
+
const doc = document;
|
|
1488
|
+
if (doc.prerendering) {
|
|
1489
|
+
document.addEventListener("prerenderingchange", () => this.start(), {
|
|
1490
|
+
once: true
|
|
1491
|
+
});
|
|
1492
|
+
} else {
|
|
1493
|
+
this.start();
|
|
1311
1494
|
}
|
|
1312
1495
|
}
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1496
|
+
/** Send a custom event. No-op outside the browser or when inactive. */
|
|
1497
|
+
track(name, props) {
|
|
1498
|
+
if (!this.active || typeof name !== "string" || !name) {
|
|
1499
|
+
return;
|
|
1500
|
+
}
|
|
1501
|
+
const event = {
|
|
1502
|
+
e: "ev",
|
|
1503
|
+
n: name.slice(0, 64),
|
|
1504
|
+
dt: document.title
|
|
1505
|
+
};
|
|
1506
|
+
if (props && typeof props === "object") {
|
|
1507
|
+
event.p = props;
|
|
1322
1508
|
}
|
|
1509
|
+
this.post(event);
|
|
1323
1510
|
}
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
});
|
|
1329
|
-
const currentUser = this.tokenManager.getUser();
|
|
1330
|
-
if (!this.isServerMode() && currentUser && response.profile !== void 0) {
|
|
1331
|
-
this.tokenManager.setUser({
|
|
1332
|
-
...currentUser,
|
|
1333
|
-
profile: response.profile
|
|
1334
|
-
});
|
|
1335
|
-
}
|
|
1336
|
-
return { data: response, error: null };
|
|
1337
|
-
} catch (error) {
|
|
1338
|
-
return wrapError(error, "An unexpected error occurred while updating user profile");
|
|
1511
|
+
/** Attach your own user id to subsequent events. */
|
|
1512
|
+
identify(id) {
|
|
1513
|
+
if (!this.active || typeof id !== "string" || !id) {
|
|
1514
|
+
return;
|
|
1339
1515
|
}
|
|
1516
|
+
this.write("rwa_uid", id.slice(0, 64));
|
|
1340
1517
|
}
|
|
1341
|
-
//
|
|
1342
|
-
|
|
1343
|
-
// ============================================================================
|
|
1344
|
-
async resendVerificationEmail(request) {
|
|
1518
|
+
// -- internals -------------------------------------------------------------
|
|
1519
|
+
read(key) {
|
|
1345
1520
|
try {
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
return { data: response, error: null };
|
|
1350
|
-
} catch (error) {
|
|
1351
|
-
return wrapError(error, "An unexpected error occurred while sending verification email");
|
|
1521
|
+
return localStorage.getItem(key) || this.mem[key] || "";
|
|
1522
|
+
} catch {
|
|
1523
|
+
return this.mem[key] || "";
|
|
1352
1524
|
}
|
|
1353
1525
|
}
|
|
1354
|
-
|
|
1526
|
+
write(key, value) {
|
|
1527
|
+
this.mem[key] = value;
|
|
1355
1528
|
try {
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
request,
|
|
1359
|
-
{ credentials: "include", skipAuthRefresh: true }
|
|
1360
|
-
);
|
|
1361
|
-
this.saveSessionFromResponse(response);
|
|
1362
|
-
if (response.refreshToken) {
|
|
1363
|
-
this.http.setRefreshToken(response.refreshToken);
|
|
1364
|
-
}
|
|
1365
|
-
return { data: response, error: null };
|
|
1366
|
-
} catch (error) {
|
|
1367
|
-
return wrapError(error, "An unexpected error occurred while verifying email");
|
|
1529
|
+
localStorage.setItem(key, value);
|
|
1530
|
+
} catch {
|
|
1368
1531
|
}
|
|
1369
1532
|
}
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1533
|
+
visitor() {
|
|
1534
|
+
let visitor = this.read("rwa_id");
|
|
1535
|
+
if (!visitor) {
|
|
1536
|
+
visitor = uuid4();
|
|
1537
|
+
this.write("rwa_id", visitor);
|
|
1538
|
+
}
|
|
1539
|
+
return visitor;
|
|
1540
|
+
}
|
|
1541
|
+
session() {
|
|
1542
|
+
const parts = this.read("rwa_s").split(".");
|
|
1543
|
+
let id = parts[0];
|
|
1544
|
+
const last = Number(parts[1]) || 0;
|
|
1545
|
+
const now = Date.now();
|
|
1546
|
+
if (!id || now - last > IDLE_MS) {
|
|
1547
|
+
id = uuid7();
|
|
1548
|
+
}
|
|
1549
|
+
this.write("rwa_s", `${id}.${now}`);
|
|
1550
|
+
return id;
|
|
1551
|
+
}
|
|
1552
|
+
pageUrl() {
|
|
1553
|
+
let url = location.href;
|
|
1554
|
+
if (this.options.excludeHash) {
|
|
1555
|
+
url = url.split("#")[0];
|
|
1556
|
+
}
|
|
1557
|
+
if (this.options.excludeSearch) {
|
|
1558
|
+
const hash = this.options.excludeHash ? "" : url.split("#")[1] || "";
|
|
1559
|
+
url = url.split("#")[0].split("?")[0] + (hash ? `#${hash}` : "");
|
|
1560
|
+
}
|
|
1561
|
+
return url.slice(0, 4096);
|
|
1562
|
+
}
|
|
1563
|
+
post(event) {
|
|
1564
|
+
event.u = event.u || this.pageUrl();
|
|
1565
|
+
event.d = this.visitor();
|
|
1566
|
+
event.s = this.session();
|
|
1567
|
+
event.t = Date.now();
|
|
1568
|
+
event.w = innerWidth;
|
|
1569
|
+
event.h = innerHeight;
|
|
1570
|
+
event.sw = screen.width;
|
|
1571
|
+
event.sh = screen.height;
|
|
1572
|
+
const uid = this.read("rwa_uid");
|
|
1573
|
+
if (uid) {
|
|
1574
|
+
event.uid = uid;
|
|
1575
|
+
}
|
|
1576
|
+
const body = JSON.stringify({ site: this.siteId, events: [event] });
|
|
1577
|
+
if (!navigator.sendBeacon?.(this.endpoint, body)) {
|
|
1578
|
+
fetch(this.endpoint, {
|
|
1579
|
+
method: "POST",
|
|
1580
|
+
body,
|
|
1581
|
+
keepalive: true,
|
|
1582
|
+
mode: "no-cors"
|
|
1583
|
+
}).catch(() => {
|
|
1377
1584
|
});
|
|
1378
|
-
return { data: response, error: null };
|
|
1379
|
-
} catch (error) {
|
|
1380
|
-
return wrapError(error, "An unexpected error occurred while sending password reset email");
|
|
1381
1585
|
}
|
|
1382
1586
|
}
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
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");
|
|
1587
|
+
pageview() {
|
|
1588
|
+
const url = this.pageUrl();
|
|
1589
|
+
if (url === this.lastUrl) {
|
|
1590
|
+
return;
|
|
1393
1591
|
}
|
|
1592
|
+
const from = this.lastUrl ? this.lastUrl : this.referrer;
|
|
1593
|
+
this.lastUrl = url;
|
|
1594
|
+
this.left = false;
|
|
1595
|
+
this.post({ e: "pv", u: url, r: from, dt: document.title });
|
|
1394
1596
|
}
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
"/api/auth/email/reset-password",
|
|
1399
|
-
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");
|
|
1597
|
+
pageleave() {
|
|
1598
|
+
if (this.left || !this.lastUrl) {
|
|
1599
|
+
return;
|
|
1405
1600
|
}
|
|
1601
|
+
this.left = true;
|
|
1602
|
+
this.post({ e: "pl", u: this.lastUrl });
|
|
1406
1603
|
}
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
const
|
|
1413
|
-
|
|
1604
|
+
start() {
|
|
1605
|
+
const onNav = () => {
|
|
1606
|
+
setTimeout(() => this.pageview(), 0);
|
|
1607
|
+
};
|
|
1608
|
+
if (this.options.autoTrack !== false) {
|
|
1609
|
+
const push = history.pushState;
|
|
1610
|
+
const replace = history.replaceState;
|
|
1611
|
+
history.pushState = function(...args) {
|
|
1612
|
+
push.apply(this, args);
|
|
1613
|
+
onNav();
|
|
1614
|
+
};
|
|
1615
|
+
history.replaceState = function(...args) {
|
|
1616
|
+
replace.apply(this, args);
|
|
1617
|
+
onNav();
|
|
1618
|
+
};
|
|
1619
|
+
addEventListener("popstate", onNav);
|
|
1620
|
+
addEventListener("hashchange", onNav);
|
|
1621
|
+
addEventListener("pageshow", (event) => {
|
|
1622
|
+
if (event.persisted) {
|
|
1623
|
+
this.lastUrl = null;
|
|
1624
|
+
this.pageview();
|
|
1625
|
+
}
|
|
1414
1626
|
});
|
|
1415
|
-
|
|
1416
|
-
} catch (error) {
|
|
1417
|
-
return wrapError(error, "An unexpected error occurred while fetching auth configuration");
|
|
1627
|
+
this.pageview();
|
|
1418
1628
|
}
|
|
1629
|
+
addEventListener("pagehide", () => this.pageleave());
|
|
1630
|
+
document.addEventListener("visibilitychange", () => {
|
|
1631
|
+
if (document.visibilityState === "hidden") {
|
|
1632
|
+
this.pageleave();
|
|
1633
|
+
} else {
|
|
1634
|
+
this.left = false;
|
|
1635
|
+
}
|
|
1636
|
+
});
|
|
1637
|
+
document.addEventListener(
|
|
1638
|
+
"click",
|
|
1639
|
+
(clickEvent) => {
|
|
1640
|
+
const source = clickEvent.target;
|
|
1641
|
+
const target = source?.closest ? source.closest("[data-wa-event]") : null;
|
|
1642
|
+
if (!target) {
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
const name = target.getAttribute("data-wa-event");
|
|
1646
|
+
if (!name) {
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
const props = {};
|
|
1650
|
+
let any = false;
|
|
1651
|
+
for (const attr of Array.from(target.attributes)) {
|
|
1652
|
+
if (attr.name.indexOf("data-wa-event-") === 0) {
|
|
1653
|
+
props[attr.name.slice(14)] = attr.value;
|
|
1654
|
+
any = true;
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
this.track(name, any ? props : void 0);
|
|
1658
|
+
},
|
|
1659
|
+
true
|
|
1660
|
+
);
|
|
1419
1661
|
}
|
|
1420
1662
|
};
|
|
1421
|
-
function
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
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
|
-
};
|
|
1663
|
+
function bytes() {
|
|
1664
|
+
const b = new Uint8Array(16);
|
|
1665
|
+
crypto.getRandomValues(b);
|
|
1666
|
+
return b;
|
|
1439
1667
|
}
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1668
|
+
function format(b, version) {
|
|
1669
|
+
b[6] = b[6] & 15 | version << 4;
|
|
1670
|
+
b[8] = b[8] & 63 | 128;
|
|
1671
|
+
let hex = "";
|
|
1672
|
+
for (let i = 0; i < 16; i++) {
|
|
1673
|
+
hex += (b[i] + 256).toString(16).slice(1);
|
|
1674
|
+
if (i === 3 || i === 5 || i === 7 || i === 9) hex += "-";
|
|
1675
|
+
}
|
|
1676
|
+
return hex;
|
|
1677
|
+
}
|
|
1678
|
+
function uuid4() {
|
|
1679
|
+
return format(bytes(), 4);
|
|
1680
|
+
}
|
|
1681
|
+
function uuid7() {
|
|
1682
|
+
const b = bytes();
|
|
1683
|
+
let t = Date.now();
|
|
1684
|
+
for (let i = 5; i >= 0; i--) {
|
|
1685
|
+
b[i] = t & 255;
|
|
1686
|
+
t = Math.floor(t / 256);
|
|
1687
|
+
}
|
|
1688
|
+
return format(b, 7);
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
// src/types/api.ts
|
|
1692
|
+
var ERROR_CODE_VALUES = [
|
|
1693
|
+
// Auth
|
|
1694
|
+
"AUTH_INVALID_EMAIL",
|
|
1695
|
+
"AUTH_WEAK_PASSWORD",
|
|
1696
|
+
"AUTH_INVALID_CREDENTIALS",
|
|
1697
|
+
"AUTH_INVALID_API_KEY",
|
|
1698
|
+
"AUTH_EMAIL_EXISTS",
|
|
1699
|
+
"AUTH_USER_NOT_FOUND",
|
|
1700
|
+
"AUTH_OAUTH_CONFIG_ALREADY_EXISTS",
|
|
1701
|
+
"AUTH_OAUTH_CONFIG_ERROR",
|
|
1702
|
+
"AUTH_OAUTH_CONFIG_NOT_FOUND",
|
|
1703
|
+
"AUTH_UNSUPPORTED_PROVIDER",
|
|
1704
|
+
"AUTH_TOKEN_EXPIRED",
|
|
1705
|
+
"AUTH_UNAUTHORIZED",
|
|
1706
|
+
"AUTH_NEED_VERIFICATION",
|
|
1707
|
+
"AUTH_SIGNUP_DISABLED",
|
|
1708
|
+
"AUTH_VERIFICATION_EMAIL_DELIVERY_FAILED",
|
|
1709
|
+
// Database
|
|
1710
|
+
"DATABASE_INVALID_PARAMETER",
|
|
1711
|
+
"DATABASE_VALIDATION_ERROR",
|
|
1712
|
+
"DATABASE_CONSTRAINT_VIOLATION",
|
|
1713
|
+
"DATABASE_NOT_FOUND",
|
|
1714
|
+
"DATABASE_DUPLICATE",
|
|
1715
|
+
"DATABASE_MIGRATION_ALREADY_EXISTS",
|
|
1716
|
+
"DATABASE_PERMISSION_DENIED",
|
|
1717
|
+
"DATABASE_INTERNAL_ERROR",
|
|
1718
|
+
"DATABASE_FORBIDDEN",
|
|
1719
|
+
// Storage
|
|
1720
|
+
"STORAGE_ALREADY_EXISTS",
|
|
1721
|
+
"STORAGE_INVALID_PARAMETER",
|
|
1722
|
+
"STORAGE_INVALID_FILE_TYPE",
|
|
1723
|
+
"STORAGE_INSUFFICIENT_QUOTA",
|
|
1724
|
+
"STORAGE_NOT_FOUND",
|
|
1725
|
+
"STORAGE_PERMISSION_DENIED",
|
|
1726
|
+
"S3_ACCESS_KEY_LIMIT_EXCEEDED",
|
|
1727
|
+
"S3_ACCESS_KEY_NOT_FOUND",
|
|
1728
|
+
"S3_PROTOCOL_UNAVAILABLE",
|
|
1729
|
+
// Realtime
|
|
1730
|
+
"REALTIME_CHANNEL_NOT_FOUND",
|
|
1731
|
+
"REALTIME_CONNECTION_FAILED",
|
|
1732
|
+
"REALTIME_INVALID_CHANNEL_REQUEST",
|
|
1733
|
+
"REALTIME_INVALID_CHANNEL_PATTERN",
|
|
1734
|
+
"REALTIME_INVALID_EVENT",
|
|
1735
|
+
"REALTIME_NOT_SUBSCRIBED",
|
|
1736
|
+
"REALTIME_UNAUTHORIZED",
|
|
1737
|
+
// AI
|
|
1738
|
+
"AI_INVALID_API_KEY",
|
|
1739
|
+
"AI_INVALID_MODEL",
|
|
1740
|
+
"AI_UPSTREAM_UNAVAILABLE",
|
|
1741
|
+
// Analytics
|
|
1742
|
+
"ANALYTICS_NOT_CONNECTED",
|
|
1743
|
+
"ANALYTICS_UNAVAILABLE",
|
|
1744
|
+
// Logs
|
|
1745
|
+
"LOGS_AWS_NOT_CONFIGURED",
|
|
1746
|
+
"LOG_NOT_FOUND",
|
|
1747
|
+
// Compute
|
|
1748
|
+
"COMPUTE_CLOUD_UNAVAILABLE",
|
|
1749
|
+
"COMPUTE_NOT_CONFIGURED",
|
|
1750
|
+
"COMPUTE_PROVIDER_ERROR",
|
|
1751
|
+
"COMPUTE_SERVICE_NOT_FOUND",
|
|
1752
|
+
"COMPUTE_MACHINE_NOT_FOUND",
|
|
1753
|
+
"COMPUTE_SERVICE_NOT_CONFIGURED",
|
|
1754
|
+
"COMPUTE_SERVICE_DEPLOY_FAILED",
|
|
1755
|
+
"COMPUTE_SERVICE_ALREADY_EXISTS",
|
|
1756
|
+
"COMPUTE_SERVICE_START_FAILED",
|
|
1757
|
+
"COMPUTE_SERVICE_STOP_FAILED",
|
|
1758
|
+
"COMPUTE_SERVICE_DELETE_FAILED",
|
|
1759
|
+
"COMPUTE_REGION_CHANGE_NOT_SUPPORTED",
|
|
1760
|
+
"COMPUTE_QUOTA_EXCEEDED",
|
|
1761
|
+
// Billing
|
|
1762
|
+
"BILLING_INSUFFICIENT_BALANCE",
|
|
1763
|
+
// Email
|
|
1764
|
+
"EMAIL_PROVIDER_NOT_CONFIGURED",
|
|
1765
|
+
"EMAIL_SMTP_CONNECTION_FAILED",
|
|
1766
|
+
"EMAIL_SMTP_SEND_FAILED",
|
|
1767
|
+
"EMAIL_TEMPLATE_NOT_FOUND",
|
|
1768
|
+
// Deployments
|
|
1769
|
+
"DEPLOYMENT_ALREADY_EXISTS",
|
|
1770
|
+
"DEPLOYMENT_INVALID_FILE",
|
|
1771
|
+
"DEPLOYMENT_NOT_FOUND",
|
|
1772
|
+
"DEPLOYMENT_UPLOAD_CANCELED",
|
|
1773
|
+
"DOMAIN_ALREADY_EXISTS",
|
|
1774
|
+
"DOMAIN_INVALID",
|
|
1775
|
+
"DOMAIN_NOT_FOUND",
|
|
1776
|
+
"ENVIRONMENT_VARIABLE_NOT_FOUND",
|
|
1777
|
+
// Docs
|
|
1778
|
+
"DOCS_NOT_FOUND",
|
|
1779
|
+
// Functions
|
|
1780
|
+
"FUNCTION_ALREADY_EXISTS",
|
|
1781
|
+
"FUNCTION_DEPLOYMENT_NOT_FOUND",
|
|
1782
|
+
"FUNCTION_NOT_FOUND",
|
|
1783
|
+
// Schedules
|
|
1784
|
+
"SCHEDULE_INVALID_CRON",
|
|
1785
|
+
"SCHEDULE_NOT_FOUND",
|
|
1786
|
+
// Payments
|
|
1787
|
+
"PAYMENT_CHECKOUT_ALREADY_EXISTS",
|
|
1788
|
+
"PAYMENT_CONFIG_INVALID",
|
|
1789
|
+
"PAYMENT_CONFIG_NOT_FOUND",
|
|
1790
|
+
"PAYMENT_NOT_FOUND",
|
|
1791
|
+
"PAYMENT_METHOD_DECLINED",
|
|
1792
|
+
"PAYMENT_PRICE_NOT_FOUND",
|
|
1793
|
+
"PAYMENT_PRODUCT_NOT_FOUND",
|
|
1794
|
+
// Secrets
|
|
1795
|
+
"SECRET_ALREADY_EXISTS",
|
|
1796
|
+
"SECRET_NOT_FOUND",
|
|
1797
|
+
// General
|
|
1798
|
+
"MISSING_FIELD",
|
|
1799
|
+
"ALREADY_EXISTS",
|
|
1800
|
+
"INVALID_INPUT",
|
|
1801
|
+
"NOT_FOUND",
|
|
1802
|
+
"UNKNOWN_ERROR",
|
|
1803
|
+
"INTERNAL_ERROR",
|
|
1804
|
+
"TOO_MANY_REQUESTS",
|
|
1805
|
+
"FORBIDDEN",
|
|
1806
|
+
"RATE_LIMITED",
|
|
1807
|
+
"NOT_IMPLEMENTED",
|
|
1808
|
+
"UPSTREAM_FAILURE"
|
|
1809
|
+
];
|
|
1810
|
+
var ERROR_CODES = Object.fromEntries(
|
|
1811
|
+
ERROR_CODE_VALUES.map((code) => [code, code])
|
|
1812
|
+
);
|
|
1813
|
+
var OAUTH_PROVIDERS = [
|
|
1814
|
+
"google",
|
|
1815
|
+
"github",
|
|
1816
|
+
"discord",
|
|
1817
|
+
"linkedin",
|
|
1818
|
+
"facebook",
|
|
1819
|
+
"instagram",
|
|
1820
|
+
"tiktok",
|
|
1821
|
+
"apple",
|
|
1822
|
+
"x",
|
|
1823
|
+
"spotify",
|
|
1824
|
+
"microsoft"
|
|
1825
|
+
];
|
|
1826
|
+
|
|
1827
|
+
// src/modules/auth/helpers.ts
|
|
1828
|
+
var PKCE_VERIFIER_KEY = "result_pkce_verifier";
|
|
1829
|
+
async function getWebCrypto() {
|
|
1830
|
+
const webCrypto = globalThis.crypto;
|
|
1831
|
+
if (typeof webCrypto?.getRandomValues === "function" && webCrypto.subtle) {
|
|
1832
|
+
return webCrypto;
|
|
1447
1833
|
}
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
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);
|
|
1834
|
+
if (typeof process !== "undefined" && process.versions?.node) {
|
|
1835
|
+
const { webcrypto } = await import("crypto");
|
|
1836
|
+
return webcrypto;
|
|
1464
1837
|
}
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
return
|
|
1838
|
+
throw new Error("Web Crypto API is not available in this environment");
|
|
1839
|
+
}
|
|
1840
|
+
function base64UrlEncode(buffer) {
|
|
1841
|
+
const base64 = btoa(String.fromCharCode(...buffer));
|
|
1842
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1843
|
+
}
|
|
1844
|
+
async function generateCodeVerifier() {
|
|
1845
|
+
const webCrypto = await getWebCrypto();
|
|
1846
|
+
const array = new Uint8Array(32);
|
|
1847
|
+
webCrypto.getRandomValues(array);
|
|
1848
|
+
return base64UrlEncode(array);
|
|
1849
|
+
}
|
|
1850
|
+
async function generateCodeChallenge(verifier) {
|
|
1851
|
+
const webCrypto = await getWebCrypto();
|
|
1852
|
+
const encoder = new TextEncoder();
|
|
1853
|
+
const data = encoder.encode(verifier);
|
|
1854
|
+
const hash = await webCrypto.subtle.digest("SHA-256", data);
|
|
1855
|
+
return base64UrlEncode(new Uint8Array(hash));
|
|
1856
|
+
}
|
|
1857
|
+
function storePkceVerifier(verifier) {
|
|
1858
|
+
if (typeof sessionStorage !== "undefined") {
|
|
1859
|
+
sessionStorage.setItem(PKCE_VERIFIER_KEY, verifier);
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
function retrievePkceVerifier() {
|
|
1863
|
+
if (typeof sessionStorage === "undefined") {
|
|
1864
|
+
return null;
|
|
1865
|
+
}
|
|
1866
|
+
const verifier = sessionStorage.getItem(PKCE_VERIFIER_KEY);
|
|
1867
|
+
if (verifier) {
|
|
1868
|
+
sessionStorage.removeItem(PKCE_VERIFIER_KEY);
|
|
1869
|
+
}
|
|
1870
|
+
return verifier;
|
|
1871
|
+
}
|
|
1872
|
+
function wrapError(error, fallbackMessage) {
|
|
1873
|
+
if (error instanceof ResultError) {
|
|
1874
|
+
return { data: null, error };
|
|
1502
1875
|
}
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
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);
|
|
1876
|
+
return {
|
|
1877
|
+
data: null,
|
|
1878
|
+
error: new ResultError(
|
|
1879
|
+
error instanceof Error ? error.message : fallbackMessage,
|
|
1880
|
+
500,
|
|
1881
|
+
"UNEXPECTED_ERROR"
|
|
1882
|
+
)
|
|
1883
|
+
};
|
|
1884
|
+
}
|
|
1885
|
+
function cleanUrlParams(...params) {
|
|
1886
|
+
if (typeof window === "undefined") {
|
|
1887
|
+
return;
|
|
1521
1888
|
}
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1889
|
+
const url = new URL(window.location.href);
|
|
1890
|
+
for (const p of params) {
|
|
1891
|
+
url.searchParams.delete(p);
|
|
1892
|
+
}
|
|
1893
|
+
window.history.replaceState({}, document.title, url.toString());
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
// src/modules/auth/auth.ts
|
|
1897
|
+
var Auth = class {
|
|
1898
|
+
constructor(http, tokenManager, options = {}) {
|
|
1526
1899
|
this.http = http;
|
|
1900
|
+
this.tokenManager = tokenManager;
|
|
1901
|
+
this.options = options;
|
|
1902
|
+
this.authCallbackHandled = options.detectOAuthCallback === false ? Promise.resolve() : this.detectAuthCallback();
|
|
1903
|
+
}
|
|
1904
|
+
http;
|
|
1905
|
+
tokenManager;
|
|
1906
|
+
options;
|
|
1907
|
+
authCallbackHandled;
|
|
1908
|
+
isServerMode() {
|
|
1909
|
+
return !!this.options.isServerMode;
|
|
1910
|
+
}
|
|
1911
|
+
/** Subscribe to SDK authentication state changes. */
|
|
1912
|
+
onAuthStateChange(callback) {
|
|
1913
|
+
return this.tokenManager.onAuthStateChange(callback);
|
|
1527
1914
|
}
|
|
1528
1915
|
/**
|
|
1529
|
-
*
|
|
1530
|
-
*
|
|
1531
|
-
* @param path - The object key/path
|
|
1532
|
-
* @param file - File or Blob to upload
|
|
1916
|
+
* Save session from API response
|
|
1917
|
+
* Handles token storage, CSRF token, and HTTP auth header
|
|
1533
1918
|
*/
|
|
1534
|
-
|
|
1919
|
+
saveSessionFromResponse(response, event = AuthChangeEvent.SIGNED_IN) {
|
|
1920
|
+
if (!response.accessToken || !response.user) {
|
|
1921
|
+
return false;
|
|
1922
|
+
}
|
|
1923
|
+
const session = {
|
|
1924
|
+
accessToken: response.accessToken,
|
|
1925
|
+
user: response.user
|
|
1926
|
+
};
|
|
1927
|
+
if (!this.isServerMode() && response.csrfToken) {
|
|
1928
|
+
setCsrfToken(response.csrfToken);
|
|
1929
|
+
}
|
|
1930
|
+
if (!this.isServerMode()) {
|
|
1931
|
+
this.tokenManager.saveSession(session, event);
|
|
1932
|
+
}
|
|
1933
|
+
this.http.setAuthToken(response.accessToken);
|
|
1934
|
+
this.http.setRefreshToken(response.refreshToken ?? null);
|
|
1935
|
+
return true;
|
|
1936
|
+
}
|
|
1937
|
+
// ============================================================================
|
|
1938
|
+
// OAuth Callback Detection (runs on initialization)
|
|
1939
|
+
// ============================================================================
|
|
1940
|
+
/**
|
|
1941
|
+
* Detect and handle OAuth callback parameters in URL
|
|
1942
|
+
* Supports the PKCE flow (OAuth callback code in the URL)
|
|
1943
|
+
*/
|
|
1944
|
+
async detectAuthCallback() {
|
|
1945
|
+
if (this.isServerMode() || typeof window === "undefined") {
|
|
1946
|
+
return;
|
|
1947
|
+
}
|
|
1535
1948
|
try {
|
|
1536
|
-
const
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1949
|
+
const params = new URLSearchParams(window.location.search);
|
|
1950
|
+
const error = params.get("error");
|
|
1951
|
+
if (error) {
|
|
1952
|
+
cleanUrlParams("error");
|
|
1953
|
+
console.debug("OAuth callback error:", error);
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
const code = params.get("insforge_code");
|
|
1957
|
+
if (code) {
|
|
1958
|
+
cleanUrlParams("insforge_code");
|
|
1959
|
+
const { error: exchangeError } = await this.exchangeOAuthCode(code);
|
|
1960
|
+
if (exchangeError) {
|
|
1961
|
+
console.debug("OAuth code exchange failed:", exchangeError.message);
|
|
1542
1962
|
}
|
|
1963
|
+
return;
|
|
1964
|
+
}
|
|
1965
|
+
} catch (error) {
|
|
1966
|
+
console.debug("OAuth callback detection skipped:", error);
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
// ============================================================================
|
|
1970
|
+
// Sign Up / Sign In / Sign Out
|
|
1971
|
+
// ============================================================================
|
|
1972
|
+
async signUp(request) {
|
|
1973
|
+
try {
|
|
1974
|
+
const response = await this.http.post(
|
|
1975
|
+
this.isServerMode() ? "/api/auth/users?client_type=mobile" : "/api/auth/users",
|
|
1976
|
+
request,
|
|
1977
|
+
{ credentials: "include", skipAuthRefresh: true }
|
|
1543
1978
|
);
|
|
1544
|
-
if (
|
|
1545
|
-
|
|
1979
|
+
if (response.accessToken && response.user) {
|
|
1980
|
+
this.saveSessionFromResponse(response);
|
|
1546
1981
|
}
|
|
1547
|
-
if (
|
|
1548
|
-
|
|
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 };
|
|
1982
|
+
if (response.refreshToken) {
|
|
1983
|
+
this.http.setRefreshToken(response.refreshToken);
|
|
1561
1984
|
}
|
|
1562
|
-
|
|
1563
|
-
`Unsupported upload method: ${strategyResponse.method}`,
|
|
1564
|
-
500,
|
|
1565
|
-
"STORAGE_ERROR"
|
|
1566
|
-
);
|
|
1985
|
+
return { data: response, error: null };
|
|
1567
1986
|
} catch (error) {
|
|
1568
|
-
return
|
|
1569
|
-
data: null,
|
|
1570
|
-
error: error instanceof InsForgeError ? error : new InsForgeError("Upload failed", 500, "STORAGE_ERROR")
|
|
1571
|
-
};
|
|
1987
|
+
return wrapError(error, "An unexpected error occurred during sign up");
|
|
1572
1988
|
}
|
|
1573
1989
|
}
|
|
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) {
|
|
1990
|
+
async signInWithPassword(request) {
|
|
1580
1991
|
try {
|
|
1581
|
-
const
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
{
|
|
1585
|
-
filename,
|
|
1586
|
-
contentType: file.type || "application/octet-stream",
|
|
1587
|
-
size: file.size
|
|
1588
|
-
}
|
|
1992
|
+
const response = await this.http.post(
|
|
1993
|
+
this.isServerMode() ? "/api/auth/sessions?client_type=mobile" : "/api/auth/sessions",
|
|
1994
|
+
request,
|
|
1995
|
+
{ credentials: "include", skipAuthRefresh: true }
|
|
1589
1996
|
);
|
|
1590
|
-
|
|
1591
|
-
|
|
1997
|
+
this.saveSessionFromResponse(response);
|
|
1998
|
+
if (response.refreshToken) {
|
|
1999
|
+
this.http.setRefreshToken(response.refreshToken);
|
|
1592
2000
|
}
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
2001
|
+
return { data: response, error: null };
|
|
2002
|
+
} catch (error) {
|
|
2003
|
+
return wrapError(error, "An unexpected error occurred during sign in");
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
async signOut() {
|
|
2007
|
+
try {
|
|
2008
|
+
try {
|
|
2009
|
+
const serverMode = this.isServerMode();
|
|
2010
|
+
const csrfToken = !serverMode ? getCsrfToken() : null;
|
|
2011
|
+
await this.http.post(
|
|
2012
|
+
serverMode ? "/api/auth/logout?client_type=mobile" : "/api/auth/logout",
|
|
2013
|
+
void 0,
|
|
1599
2014
|
{
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
}
|
|
2015
|
+
credentials: "include",
|
|
2016
|
+
skipAuthRefresh: true,
|
|
2017
|
+
...csrfToken ? { headers: { "X-CSRF-Token": csrfToken } } : {}
|
|
1604
2018
|
}
|
|
1605
2019
|
);
|
|
1606
|
-
|
|
2020
|
+
} catch {
|
|
1607
2021
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
2022
|
+
this.tokenManager.clearSession();
|
|
2023
|
+
this.http.setAuthToken(null);
|
|
2024
|
+
this.http.setRefreshToken(null);
|
|
2025
|
+
if (!this.isServerMode()) {
|
|
2026
|
+
clearCsrfToken();
|
|
2027
|
+
}
|
|
2028
|
+
return { error: null };
|
|
2029
|
+
} catch {
|
|
1614
2030
|
return {
|
|
1615
|
-
|
|
1616
|
-
error: error instanceof InsForgeError ? error : new InsForgeError("Upload failed", 500, "STORAGE_ERROR")
|
|
2031
|
+
error: new ResultError("Failed to sign out", 500, "SIGNOUT_ERROR")
|
|
1617
2032
|
};
|
|
1618
2033
|
}
|
|
1619
2034
|
}
|
|
1620
|
-
|
|
1621
|
-
* Internal method to handle presigned URL uploads
|
|
1622
|
-
*/
|
|
1623
|
-
async uploadWithPresignedUrl(strategy, file) {
|
|
2035
|
+
async signInWithOAuth(providerOrOptions, options) {
|
|
1624
2036
|
try {
|
|
1625
|
-
|
|
1626
|
-
if (
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
}
|
|
2037
|
+
let signInOptions;
|
|
2038
|
+
if (typeof providerOrOptions === "object") {
|
|
2039
|
+
signInOptions = providerOrOptions;
|
|
2040
|
+
} else if (options) {
|
|
2041
|
+
signInOptions = { provider: providerOrOptions, ...options };
|
|
2042
|
+
} else {
|
|
2043
|
+
return {
|
|
2044
|
+
data: {},
|
|
2045
|
+
error: new ResultError(
|
|
2046
|
+
"OAuth sign-in options are required",
|
|
2047
|
+
400,
|
|
2048
|
+
ERROR_CODES.INVALID_INPUT
|
|
2049
|
+
)
|
|
2050
|
+
};
|
|
1630
2051
|
}
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
2052
|
+
if (!signInOptions || !signInOptions.redirectTo) {
|
|
2053
|
+
return {
|
|
2054
|
+
data: {},
|
|
2055
|
+
error: new ResultError(
|
|
2056
|
+
"Redirect URI is required",
|
|
2057
|
+
400,
|
|
2058
|
+
ERROR_CODES.INVALID_INPUT
|
|
2059
|
+
)
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
const { provider } = signInOptions;
|
|
2063
|
+
const providerKey = encodeURIComponent(provider.toLowerCase());
|
|
2064
|
+
const codeVerifier = await generateCodeVerifier();
|
|
2065
|
+
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
2066
|
+
storePkceVerifier(codeVerifier);
|
|
2067
|
+
const params = {
|
|
2068
|
+
...signInOptions.additionalParams ?? {},
|
|
2069
|
+
redirect_uri: signInOptions.redirectTo,
|
|
2070
|
+
code_challenge: codeChallenge
|
|
2071
|
+
};
|
|
2072
|
+
const isBuiltInProvider = OAUTH_PROVIDERS.includes(
|
|
2073
|
+
providerKey
|
|
2074
|
+
);
|
|
2075
|
+
const oauthPath = isBuiltInProvider ? `/api/auth/oauth/${providerKey}` : `/api/auth/oauth/custom/${providerKey}`;
|
|
2076
|
+
const response = await this.http.get(oauthPath, {
|
|
2077
|
+
params,
|
|
2078
|
+
skipAuthRefresh: true
|
|
1635
2079
|
});
|
|
1636
|
-
if (!
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
uploadResponse.status,
|
|
1640
|
-
"STORAGE_ERROR"
|
|
1641
|
-
);
|
|
1642
|
-
}
|
|
1643
|
-
if (strategy.confirmRequired && strategy.confirmUrl) {
|
|
1644
|
-
const confirmResponse = await this.http.post(strategy.confirmUrl, {
|
|
1645
|
-
size: file.size,
|
|
1646
|
-
contentType: file.type || "application/octet-stream"
|
|
1647
|
-
});
|
|
1648
|
-
return { data: confirmResponse, error: null };
|
|
2080
|
+
if (!this.isServerMode() && typeof window !== "undefined" && !signInOptions.skipBrowserRedirect) {
|
|
2081
|
+
window.location.href = response.authUrl;
|
|
2082
|
+
return { data: {}, error: null };
|
|
1649
2083
|
}
|
|
1650
2084
|
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
|
-
},
|
|
2085
|
+
data: { url: response.authUrl, provider: providerKey, codeVerifier },
|
|
1659
2086
|
error: null
|
|
1660
2087
|
};
|
|
1661
2088
|
} catch (error) {
|
|
1662
|
-
|
|
2089
|
+
if (error instanceof ResultError) {
|
|
2090
|
+
return { data: {}, error };
|
|
2091
|
+
}
|
|
2092
|
+
return {
|
|
2093
|
+
data: {},
|
|
2094
|
+
error: new ResultError(
|
|
2095
|
+
"An unexpected error occurred during OAuth initialization",
|
|
2096
|
+
500,
|
|
2097
|
+
"UNEXPECTED_ERROR"
|
|
2098
|
+
)
|
|
2099
|
+
};
|
|
1663
2100
|
}
|
|
1664
2101
|
}
|
|
1665
2102
|
/**
|
|
1666
|
-
*
|
|
1667
|
-
*
|
|
1668
|
-
* @param path - The object key/path
|
|
1669
|
-
* Returns the file as a Blob
|
|
2103
|
+
* Exchange OAuth authorization code for tokens (PKCE flow)
|
|
2104
|
+
* Called automatically on initialization when the OAuth callback code is in the URL
|
|
1670
2105
|
*/
|
|
1671
|
-
async
|
|
2106
|
+
async exchangeOAuthCode(code, codeVerifier) {
|
|
1672
2107
|
try {
|
|
1673
|
-
const
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
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
|
-
}
|
|
2108
|
+
const verifier = codeVerifier ?? retrievePkceVerifier();
|
|
2109
|
+
if (!verifier) {
|
|
2110
|
+
return {
|
|
2111
|
+
data: null,
|
|
2112
|
+
error: new ResultError(
|
|
2113
|
+
"PKCE code verifier not found. Ensure signInWithOAuth was called in the same browser session.",
|
|
2114
|
+
400,
|
|
2115
|
+
"PKCE_VERIFIER_MISSING"
|
|
2116
|
+
)
|
|
2117
|
+
};
|
|
1710
2118
|
}
|
|
1711
|
-
const
|
|
1712
|
-
|
|
1713
|
-
|
|
2119
|
+
const request = {
|
|
2120
|
+
code,
|
|
2121
|
+
code_verifier: verifier
|
|
2122
|
+
};
|
|
2123
|
+
const response = await this.http.post(
|
|
2124
|
+
this.isServerMode() ? "/api/auth/oauth/exchange?client_type=mobile" : "/api/auth/oauth/exchange",
|
|
2125
|
+
request,
|
|
2126
|
+
{ credentials: "include", skipAuthRefresh: true }
|
|
2127
|
+
);
|
|
2128
|
+
this.saveSessionFromResponse(response);
|
|
1714
2129
|
return {
|
|
1715
|
-
data:
|
|
1716
|
-
error:
|
|
2130
|
+
data: response,
|
|
2131
|
+
error: null
|
|
1717
2132
|
};
|
|
2133
|
+
} catch (error) {
|
|
2134
|
+
return wrapError(
|
|
2135
|
+
error,
|
|
2136
|
+
"An unexpected error occurred during OAuth code exchange"
|
|
2137
|
+
);
|
|
1718
2138
|
}
|
|
1719
2139
|
}
|
|
1720
2140
|
/**
|
|
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}.
|
|
2141
|
+
* Sign in with an ID token from a native SDK (Google One Tap, etc.)
|
|
2142
|
+
* Use this for native mobile apps or Google One Tap on web.
|
|
1725
2143
|
*
|
|
1726
|
-
* @param
|
|
1727
|
-
* @
|
|
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.
|
|
2144
|
+
* @param credentials.provider - The identity provider (currently only 'google' is supported)
|
|
2145
|
+
* @param credentials.token - The ID token from the native SDK
|
|
1740
2146
|
*/
|
|
1741
|
-
async
|
|
1742
|
-
const encoded = encodeURIComponent(path);
|
|
2147
|
+
async signInWithIdToken(credentials) {
|
|
1743
2148
|
try {
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
2149
|
+
const { provider, token } = credentials;
|
|
2150
|
+
const response = await this.http.post(
|
|
2151
|
+
"/api/auth/id-token?client_type=mobile",
|
|
2152
|
+
{ provider, token },
|
|
2153
|
+
{ credentials: "include", skipAuthRefresh: true }
|
|
1747
2154
|
);
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
if (!isMissingRoute) {
|
|
1752
|
-
throw error;
|
|
2155
|
+
this.saveSessionFromResponse(response);
|
|
2156
|
+
if (response.refreshToken) {
|
|
2157
|
+
this.http.setRefreshToken(response.refreshToken);
|
|
1753
2158
|
}
|
|
1754
|
-
return
|
|
1755
|
-
|
|
1756
|
-
|
|
2159
|
+
return {
|
|
2160
|
+
data: response,
|
|
2161
|
+
error: null
|
|
2162
|
+
};
|
|
2163
|
+
} catch (error) {
|
|
2164
|
+
return wrapError(
|
|
2165
|
+
error,
|
|
2166
|
+
"An unexpected error occurred during ID token sign in"
|
|
1757
2167
|
);
|
|
1758
2168
|
}
|
|
1759
2169
|
}
|
|
2170
|
+
// ============================================================================
|
|
2171
|
+
// Session Management
|
|
2172
|
+
// ============================================================================
|
|
1760
2173
|
/**
|
|
1761
|
-
*
|
|
2174
|
+
* Refresh the current auth session.
|
|
1762
2175
|
*
|
|
1763
|
-
*
|
|
1764
|
-
*
|
|
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.
|
|
2176
|
+
* Browser mode:
|
|
2177
|
+
* - Uses httpOnly refresh cookie and optional CSRF header.
|
|
1768
2178
|
*
|
|
1769
|
-
*
|
|
1770
|
-
*
|
|
1771
|
-
*
|
|
2179
|
+
* Legacy server mode (`isServerMode: true`):
|
|
2180
|
+
* - Uses mobile auth flow and requires `refreshToken` in request body.
|
|
2181
|
+
*
|
|
2182
|
+
* SSR apps should prefer `createRefreshAuthRouter()` / `refreshAuth()` from
|
|
2183
|
+
* `@resultdev/sdk/ssr`.
|
|
1772
2184
|
*/
|
|
1773
|
-
async
|
|
2185
|
+
async refreshSession(options) {
|
|
1774
2186
|
try {
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
2187
|
+
if (this.isServerMode() && !options?.refreshToken) {
|
|
2188
|
+
return {
|
|
2189
|
+
data: null,
|
|
2190
|
+
error: new ResultError(
|
|
2191
|
+
"refreshToken is required when refreshing session in server mode",
|
|
2192
|
+
400,
|
|
2193
|
+
ERROR_CODES.AUTH_UNAUTHORIZED
|
|
2194
|
+
)
|
|
2195
|
+
};
|
|
2196
|
+
}
|
|
2197
|
+
const csrfToken = !this.isServerMode() ? getCsrfToken() : null;
|
|
2198
|
+
const response = await this.http.post(
|
|
2199
|
+
this.isServerMode() ? "/api/auth/refresh?client_type=mobile" : "/api/auth/refresh",
|
|
2200
|
+
this.isServerMode() ? { refresh_token: options?.refreshToken } : void 0,
|
|
2201
|
+
{
|
|
2202
|
+
headers: csrfToken ? { "X-CSRF-Token": csrfToken } : {},
|
|
2203
|
+
credentials: "include",
|
|
2204
|
+
skipAuthRefresh: true
|
|
2205
|
+
}
|
|
2206
|
+
);
|
|
2207
|
+
if (response.accessToken) {
|
|
2208
|
+
this.saveSessionFromResponse(response, AuthChangeEvent.TOKEN_REFRESHED);
|
|
2209
|
+
}
|
|
2210
|
+
return { data: response, error: null };
|
|
1783
2211
|
} catch (error) {
|
|
1784
|
-
return
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
2212
|
+
return wrapError(
|
|
2213
|
+
error,
|
|
2214
|
+
"An unexpected error occurred during session refresh"
|
|
2215
|
+
);
|
|
1788
2216
|
}
|
|
1789
2217
|
}
|
|
1790
2218
|
/**
|
|
1791
|
-
*
|
|
1792
|
-
*
|
|
1793
|
-
* Each entry resolves independently: a failure on one key (not found / not
|
|
1794
|
-
* permitted) is reported on that entry's `error` without failing the rest.
|
|
1795
|
-
*
|
|
1796
|
-
* @param paths - The object keys/paths
|
|
1797
|
-
* @param expiresIn - Lifetime in seconds (default 3600 = 1h, max 604800 = 7d)
|
|
2219
|
+
* Get current user, automatically waits for pending OAuth callback
|
|
1798
2220
|
*/
|
|
1799
|
-
async
|
|
2221
|
+
async getCurrentUser() {
|
|
2222
|
+
await this.authCallbackHandled;
|
|
1800
2223
|
try {
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
return {
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
2224
|
+
if (this.isServerMode()) {
|
|
2225
|
+
const accessToken = this.tokenManager.getAccessToken();
|
|
2226
|
+
if (!accessToken) {
|
|
2227
|
+
return { data: { user: null }, error: null };
|
|
2228
|
+
}
|
|
2229
|
+
this.http.setAuthToken(accessToken);
|
|
2230
|
+
const response = await this.http.get(
|
|
2231
|
+
"/api/auth/sessions/current"
|
|
2232
|
+
);
|
|
2233
|
+
const user = response.user ?? null;
|
|
2234
|
+
return { data: { user }, error: null };
|
|
2235
|
+
}
|
|
2236
|
+
const session = this.tokenManager.getSession();
|
|
2237
|
+
if (session) {
|
|
2238
|
+
this.http.setAuthToken(session.accessToken);
|
|
2239
|
+
return { data: { user: session.user }, error: null };
|
|
2240
|
+
}
|
|
2241
|
+
if (typeof window !== "undefined") {
|
|
2242
|
+
const { data: refreshed, error: refreshError } = await this.refreshSession();
|
|
2243
|
+
if (refreshError) {
|
|
2244
|
+
return { data: { user: null }, error: refreshError };
|
|
2245
|
+
}
|
|
2246
|
+
if (refreshed?.accessToken) {
|
|
2247
|
+
return { data: { user: refreshed.user ?? null }, error: null };
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
return { data: { user: null }, error: null };
|
|
1812
2251
|
} catch (error) {
|
|
2252
|
+
if (error instanceof ResultError) {
|
|
2253
|
+
return { data: { user: null }, error };
|
|
2254
|
+
}
|
|
1813
2255
|
return {
|
|
1814
|
-
data: null,
|
|
1815
|
-
error:
|
|
2256
|
+
data: { user: null },
|
|
2257
|
+
error: new ResultError(
|
|
2258
|
+
"An unexpected error occurred while getting user",
|
|
2259
|
+
500,
|
|
2260
|
+
"UNEXPECTED_ERROR"
|
|
2261
|
+
)
|
|
1816
2262
|
};
|
|
1817
2263
|
}
|
|
1818
2264
|
}
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
* @param limit - Maximum number of results (default: 100, max: 1000)
|
|
1824
|
-
* @param offset - Number of results to skip
|
|
1825
|
-
*/
|
|
1826
|
-
async list(options) {
|
|
2265
|
+
// ============================================================================
|
|
2266
|
+
// Profile Management
|
|
2267
|
+
// ============================================================================
|
|
2268
|
+
async getProfile(userId) {
|
|
1827
2269
|
try {
|
|
1828
|
-
const params = {};
|
|
1829
|
-
if (options?.prefix) {
|
|
1830
|
-
params.prefix = options.prefix;
|
|
1831
|
-
}
|
|
1832
|
-
if (options?.search) {
|
|
1833
|
-
params.search = options.search;
|
|
1834
|
-
}
|
|
1835
|
-
if (options?.limit) {
|
|
1836
|
-
params.limit = options.limit.toString();
|
|
1837
|
-
}
|
|
1838
|
-
if (options?.offset) {
|
|
1839
|
-
params.offset = options.offset.toString();
|
|
1840
|
-
}
|
|
1841
2270
|
const response = await this.http.get(
|
|
1842
|
-
`/api/
|
|
1843
|
-
{ params }
|
|
2271
|
+
`/api/auth/profiles/${userId}`
|
|
1844
2272
|
);
|
|
1845
2273
|
return { data: response, error: null };
|
|
1846
2274
|
} catch (error) {
|
|
1847
|
-
return
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
2275
|
+
return wrapError(
|
|
2276
|
+
error,
|
|
2277
|
+
"An unexpected error occurred while fetching user profile"
|
|
2278
|
+
);
|
|
1851
2279
|
}
|
|
1852
2280
|
}
|
|
1853
|
-
|
|
1854
|
-
* Delete a file
|
|
1855
|
-
* @param path - The object key/path
|
|
1856
|
-
*/
|
|
1857
|
-
async remove(path) {
|
|
2281
|
+
async setProfile(profile) {
|
|
1858
2282
|
try {
|
|
1859
|
-
const response = await this.http.
|
|
1860
|
-
|
|
2283
|
+
const response = await this.http.patch(
|
|
2284
|
+
"/api/auth/profiles/current",
|
|
2285
|
+
{
|
|
2286
|
+
profile
|
|
2287
|
+
}
|
|
1861
2288
|
);
|
|
2289
|
+
const currentUser = this.tokenManager.getUser();
|
|
2290
|
+
if (!this.isServerMode() && currentUser && response.profile !== void 0) {
|
|
2291
|
+
this.tokenManager.setUser({
|
|
2292
|
+
...currentUser,
|
|
2293
|
+
profile: response.profile
|
|
2294
|
+
});
|
|
2295
|
+
}
|
|
1862
2296
|
return { data: response, error: null };
|
|
1863
2297
|
} catch (error) {
|
|
1864
|
-
return
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
2298
|
+
return wrapError(
|
|
2299
|
+
error,
|
|
2300
|
+
"An unexpected error occurred while updating user profile"
|
|
2301
|
+
);
|
|
1868
2302
|
}
|
|
1869
2303
|
}
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
* @param bucketName - Name of the bucket
|
|
1878
|
-
*/
|
|
1879
|
-
from(bucketName) {
|
|
1880
|
-
return new StorageBucket(bucketName, this.http);
|
|
1881
|
-
}
|
|
1882
|
-
};
|
|
1883
|
-
var AI = class {
|
|
1884
|
-
constructor(http) {
|
|
1885
|
-
this.http = http;
|
|
1886
|
-
this.chat = new Chat(http);
|
|
1887
|
-
this.images = new Images(http);
|
|
1888
|
-
this.embeddings = new Embeddings(http);
|
|
1889
|
-
}
|
|
1890
|
-
};
|
|
1891
|
-
var Chat = class {
|
|
1892
|
-
constructor(http) {
|
|
1893
|
-
this.completions = new ChatCompletions(http);
|
|
1894
|
-
}
|
|
1895
|
-
};
|
|
1896
|
-
var ChatCompletions = class {
|
|
1897
|
-
constructor(http) {
|
|
1898
|
-
this.http = http;
|
|
1899
|
-
}
|
|
1900
|
-
/**
|
|
1901
|
-
* Create a chat completion - OpenAI-like response format
|
|
1902
|
-
*
|
|
1903
|
-
* @example
|
|
1904
|
-
* ```typescript
|
|
1905
|
-
* // Non-streaming
|
|
1906
|
-
* const completion = await client.ai.chat.completions.create({
|
|
1907
|
-
* model: 'gpt-4',
|
|
1908
|
-
* messages: [{ role: 'user', content: 'Hello!' }]
|
|
1909
|
-
* });
|
|
1910
|
-
* console.log(completion.choices[0].message.content);
|
|
1911
|
-
*
|
|
1912
|
-
* // With images (OpenAI-compatible format)
|
|
1913
|
-
* const response = await client.ai.chat.completions.create({
|
|
1914
|
-
* model: 'gpt-4-vision',
|
|
1915
|
-
* messages: [{
|
|
1916
|
-
* role: 'user',
|
|
1917
|
-
* content: [
|
|
1918
|
-
* { type: 'text', text: 'What is in this image?' },
|
|
1919
|
-
* { type: 'image_url', image_url: { url: 'https://example.com/image.jpg' } }
|
|
1920
|
-
* ]
|
|
1921
|
-
* }]
|
|
1922
|
-
* });
|
|
1923
|
-
*
|
|
1924
|
-
* // With PDF files
|
|
1925
|
-
* const pdfResponse = await client.ai.chat.completions.create({
|
|
1926
|
-
* model: 'anthropic/claude-3.5-sonnet',
|
|
1927
|
-
* messages: [{
|
|
1928
|
-
* role: 'user',
|
|
1929
|
-
* content: [
|
|
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
|
-
* });
|
|
1936
|
-
*
|
|
1937
|
-
* // With web search
|
|
1938
|
-
* const searchResponse = await client.ai.chat.completions.create({
|
|
1939
|
-
* model: 'openai/gpt-4',
|
|
1940
|
-
* messages: [{ role: 'user', content: 'What are the latest news about AI?' }],
|
|
1941
|
-
* webSearch: { enabled: true, maxResults: 5 }
|
|
1942
|
-
* });
|
|
1943
|
-
* // Access citations from response.choices[0].message.annotations
|
|
1944
|
-
*
|
|
1945
|
-
* // With thinking/reasoning mode (Anthropic models)
|
|
1946
|
-
* const thinkingResponse = await client.ai.chat.completions.create({
|
|
1947
|
-
* model: 'anthropic/claude-3.5-sonnet',
|
|
1948
|
-
* messages: [{ role: 'user', content: 'Solve this complex math problem...' }],
|
|
1949
|
-
* thinking: true
|
|
1950
|
-
* });
|
|
1951
|
-
*
|
|
1952
|
-
* // Streaming - returns async iterable
|
|
1953
|
-
* const stream = await client.ai.chat.completions.create({
|
|
1954
|
-
* model: 'gpt-4',
|
|
1955
|
-
* messages: [{ role: 'user', content: 'Tell me a story' }],
|
|
1956
|
-
* stream: true
|
|
1957
|
-
* });
|
|
1958
|
-
*
|
|
1959
|
-
* for await (const chunk of stream) {
|
|
1960
|
-
* if (chunk.choices[0]?.delta?.content) {
|
|
1961
|
-
* process.stdout.write(chunk.choices[0].delta.content);
|
|
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)
|
|
2304
|
+
// ============================================================================
|
|
2305
|
+
// Email Verification
|
|
2306
|
+
// ============================================================================
|
|
2307
|
+
async resendVerificationEmail(request) {
|
|
2308
|
+
try {
|
|
2309
|
+
const response = await this.http.post("/api/auth/email/send-verification", request, {
|
|
2310
|
+
skipAuthRefresh: true
|
|
1990
2311
|
});
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
2312
|
+
return { data: response, error: null };
|
|
2313
|
+
} catch (error) {
|
|
2314
|
+
return wrapError(
|
|
2315
|
+
error,
|
|
2316
|
+
"An unexpected error occurred while sending verification email"
|
|
2317
|
+
);
|
|
1996
2318
|
}
|
|
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
2319
|
}
|
|
2028
|
-
|
|
2029
|
-
* Parse SSE stream into async iterable of OpenAI-like chunks
|
|
2030
|
-
*/
|
|
2031
|
-
async *parseSSEStream(response, model) {
|
|
2032
|
-
const reader = response.body.getReader();
|
|
2033
|
-
const decoder = new TextDecoder();
|
|
2034
|
-
let buffer = "";
|
|
2320
|
+
async verifyEmail(request) {
|
|
2035
2321
|
try {
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2322
|
+
const response = await this.http.post(
|
|
2323
|
+
this.isServerMode() ? "/api/auth/email/verify?client_type=mobile" : "/api/auth/email/verify",
|
|
2324
|
+
request,
|
|
2325
|
+
{ credentials: "include", skipAuthRefresh: true }
|
|
2326
|
+
);
|
|
2327
|
+
this.saveSessionFromResponse(response);
|
|
2328
|
+
if (response.refreshToken) {
|
|
2329
|
+
this.http.setRefreshToken(response.refreshToken);
|
|
2330
|
+
}
|
|
2331
|
+
return { data: response, error: null };
|
|
2332
|
+
} catch (error) {
|
|
2333
|
+
return wrapError(
|
|
2334
|
+
error,
|
|
2335
|
+
"An unexpected error occurred while verifying email"
|
|
2336
|
+
);
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
// ============================================================================
|
|
2340
|
+
// Password Reset
|
|
2341
|
+
// ============================================================================
|
|
2342
|
+
async sendResetPasswordEmail(request) {
|
|
2343
|
+
try {
|
|
2344
|
+
const response = await this.http.post("/api/auth/email/send-reset-password", request, {
|
|
2345
|
+
skipAuthRefresh: true
|
|
2346
|
+
});
|
|
2347
|
+
return { data: response, error: null };
|
|
2348
|
+
} catch (error) {
|
|
2349
|
+
return wrapError(
|
|
2350
|
+
error,
|
|
2351
|
+
"An unexpected error occurred while sending password reset email"
|
|
2352
|
+
);
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
async exchangeResetPasswordToken(request) {
|
|
2356
|
+
try {
|
|
2357
|
+
const response = await this.http.post(
|
|
2358
|
+
"/api/auth/email/exchange-reset-password-token",
|
|
2359
|
+
request,
|
|
2360
|
+
{ skipAuthRefresh: true }
|
|
2361
|
+
);
|
|
2362
|
+
return { data: response, error: null };
|
|
2363
|
+
} catch (error) {
|
|
2364
|
+
return wrapError(
|
|
2365
|
+
error,
|
|
2366
|
+
"An unexpected error occurred while verifying reset code"
|
|
2367
|
+
);
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
async resetPassword(request) {
|
|
2371
|
+
try {
|
|
2372
|
+
const response = await this.http.post(
|
|
2373
|
+
"/api/auth/email/reset-password",
|
|
2374
|
+
request,
|
|
2375
|
+
{ skipAuthRefresh: true }
|
|
2376
|
+
);
|
|
2377
|
+
return { data: response, error: null };
|
|
2378
|
+
} catch (error) {
|
|
2379
|
+
return wrapError(
|
|
2380
|
+
error,
|
|
2381
|
+
"An unexpected error occurred while resetting password"
|
|
2382
|
+
);
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
// ============================================================================
|
|
2386
|
+
// Configuration
|
|
2387
|
+
// ============================================================================
|
|
2388
|
+
async getPublicAuthConfig() {
|
|
2389
|
+
try {
|
|
2390
|
+
const response = await this.http.get(
|
|
2391
|
+
"/api/auth/public-config",
|
|
2392
|
+
{
|
|
2393
|
+
skipAuthRefresh: true
|
|
2093
2394
|
}
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2395
|
+
);
|
|
2396
|
+
return { data: response, error: null };
|
|
2397
|
+
} catch (error) {
|
|
2398
|
+
return wrapError(
|
|
2399
|
+
error,
|
|
2400
|
+
"An unexpected error occurred while fetching auth configuration"
|
|
2401
|
+
);
|
|
2097
2402
|
}
|
|
2098
2403
|
}
|
|
2099
2404
|
};
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2405
|
+
|
|
2406
|
+
// src/modules/database-postgrest.ts
|
|
2407
|
+
import { PostgrestClient } from "@supabase/postgrest-js";
|
|
2408
|
+
function createResultPostgrestFetch(httpClient) {
|
|
2409
|
+
return async (input, init) => {
|
|
2410
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
2411
|
+
const urlObj = new URL(url);
|
|
2412
|
+
const pathname = urlObj.pathname.slice(1);
|
|
2413
|
+
const rpcMatch = pathname.match(/^rpc\/(.+)$/);
|
|
2414
|
+
const endpoint = rpcMatch ? `/api/database/rpc/${rpcMatch[1]}` : `/api/database/records/${pathname}`;
|
|
2415
|
+
const backendUrl = `${httpClient.baseUrl}${endpoint}${urlObj.search}`;
|
|
2416
|
+
const headers = new Headers(httpClient.getHeaders());
|
|
2417
|
+
new Headers(init?.headers).forEach((value, key) => {
|
|
2418
|
+
headers.set(key, value);
|
|
2419
|
+
});
|
|
2420
|
+
const response = await httpClient.rawFetch(backendUrl, {
|
|
2421
|
+
...init,
|
|
2422
|
+
headers
|
|
2423
|
+
});
|
|
2424
|
+
return response;
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
var Database = class {
|
|
2428
|
+
postgrest;
|
|
2429
|
+
constructor(httpClient, defaultSchema) {
|
|
2430
|
+
this.postgrest = new PostgrestClient("http://dummy", {
|
|
2431
|
+
fetch: createResultPostgrestFetch(httpClient),
|
|
2432
|
+
headers: {},
|
|
2433
|
+
...defaultSchema ? { schema: defaultSchema } : {}
|
|
2434
|
+
});
|
|
2103
2435
|
}
|
|
2104
2436
|
/**
|
|
2105
|
-
*
|
|
2437
|
+
* Select a non-default Postgres schema for the chained query. Maps to
|
|
2438
|
+
* PostgREST's `Accept-Profile` (reads) / `Content-Profile` (writes) header.
|
|
2439
|
+
* The schema must be exposed by the backend.
|
|
2106
2440
|
*
|
|
2107
2441
|
* @example
|
|
2108
|
-
*
|
|
2109
|
-
*
|
|
2110
|
-
*
|
|
2111
|
-
*
|
|
2112
|
-
* input: 'Hello world'
|
|
2113
|
-
* });
|
|
2114
|
-
* console.log(response.data[0].embedding); // number[]
|
|
2442
|
+
* const { data } = await client.database
|
|
2443
|
+
* .schema('analytics')
|
|
2444
|
+
* .from('events')
|
|
2445
|
+
* .select('*');
|
|
2115
2446
|
*
|
|
2116
|
-
*
|
|
2117
|
-
*
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
*
|
|
2447
|
+
* @example
|
|
2448
|
+
* await client.database.schema('analytics').rpc('rollup', { day: '2026-01-01' });
|
|
2449
|
+
*/
|
|
2450
|
+
schema(schemaName) {
|
|
2451
|
+
return this.postgrest.schema(schemaName);
|
|
2452
|
+
}
|
|
2453
|
+
/**
|
|
2454
|
+
* Create a query builder for a table
|
|
2124
2455
|
*
|
|
2125
|
-
*
|
|
2126
|
-
*
|
|
2127
|
-
*
|
|
2128
|
-
*
|
|
2129
|
-
*
|
|
2130
|
-
*
|
|
2456
|
+
* @example
|
|
2457
|
+
* // Basic query
|
|
2458
|
+
* const { data, error } = await client.database
|
|
2459
|
+
* .from('posts')
|
|
2460
|
+
* .select('*')
|
|
2461
|
+
* .eq('user_id', userId);
|
|
2131
2462
|
*
|
|
2132
|
-
* // With
|
|
2133
|
-
* const
|
|
2134
|
-
*
|
|
2135
|
-
*
|
|
2136
|
-
*
|
|
2137
|
-
*
|
|
2138
|
-
*
|
|
2463
|
+
* // With count (Supabase style!)
|
|
2464
|
+
* const { data, error, count } = await client.database
|
|
2465
|
+
* .from('posts')
|
|
2466
|
+
* .select('*', { count: 'exact' })
|
|
2467
|
+
* .range(0, 9);
|
|
2468
|
+
*
|
|
2469
|
+
* // Just get count, no data
|
|
2470
|
+
* const { count } = await client.database
|
|
2471
|
+
* .from('posts')
|
|
2472
|
+
* .select('*', { count: 'exact', head: true });
|
|
2473
|
+
*
|
|
2474
|
+
* // Complex queries with OR
|
|
2475
|
+
* const { data } = await client.database
|
|
2476
|
+
* .from('posts')
|
|
2477
|
+
* .select('*, users!inner(*)')
|
|
2478
|
+
* .or('status.eq.active,status.eq.pending');
|
|
2479
|
+
*
|
|
2480
|
+
* // All features work:
|
|
2481
|
+
* - Nested selects
|
|
2482
|
+
* - Foreign key expansion
|
|
2483
|
+
* - OR/AND/NOT conditions
|
|
2484
|
+
* - Count with head
|
|
2485
|
+
* - Range pagination
|
|
2486
|
+
* - Upserts
|
|
2139
2487
|
*/
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2488
|
+
from(table) {
|
|
2489
|
+
return this.postgrest.from(table);
|
|
2490
|
+
}
|
|
2491
|
+
/**
|
|
2492
|
+
* Call a PostgreSQL function (RPC)
|
|
2493
|
+
*
|
|
2494
|
+
* @example
|
|
2495
|
+
* // Call a function with parameters
|
|
2496
|
+
* const { data, error } = await client.database
|
|
2497
|
+
* .rpc('get_user_stats', { user_id: 123 });
|
|
2498
|
+
*
|
|
2499
|
+
* // Call a function with no parameters
|
|
2500
|
+
* const { data, error } = await client.database
|
|
2501
|
+
* .rpc('get_all_active_users');
|
|
2502
|
+
*
|
|
2503
|
+
* // With options (head, count, get)
|
|
2504
|
+
* const { data, count } = await client.database
|
|
2505
|
+
* .rpc('search_posts', { query: 'hello' }, { count: 'exact' });
|
|
2506
|
+
*/
|
|
2507
|
+
rpc(fn, args, options) {
|
|
2508
|
+
return this.postgrest.rpc(fn, args, options);
|
|
2154
2509
|
}
|
|
2155
2510
|
};
|
|
2156
|
-
|
|
2511
|
+
|
|
2512
|
+
// src/modules/email.ts
|
|
2513
|
+
var Emails = class {
|
|
2514
|
+
http;
|
|
2157
2515
|
constructor(http) {
|
|
2158
2516
|
this.http = http;
|
|
2159
2517
|
}
|
|
2160
2518
|
/**
|
|
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
|
-
* ```
|
|
2519
|
+
* Send a custom HTML email
|
|
2520
|
+
* @param options Email options including recipients, subject, and HTML content
|
|
2183
2521
|
*/
|
|
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
|
-
}
|
|
2522
|
+
async send(options) {
|
|
2523
|
+
try {
|
|
2524
|
+
const data = await this.http.post(
|
|
2525
|
+
"/api/email/send-raw",
|
|
2526
|
+
options
|
|
2527
|
+
);
|
|
2528
|
+
return { data, error: null };
|
|
2529
|
+
} catch (error) {
|
|
2530
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
2531
|
+
throw error;
|
|
2207
2532
|
}
|
|
2208
|
-
|
|
2533
|
+
return {
|
|
2534
|
+
data: null,
|
|
2535
|
+
error: error instanceof ResultError ? error : new ResultError(
|
|
2536
|
+
error instanceof Error ? error.message : "Email send failed",
|
|
2537
|
+
500,
|
|
2538
|
+
"EMAIL_ERROR"
|
|
2539
|
+
)
|
|
2540
|
+
};
|
|
2541
|
+
}
|
|
2209
2542
|
}
|
|
2210
2543
|
};
|
|
2544
|
+
|
|
2545
|
+
// src/modules/functions.ts
|
|
2211
2546
|
var Functions = class _Functions {
|
|
2547
|
+
http;
|
|
2548
|
+
functionsUrl;
|
|
2212
2549
|
constructor(http, functionsUrl) {
|
|
2213
2550
|
this.http = http;
|
|
2214
2551
|
this.functionsUrl = functionsUrl || _Functions.deriveSubhostingUrl(http.baseUrl);
|
|
2215
2552
|
}
|
|
2216
2553
|
/**
|
|
2217
2554
|
* Derive the subhosting URL from the base URL.
|
|
2218
|
-
*
|
|
2219
|
-
*
|
|
2220
|
-
* Only applies to
|
|
2555
|
+
* Rewrites the backend base URL to its functions subhosting URL
|
|
2556
|
+
* ({appKey}.{region}.<domain> -> {appKey}.functions.<domain>).
|
|
2557
|
+
* Only applies to platform-hosted backends.
|
|
2221
2558
|
*/
|
|
2222
2559
|
static deriveSubhostingUrl(baseUrl) {
|
|
2223
2560
|
try {
|
|
@@ -2236,7 +2573,7 @@ var Functions = class _Functions {
|
|
|
2236
2573
|
* placeholder; the router only reads pathname.
|
|
2237
2574
|
*/
|
|
2238
2575
|
buildInProcessRequest(slug, method, body, callerHeaders) {
|
|
2239
|
-
const url = new URL(
|
|
2576
|
+
const url = new URL(`/${slug}`, "http://backend.local").toString();
|
|
2240
2577
|
const headers = { ...this.http.getHeaders() };
|
|
2241
2578
|
const reqBody = serializeBody(method, body, headers);
|
|
2242
2579
|
Object.assign(headers, callerHeaders);
|
|
@@ -2250,7 +2587,7 @@ var Functions = class _Functions {
|
|
|
2250
2587
|
* Invoke an Edge Function.
|
|
2251
2588
|
*
|
|
2252
2589
|
* Dispatch order:
|
|
2253
|
-
* 1. If
|
|
2590
|
+
* 1. If the platform's in-process dispatch hook is present, call it directly.
|
|
2254
2591
|
* This avoids Deno Subhosting's 508 Loop Detected when one bundled
|
|
2255
2592
|
* function invokes another inside the same deployment.
|
|
2256
2593
|
* 2. Otherwise, try the configured subhosting URL.
|
|
@@ -2275,7 +2612,7 @@ var Functions = class _Functions {
|
|
|
2275
2612
|
}
|
|
2276
2613
|
return {
|
|
2277
2614
|
data: null,
|
|
2278
|
-
error: error instanceof
|
|
2615
|
+
error: error instanceof ResultError ? error : new ResultError(
|
|
2279
2616
|
error instanceof Error ? error.message : "Function invocation failed",
|
|
2280
2617
|
500,
|
|
2281
2618
|
"FUNCTION_ERROR"
|
|
@@ -2285,20 +2622,24 @@ var Functions = class _Functions {
|
|
|
2285
2622
|
}
|
|
2286
2623
|
if (this.functionsUrl) {
|
|
2287
2624
|
try {
|
|
2288
|
-
const data = await this.http.request(
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2625
|
+
const data = await this.http.request(
|
|
2626
|
+
method,
|
|
2627
|
+
`${this.functionsUrl}/${slug}`,
|
|
2628
|
+
{
|
|
2629
|
+
body,
|
|
2630
|
+
headers
|
|
2631
|
+
}
|
|
2632
|
+
);
|
|
2292
2633
|
return { data, error: null };
|
|
2293
2634
|
} catch (error) {
|
|
2294
2635
|
if (error instanceof Error && error.name === "AbortError") {
|
|
2295
2636
|
throw error;
|
|
2296
2637
|
}
|
|
2297
|
-
if (error instanceof
|
|
2638
|
+
if (error instanceof ResultError && error.statusCode === 404) {
|
|
2298
2639
|
} else {
|
|
2299
2640
|
return {
|
|
2300
2641
|
data: null,
|
|
2301
|
-
error: error instanceof
|
|
2642
|
+
error: error instanceof ResultError ? error : new ResultError(
|
|
2302
2643
|
error instanceof Error ? error.message : "Function invocation failed",
|
|
2303
2644
|
500,
|
|
2304
2645
|
"FUNCTION_ERROR"
|
|
@@ -2317,7 +2658,7 @@ var Functions = class _Functions {
|
|
|
2317
2658
|
}
|
|
2318
2659
|
return {
|
|
2319
2660
|
data: null,
|
|
2320
|
-
error: error instanceof
|
|
2661
|
+
error: error instanceof ResultError ? error : new ResultError(
|
|
2321
2662
|
error instanceof Error ? error.message : "Function invocation failed",
|
|
2322
2663
|
500,
|
|
2323
2664
|
"FUNCTION_ERROR"
|
|
@@ -2326,6 +2667,8 @@ var Functions = class _Functions {
|
|
|
2326
2667
|
}
|
|
2327
2668
|
}
|
|
2328
2669
|
};
|
|
2670
|
+
|
|
2671
|
+
// src/modules/realtime.ts
|
|
2329
2672
|
var CONNECT_TIMEOUT = 1e4;
|
|
2330
2673
|
var SUBSCRIBE_TIMEOUT = 1e4;
|
|
2331
2674
|
var Realtime = class {
|
|
@@ -2334,18 +2677,22 @@ var Realtime = class {
|
|
|
2334
2677
|
this.tokenManager = tokenManager;
|
|
2335
2678
|
this.anonKey = anonKey;
|
|
2336
2679
|
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
2680
|
this.tokenManager.onAuthStateChange((event) => {
|
|
2344
2681
|
if (event !== AuthChangeEvent.TOKEN_REFRESHED) {
|
|
2345
2682
|
this.reconnectForAuthChange();
|
|
2346
2683
|
}
|
|
2347
2684
|
});
|
|
2348
2685
|
}
|
|
2686
|
+
baseUrl;
|
|
2687
|
+
tokenManager;
|
|
2688
|
+
anonKey;
|
|
2689
|
+
getValidAccessToken;
|
|
2690
|
+
socket = null;
|
|
2691
|
+
connectPromise = null;
|
|
2692
|
+
connectionAttempt = null;
|
|
2693
|
+
nextConnectionAttemptId = 0;
|
|
2694
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
2695
|
+
eventListeners = /* @__PURE__ */ new Map();
|
|
2349
2696
|
notifyListeners(event, payload) {
|
|
2350
2697
|
for (const callback of this.eventListeners.get(event) ?? []) {
|
|
2351
2698
|
try {
|
|
@@ -2510,7 +2857,10 @@ var Realtime = class {
|
|
|
2510
2857
|
{
|
|
2511
2858
|
ok: false,
|
|
2512
2859
|
channel: subscription.channel,
|
|
2513
|
-
error: {
|
|
2860
|
+
error: {
|
|
2861
|
+
code: "DISCONNECTED",
|
|
2862
|
+
message: "Connection lost before subscription completed"
|
|
2863
|
+
}
|
|
2514
2864
|
},
|
|
2515
2865
|
true
|
|
2516
2866
|
);
|
|
@@ -2533,7 +2883,10 @@ var Realtime = class {
|
|
|
2533
2883
|
return Promise.resolve({
|
|
2534
2884
|
ok: false,
|
|
2535
2885
|
channel,
|
|
2536
|
-
error: {
|
|
2886
|
+
error: {
|
|
2887
|
+
code: "CONNECTION_FAILED",
|
|
2888
|
+
message: "Not connected to realtime server"
|
|
2889
|
+
}
|
|
2537
2890
|
});
|
|
2538
2891
|
}
|
|
2539
2892
|
subscription.status = "pending";
|
|
@@ -2564,333 +2917,702 @@ var Realtime = class {
|
|
|
2564
2917
|
);
|
|
2565
2918
|
}
|
|
2566
2919
|
}, SUBSCRIBE_TIMEOUT);
|
|
2567
|
-
socket.emit(
|
|
2568
|
-
|
|
2569
|
-
|
|
2920
|
+
socket.emit(
|
|
2921
|
+
"realtime:subscribe",
|
|
2922
|
+
{ channel },
|
|
2923
|
+
(response) => {
|
|
2924
|
+
if (this.subscriptions.get(channel) !== subscription || subscription.epoch !== epoch) {
|
|
2925
|
+
return;
|
|
2926
|
+
}
|
|
2927
|
+
if (response.ok) {
|
|
2928
|
+
subscription.status = "subscribed";
|
|
2929
|
+
subscription.members = new Map(
|
|
2930
|
+
response.presence.members.map((member) => [
|
|
2931
|
+
member.presenceId,
|
|
2932
|
+
member
|
|
2933
|
+
])
|
|
2934
|
+
);
|
|
2935
|
+
} else {
|
|
2936
|
+
subscription.status = "rejected";
|
|
2937
|
+
subscription.members.clear();
|
|
2938
|
+
}
|
|
2939
|
+
this.settleSubscription(subscription, response, false);
|
|
2940
|
+
}
|
|
2941
|
+
);
|
|
2942
|
+
});
|
|
2943
|
+
return subscription.pending;
|
|
2944
|
+
}
|
|
2945
|
+
settleSubscription(subscription, response, incrementEpoch) {
|
|
2946
|
+
if (incrementEpoch) {
|
|
2947
|
+
subscription.epoch++;
|
|
2948
|
+
}
|
|
2949
|
+
subscription.settlePending?.(response);
|
|
2950
|
+
}
|
|
2951
|
+
applyPresenceEvent(event, message) {
|
|
2952
|
+
if (event !== "presence:join" && event !== "presence:leave") {
|
|
2953
|
+
return;
|
|
2954
|
+
}
|
|
2955
|
+
const presenceEvent = message;
|
|
2956
|
+
const channel = presenceEvent.meta?.channel;
|
|
2957
|
+
const member = presenceEvent.member;
|
|
2958
|
+
if (!channel || !member) {
|
|
2959
|
+
return;
|
|
2960
|
+
}
|
|
2961
|
+
const subscription = this.subscriptions.get(channel);
|
|
2962
|
+
if (!subscription) {
|
|
2963
|
+
return;
|
|
2964
|
+
}
|
|
2965
|
+
if (event === "presence:join") {
|
|
2966
|
+
subscription.members.set(member.presenceId, member);
|
|
2967
|
+
} else {
|
|
2968
|
+
subscription.members.delete(member.presenceId);
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
get isConnected() {
|
|
2972
|
+
return this.socket?.connected ?? false;
|
|
2973
|
+
}
|
|
2974
|
+
get connectionState() {
|
|
2975
|
+
if (!this.socket) {
|
|
2976
|
+
return "disconnected";
|
|
2977
|
+
}
|
|
2978
|
+
return this.socket.connected ? "connected" : "connecting";
|
|
2979
|
+
}
|
|
2980
|
+
get socketId() {
|
|
2981
|
+
return this.socket?.id;
|
|
2982
|
+
}
|
|
2983
|
+
async subscribe(channel) {
|
|
2984
|
+
let subscription = this.subscriptions.get(channel);
|
|
2985
|
+
if (subscription) {
|
|
2986
|
+
if (subscription.pending) {
|
|
2987
|
+
return subscription.pending;
|
|
2988
|
+
}
|
|
2989
|
+
if (subscription.status === "subscribed") {
|
|
2990
|
+
return {
|
|
2991
|
+
ok: true,
|
|
2992
|
+
channel,
|
|
2993
|
+
presence: { members: [...subscription.members.values()] }
|
|
2994
|
+
};
|
|
2995
|
+
}
|
|
2996
|
+
} else {
|
|
2997
|
+
subscription = {
|
|
2998
|
+
channel,
|
|
2999
|
+
epoch: 0,
|
|
3000
|
+
status: "pending",
|
|
3001
|
+
members: /* @__PURE__ */ new Map()
|
|
3002
|
+
};
|
|
3003
|
+
this.subscriptions.set(channel, subscription);
|
|
3004
|
+
}
|
|
3005
|
+
if (!this.socket?.connected) {
|
|
3006
|
+
try {
|
|
3007
|
+
await this.connect();
|
|
3008
|
+
} catch (error) {
|
|
3009
|
+
if (this.subscriptions.get(channel) === subscription) {
|
|
3010
|
+
this.subscriptions.delete(channel);
|
|
3011
|
+
}
|
|
3012
|
+
const message = error instanceof Error ? error.message : "Connection failed";
|
|
3013
|
+
return {
|
|
3014
|
+
ok: false,
|
|
3015
|
+
channel,
|
|
3016
|
+
error: { code: "CONNECTION_FAILED", message }
|
|
3017
|
+
};
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
return subscription.pending ?? this.requestSubscription(channel, subscription);
|
|
3021
|
+
}
|
|
3022
|
+
unsubscribe(channel) {
|
|
3023
|
+
const subscription = this.subscriptions.get(channel);
|
|
3024
|
+
if (!subscription) {
|
|
3025
|
+
return;
|
|
3026
|
+
}
|
|
3027
|
+
this.subscriptions.delete(channel);
|
|
3028
|
+
this.settleSubscription(
|
|
3029
|
+
subscription,
|
|
3030
|
+
{
|
|
3031
|
+
ok: false,
|
|
3032
|
+
channel,
|
|
3033
|
+
error: {
|
|
3034
|
+
code: "SUBSCRIPTION_CANCELLED",
|
|
3035
|
+
message: "Subscription cancelled"
|
|
3036
|
+
}
|
|
3037
|
+
},
|
|
3038
|
+
true
|
|
3039
|
+
);
|
|
3040
|
+
if (this.socket?.connected) {
|
|
3041
|
+
this.socket.emit("realtime:unsubscribe", { channel });
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
async publish(channel, event, payload) {
|
|
3045
|
+
if (!this.socket?.connected) {
|
|
3046
|
+
throw new Error(
|
|
3047
|
+
"Not connected to realtime server. Call connect() first."
|
|
3048
|
+
);
|
|
3049
|
+
}
|
|
3050
|
+
this.socket.emit("realtime:publish", { channel, event, payload });
|
|
3051
|
+
}
|
|
3052
|
+
on(event, callback) {
|
|
3053
|
+
const listeners = this.eventListeners.get(event) ?? /* @__PURE__ */ new Set();
|
|
3054
|
+
listeners.add(callback);
|
|
3055
|
+
this.eventListeners.set(event, listeners);
|
|
3056
|
+
}
|
|
3057
|
+
off(event, callback) {
|
|
3058
|
+
const listeners = this.eventListeners.get(event);
|
|
3059
|
+
listeners?.delete(callback);
|
|
3060
|
+
if (listeners?.size === 0) {
|
|
3061
|
+
this.eventListeners.delete(event);
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
once(event, callback) {
|
|
3065
|
+
const wrapper = (payload) => {
|
|
3066
|
+
this.off(event, wrapper);
|
|
3067
|
+
callback(payload);
|
|
3068
|
+
};
|
|
3069
|
+
this.on(event, wrapper);
|
|
3070
|
+
}
|
|
3071
|
+
getSubscribedChannels() {
|
|
3072
|
+
return [...this.subscriptions.values()].filter((subscription) => subscription.status === "subscribed").map((subscription) => subscription.channel);
|
|
3073
|
+
}
|
|
3074
|
+
getPresenceState(channel) {
|
|
3075
|
+
return [...this.subscriptions.get(channel)?.members.values() ?? []];
|
|
3076
|
+
}
|
|
3077
|
+
};
|
|
3078
|
+
|
|
3079
|
+
// src/modules/storage.ts
|
|
3080
|
+
function generateObjectKey(filename) {
|
|
3081
|
+
const dotIndex = filename.lastIndexOf(".");
|
|
3082
|
+
const hasExt = dotIndex > 0;
|
|
3083
|
+
const ext = hasExt ? filename.slice(dotIndex) : "";
|
|
3084
|
+
const base = hasExt ? filename.slice(0, dotIndex) : filename;
|
|
3085
|
+
const sanitizedBase = base.replace(/[^a-zA-Z0-9-_]/g, "-").slice(0, 32) || "file";
|
|
3086
|
+
const timestamp = Date.now();
|
|
3087
|
+
const random = Math.random().toString(36).slice(2, 8);
|
|
3088
|
+
return `${sanitizedBase}-${timestamp}-${random}${ext}`;
|
|
3089
|
+
}
|
|
3090
|
+
var StorageBucket = class {
|
|
3091
|
+
constructor(bucketName, http) {
|
|
3092
|
+
this.bucketName = bucketName;
|
|
3093
|
+
this.http = http;
|
|
3094
|
+
}
|
|
3095
|
+
bucketName;
|
|
3096
|
+
http;
|
|
3097
|
+
/**
|
|
3098
|
+
* Upload a file to a specific key.
|
|
3099
|
+
* Uses the upload strategy from the backend (direct or presigned).
|
|
3100
|
+
* Standard PUT semantics: uploading to an existing key replaces the
|
|
3101
|
+
* current object in place.
|
|
3102
|
+
* @param path - The object key/path
|
|
3103
|
+
* @param file - File or Blob to upload
|
|
3104
|
+
*/
|
|
3105
|
+
async upload(path, file) {
|
|
3106
|
+
try {
|
|
3107
|
+
const strategyResponse = await this.http.post(
|
|
3108
|
+
`/api/storage/buckets/${this.bucketName}/upload-strategy`,
|
|
3109
|
+
{
|
|
3110
|
+
filename: path,
|
|
3111
|
+
contentType: file.type || "application/octet-stream",
|
|
3112
|
+
size: file.size
|
|
2570
3113
|
}
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
3114
|
+
);
|
|
3115
|
+
if (strategyResponse.method === "presigned") {
|
|
3116
|
+
return await this.uploadWithPresignedUrl(strategyResponse, file);
|
|
3117
|
+
}
|
|
3118
|
+
if (strategyResponse.method === "direct") {
|
|
3119
|
+
const formData = new FormData();
|
|
3120
|
+
formData.append("file", file);
|
|
3121
|
+
const response = await this.http.request(
|
|
3122
|
+
"PUT",
|
|
3123
|
+
`/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(path)}`,
|
|
3124
|
+
{
|
|
3125
|
+
body: formData,
|
|
3126
|
+
headers: {
|
|
3127
|
+
// Don't set Content-Type, let browser set multipart boundary
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
);
|
|
3131
|
+
return { data: response, error: null };
|
|
3132
|
+
}
|
|
3133
|
+
throw new ResultError(
|
|
3134
|
+
`Unsupported upload method: ${strategyResponse.method}`,
|
|
3135
|
+
500,
|
|
3136
|
+
"STORAGE_ERROR"
|
|
3137
|
+
);
|
|
3138
|
+
} catch (error) {
|
|
3139
|
+
return {
|
|
3140
|
+
data: null,
|
|
3141
|
+
error: error instanceof ResultError ? error : new ResultError("Upload failed", 500, "STORAGE_ERROR")
|
|
3142
|
+
};
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
/**
|
|
3146
|
+
* Upload a file under an automatically generated, collision-free key.
|
|
3147
|
+
* The key is derived client-side from the filename (sanitized base +
|
|
3148
|
+
* timestamp + random suffix) and uploaded through the standard
|
|
3149
|
+
* {@link upload} path, so repeated uploads of the same file never
|
|
3150
|
+
* overwrite each other. Reads the filename structurally to avoid assuming
|
|
3151
|
+
* a global `File` (which Node 18 does not expose).
|
|
3152
|
+
* @param file - File or Blob to upload
|
|
3153
|
+
*/
|
|
3154
|
+
async uploadAuto(file) {
|
|
3155
|
+
const filename = "name" in file && typeof file.name === "string" ? file.name : "file";
|
|
3156
|
+
return this.upload(generateObjectKey(filename), file);
|
|
3157
|
+
}
|
|
3158
|
+
/**
|
|
3159
|
+
* Internal method to handle presigned URL uploads
|
|
3160
|
+
*/
|
|
3161
|
+
async uploadWithPresignedUrl(strategy, file) {
|
|
3162
|
+
try {
|
|
3163
|
+
const formData = new FormData();
|
|
3164
|
+
if (strategy.fields) {
|
|
3165
|
+
Object.entries(strategy.fields).forEach(([key, value]) => {
|
|
3166
|
+
formData.append(key, value);
|
|
3167
|
+
});
|
|
3168
|
+
}
|
|
3169
|
+
formData.append("file", file);
|
|
3170
|
+
const uploadResponse = await fetch(strategy.uploadUrl, {
|
|
3171
|
+
method: "POST",
|
|
3172
|
+
body: formData
|
|
3173
|
+
});
|
|
3174
|
+
if (!uploadResponse.ok) {
|
|
3175
|
+
throw new ResultError(
|
|
3176
|
+
`Upload to storage failed: ${uploadResponse.statusText}`,
|
|
3177
|
+
uploadResponse.status,
|
|
3178
|
+
"STORAGE_ERROR"
|
|
3179
|
+
);
|
|
3180
|
+
}
|
|
3181
|
+
if (strategy.confirmRequired && strategy.confirmUrl) {
|
|
3182
|
+
const confirmResponse = await this.http.post(
|
|
3183
|
+
strategy.confirmUrl,
|
|
3184
|
+
{
|
|
3185
|
+
size: file.size,
|
|
3186
|
+
contentType: file.type || "application/octet-stream"
|
|
3187
|
+
}
|
|
3188
|
+
);
|
|
3189
|
+
return { data: confirmResponse, error: null };
|
|
3190
|
+
}
|
|
3191
|
+
return {
|
|
3192
|
+
data: {
|
|
3193
|
+
key: strategy.key,
|
|
3194
|
+
bucket: this.bucketName,
|
|
3195
|
+
size: file.size,
|
|
3196
|
+
mimeType: file.type || "application/octet-stream",
|
|
3197
|
+
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3198
|
+
url: this.getPublicUrl(strategy.key).data?.publicUrl
|
|
3199
|
+
},
|
|
3200
|
+
error: null
|
|
3201
|
+
};
|
|
3202
|
+
} catch (error) {
|
|
3203
|
+
throw error instanceof ResultError ? error : new ResultError("Presigned upload failed", 500, "STORAGE_ERROR");
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
/**
|
|
3207
|
+
* Download a file
|
|
3208
|
+
* Uses the download strategy from backend (direct or presigned)
|
|
3209
|
+
* @param path - The object key/path
|
|
3210
|
+
* Returns the file as a Blob
|
|
3211
|
+
*/
|
|
3212
|
+
async download(path) {
|
|
3213
|
+
try {
|
|
3214
|
+
const encodedKey = encodeURIComponent(path);
|
|
3215
|
+
let strategyResponse;
|
|
3216
|
+
try {
|
|
3217
|
+
strategyResponse = await this.http.get(
|
|
3218
|
+
`/api/storage/buckets/${this.bucketName}/download-strategy/objects/${encodedKey}`
|
|
3219
|
+
);
|
|
3220
|
+
} catch (err) {
|
|
3221
|
+
const status = err instanceof ResultError ? err.statusCode : void 0;
|
|
3222
|
+
if (status === 404 || status === 405) {
|
|
3223
|
+
strategyResponse = await this.http.post(
|
|
3224
|
+
`/api/storage/buckets/${this.bucketName}/objects/${encodedKey}/download-strategy`,
|
|
3225
|
+
{}
|
|
2575
3226
|
);
|
|
2576
3227
|
} else {
|
|
2577
|
-
|
|
2578
|
-
subscription.members.clear();
|
|
3228
|
+
throw err;
|
|
2579
3229
|
}
|
|
2580
|
-
this.settleSubscription(subscription, response, false);
|
|
2581
|
-
});
|
|
2582
|
-
});
|
|
2583
|
-
return subscription.pending;
|
|
2584
|
-
}
|
|
2585
|
-
settleSubscription(subscription, response, incrementEpoch) {
|
|
2586
|
-
if (incrementEpoch) {
|
|
2587
|
-
subscription.epoch++;
|
|
2588
|
-
}
|
|
2589
|
-
subscription.settlePending?.(response);
|
|
2590
|
-
}
|
|
2591
|
-
applyPresenceEvent(event, message) {
|
|
2592
|
-
if (event !== "presence:join" && event !== "presence:leave") {
|
|
2593
|
-
return;
|
|
2594
|
-
}
|
|
2595
|
-
const presenceEvent = message;
|
|
2596
|
-
const channel = presenceEvent.meta?.channel;
|
|
2597
|
-
const member = presenceEvent.member;
|
|
2598
|
-
if (!channel || !member) {
|
|
2599
|
-
return;
|
|
2600
|
-
}
|
|
2601
|
-
const subscription = this.subscriptions.get(channel);
|
|
2602
|
-
if (!subscription) {
|
|
2603
|
-
return;
|
|
2604
|
-
}
|
|
2605
|
-
if (event === "presence:join") {
|
|
2606
|
-
subscription.members.set(member.presenceId, member);
|
|
2607
|
-
} else {
|
|
2608
|
-
subscription.members.delete(member.presenceId);
|
|
2609
|
-
}
|
|
2610
|
-
}
|
|
2611
|
-
get isConnected() {
|
|
2612
|
-
return this.socket?.connected ?? false;
|
|
2613
|
-
}
|
|
2614
|
-
get connectionState() {
|
|
2615
|
-
if (!this.socket) {
|
|
2616
|
-
return "disconnected";
|
|
2617
|
-
}
|
|
2618
|
-
return this.socket.connected ? "connected" : "connecting";
|
|
2619
|
-
}
|
|
2620
|
-
get socketId() {
|
|
2621
|
-
return this.socket?.id;
|
|
2622
|
-
}
|
|
2623
|
-
async subscribe(channel) {
|
|
2624
|
-
let subscription = this.subscriptions.get(channel);
|
|
2625
|
-
if (subscription) {
|
|
2626
|
-
if (subscription.pending) {
|
|
2627
|
-
return subscription.pending;
|
|
2628
3230
|
}
|
|
2629
|
-
|
|
2630
|
-
|
|
3231
|
+
const downloadUrl = strategyResponse.url;
|
|
3232
|
+
const headers = {};
|
|
3233
|
+
if (strategyResponse.method === "direct") {
|
|
3234
|
+
Object.assign(headers, this.http.getHeaders());
|
|
2631
3235
|
}
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
3236
|
+
const response = await fetch(downloadUrl, {
|
|
3237
|
+
method: "GET",
|
|
3238
|
+
headers
|
|
3239
|
+
});
|
|
3240
|
+
if (!response.ok) {
|
|
3241
|
+
try {
|
|
3242
|
+
const error = await response.json();
|
|
3243
|
+
throw ResultError.fromApiError(error);
|
|
3244
|
+
} catch {
|
|
3245
|
+
throw new ResultError(
|
|
3246
|
+
`Download failed: ${response.statusText}`,
|
|
3247
|
+
response.status,
|
|
3248
|
+
"STORAGE_ERROR"
|
|
3249
|
+
);
|
|
2642
3250
|
}
|
|
2643
|
-
const message = error instanceof Error ? error.message : "Connection failed";
|
|
2644
|
-
return { ok: false, channel, error: { code: "CONNECTION_FAILED", message } };
|
|
2645
3251
|
}
|
|
3252
|
+
const blob = await response.blob();
|
|
3253
|
+
return { data: blob, error: null };
|
|
3254
|
+
} catch (error) {
|
|
3255
|
+
return {
|
|
3256
|
+
data: null,
|
|
3257
|
+
error: error instanceof ResultError ? error : new ResultError("Download failed", 500, "STORAGE_ERROR")
|
|
3258
|
+
};
|
|
2646
3259
|
}
|
|
2647
|
-
return subscription.pending ?? this.requestSubscription(channel, subscription);
|
|
2648
3260
|
}
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
true
|
|
2663
|
-
);
|
|
2664
|
-
if (this.socket?.connected) {
|
|
2665
|
-
this.socket.emit("realtime:unsubscribe", { channel });
|
|
2666
|
-
}
|
|
3261
|
+
/**
|
|
3262
|
+
* Get the public URL for an object in a public bucket.
|
|
3263
|
+
*
|
|
3264
|
+
* Pure string construction — no network call, no auth. The URL only resolves
|
|
3265
|
+
* if the bucket is public; for private objects use {@link createSignedUrl}.
|
|
3266
|
+
*
|
|
3267
|
+
* @param path - The object key/path
|
|
3268
|
+
* @returns `{ data: { publicUrl }, error }` — matches the external SDK pattern,
|
|
3269
|
+
* so `const { data } = getPublicUrl(path)` then `data.publicUrl`.
|
|
3270
|
+
*/
|
|
3271
|
+
getPublicUrl(path) {
|
|
3272
|
+
const publicUrl = `${this.http.baseUrl}/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(path)}`;
|
|
3273
|
+
return { data: { publicUrl }, error: null };
|
|
2667
3274
|
}
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
3275
|
+
/**
|
|
3276
|
+
* Resolve a download strategy (signed or direct URL) for an object with a
|
|
3277
|
+
* caller-supplied TTL. Prefers the canonical GET route and falls back to the
|
|
3278
|
+
* legacy POST alias so signed-URL creation still works against older backends
|
|
3279
|
+
* that predate the GET route (they return 404/405 for it). A genuine
|
|
3280
|
+
* "object not found" (STORAGE_NOT_FOUND) is not retried.
|
|
3281
|
+
*/
|
|
3282
|
+
async requestDownloadStrategy(path, expiresIn) {
|
|
3283
|
+
const encoded = encodeURIComponent(path);
|
|
3284
|
+
try {
|
|
3285
|
+
return await this.http.get(
|
|
3286
|
+
`/api/storage/buckets/${this.bucketName}/download-strategy/objects/${encoded}`,
|
|
3287
|
+
{ params: { expiresIn: expiresIn.toString() } }
|
|
3288
|
+
);
|
|
3289
|
+
} catch (error) {
|
|
3290
|
+
const status = error instanceof ResultError ? error.statusCode : void 0;
|
|
3291
|
+
const isMissingRoute = (status === 404 || status === 405) && !(error instanceof ResultError && error.error === "STORAGE_NOT_FOUND");
|
|
3292
|
+
if (!isMissingRoute) {
|
|
3293
|
+
throw error;
|
|
3294
|
+
}
|
|
3295
|
+
return await this.http.post(
|
|
3296
|
+
`/api/storage/buckets/${this.bucketName}/objects/${encoded}/download-strategy`,
|
|
3297
|
+
{ expiresIn }
|
|
3298
|
+
);
|
|
2671
3299
|
}
|
|
2672
|
-
this.socket.emit("realtime:publish", { channel, event, payload });
|
|
2673
|
-
}
|
|
2674
|
-
on(event, callback) {
|
|
2675
|
-
const listeners = this.eventListeners.get(event) ?? /* @__PURE__ */ new Set();
|
|
2676
|
-
listeners.add(callback);
|
|
2677
|
-
this.eventListeners.set(event, listeners);
|
|
2678
3300
|
}
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
3301
|
+
/**
|
|
3302
|
+
* Create a signed URL for an object.
|
|
3303
|
+
*
|
|
3304
|
+
* Returns a time-limited, credential-free URL that can be handed directly to
|
|
3305
|
+
* a browser (`<img src>`), an email, or a third party — no SDK or session is
|
|
3306
|
+
* needed to fetch it. Authorization is enforced when the URL is minted (the
|
|
3307
|
+
* caller must be allowed to read the object), so the resulting link is a
|
|
3308
|
+
* pre-authorized capability scoped to this one object until it expires.
|
|
3309
|
+
*
|
|
3310
|
+
* @param path - The object key/path
|
|
3311
|
+
* @param expiresIn - Lifetime in seconds (default 3600 = 1h, max 604800 = 7d).
|
|
3312
|
+
* Honored for private buckets; public buckets return their long-lived URL.
|
|
3313
|
+
*/
|
|
3314
|
+
async createSignedUrl(path, expiresIn = 3600) {
|
|
3315
|
+
try {
|
|
3316
|
+
const strategy = await this.requestDownloadStrategy(path, expiresIn);
|
|
3317
|
+
return {
|
|
3318
|
+
data: {
|
|
3319
|
+
signedUrl: strategy.url,
|
|
3320
|
+
expiresAt: strategy.expiresAt ? new Date(strategy.expiresAt).toISOString() : null
|
|
3321
|
+
},
|
|
3322
|
+
error: null
|
|
3323
|
+
};
|
|
3324
|
+
} catch (error) {
|
|
3325
|
+
return {
|
|
3326
|
+
data: null,
|
|
3327
|
+
error: error instanceof ResultError ? error : new ResultError(
|
|
3328
|
+
"Failed to create signed URL",
|
|
3329
|
+
500,
|
|
3330
|
+
"STORAGE_ERROR"
|
|
3331
|
+
)
|
|
3332
|
+
};
|
|
2684
3333
|
}
|
|
2685
3334
|
}
|
|
2686
|
-
once(event, callback) {
|
|
2687
|
-
const wrapper = (payload) => {
|
|
2688
|
-
this.off(event, wrapper);
|
|
2689
|
-
callback(payload);
|
|
2690
|
-
};
|
|
2691
|
-
this.on(event, wrapper);
|
|
2692
|
-
}
|
|
2693
|
-
getSubscribedChannels() {
|
|
2694
|
-
return [...this.subscriptions.values()].filter((subscription) => subscription.status === "subscribed").map((subscription) => subscription.channel);
|
|
2695
|
-
}
|
|
2696
|
-
getPresenceState(channel) {
|
|
2697
|
-
return [...this.subscriptions.get(channel)?.members.values() ?? []];
|
|
2698
|
-
}
|
|
2699
|
-
};
|
|
2700
|
-
var Emails = class {
|
|
2701
|
-
constructor(http) {
|
|
2702
|
-
this.http = http;
|
|
2703
|
-
}
|
|
2704
3335
|
/**
|
|
2705
|
-
*
|
|
2706
|
-
*
|
|
3336
|
+
* Create signed URLs for multiple objects in a single call.
|
|
3337
|
+
*
|
|
3338
|
+
* Each entry resolves independently: a failure on one key (not found / not
|
|
3339
|
+
* permitted) is reported on that entry's `error` without failing the rest.
|
|
3340
|
+
*
|
|
3341
|
+
* @param paths - The object keys/paths
|
|
3342
|
+
* @param expiresIn - Lifetime in seconds (default 3600 = 1h, max 604800 = 7d)
|
|
2707
3343
|
*/
|
|
2708
|
-
async
|
|
3344
|
+
async createSignedUrls(paths, expiresIn = 3600) {
|
|
2709
3345
|
try {
|
|
2710
|
-
const data = await
|
|
3346
|
+
const data = await Promise.all(
|
|
3347
|
+
paths.map(async (path) => {
|
|
3348
|
+
const { data: signed, error } = await this.createSignedUrl(
|
|
3349
|
+
path,
|
|
3350
|
+
expiresIn
|
|
3351
|
+
);
|
|
3352
|
+
return {
|
|
3353
|
+
path,
|
|
3354
|
+
signedUrl: signed?.signedUrl ?? null,
|
|
3355
|
+
error: error ? error.message : null
|
|
3356
|
+
};
|
|
3357
|
+
})
|
|
3358
|
+
);
|
|
2711
3359
|
return { data, error: null };
|
|
2712
3360
|
} catch (error) {
|
|
2713
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
2714
|
-
throw error;
|
|
2715
|
-
}
|
|
2716
3361
|
return {
|
|
2717
3362
|
data: null,
|
|
2718
|
-
error: error instanceof
|
|
2719
|
-
|
|
3363
|
+
error: error instanceof ResultError ? error : new ResultError(
|
|
3364
|
+
"Failed to create signed URLs",
|
|
2720
3365
|
500,
|
|
2721
|
-
"
|
|
3366
|
+
"STORAGE_ERROR"
|
|
2722
3367
|
)
|
|
2723
3368
|
};
|
|
2724
3369
|
}
|
|
2725
3370
|
}
|
|
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
3371
|
/**
|
|
2735
|
-
*
|
|
2736
|
-
*
|
|
2737
|
-
* @
|
|
2738
|
-
*
|
|
2739
|
-
*
|
|
2740
|
-
* mode: 'payment',
|
|
2741
|
-
* lineItems: [{ priceId: 'price_123', quantity: 1 }],
|
|
2742
|
-
* successUrl: `${window.location.origin}/success`,
|
|
2743
|
-
* cancelUrl: `${window.location.origin}/pricing`
|
|
2744
|
-
* });
|
|
2745
|
-
*
|
|
2746
|
-
* if (!error && data.checkoutSession.url) {
|
|
2747
|
-
* window.location.assign(data.checkoutSession.url);
|
|
2748
|
-
* }
|
|
2749
|
-
* ```
|
|
3372
|
+
* List objects in the bucket
|
|
3373
|
+
* @param prefix - Filter by key prefix
|
|
3374
|
+
* @param search - Search in file names
|
|
3375
|
+
* @param limit - Maximum number of results (default: 100, max: 1000)
|
|
3376
|
+
* @param offset - Number of results to skip
|
|
2750
3377
|
*/
|
|
2751
|
-
async
|
|
3378
|
+
async list(options) {
|
|
2752
3379
|
try {
|
|
2753
|
-
const
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
3380
|
+
const params = {};
|
|
3381
|
+
if (options?.prefix) {
|
|
3382
|
+
params.prefix = options.prefix;
|
|
3383
|
+
}
|
|
3384
|
+
if (options?.search) {
|
|
3385
|
+
params.search = options.search;
|
|
3386
|
+
}
|
|
3387
|
+
if (options?.limit) {
|
|
3388
|
+
params.limit = options.limit.toString();
|
|
3389
|
+
}
|
|
3390
|
+
if (options?.offset) {
|
|
3391
|
+
params.offset = options.offset.toString();
|
|
3392
|
+
}
|
|
3393
|
+
const response = await this.http.get(
|
|
3394
|
+
`/api/storage/buckets/${this.bucketName}/objects`,
|
|
3395
|
+
{ params }
|
|
2757
3396
|
);
|
|
2758
|
-
return { data, error: null };
|
|
3397
|
+
return { data: response, error: null };
|
|
2759
3398
|
} catch (error) {
|
|
2760
|
-
return
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
3399
|
+
return {
|
|
3400
|
+
data: null,
|
|
3401
|
+
error: error instanceof ResultError ? error : new ResultError("List failed", 500, "STORAGE_ERROR")
|
|
3402
|
+
};
|
|
2764
3403
|
}
|
|
2765
3404
|
}
|
|
2766
3405
|
/**
|
|
2767
|
-
*
|
|
3406
|
+
* Delete one or more files.
|
|
3407
|
+
*
|
|
3408
|
+
* A string uses the single-object endpoint. An array uses the batch endpoint,
|
|
3409
|
+
* which accepts at most 1000 keys and returns one result per key.
|
|
3410
|
+
*
|
|
3411
|
+
* @param pathOrPaths - One object key/path or an array of object keys/paths
|
|
2768
3412
|
*/
|
|
2769
|
-
async
|
|
3413
|
+
async remove(pathOrPaths) {
|
|
2770
3414
|
try {
|
|
2771
|
-
const
|
|
2772
|
-
|
|
2773
|
-
|
|
3415
|
+
const response = Array.isArray(pathOrPaths) ? await this.http.delete(
|
|
3416
|
+
`/api/storage/buckets/${this.bucketName}/objects`,
|
|
3417
|
+
{ body: { keys: pathOrPaths } }
|
|
3418
|
+
) : await this.http.delete(
|
|
3419
|
+
`/api/storage/buckets/${this.bucketName}/objects/${encodeURIComponent(pathOrPaths)}`
|
|
2774
3420
|
);
|
|
2775
|
-
return { data, error: null };
|
|
3421
|
+
return { data: response, error: null };
|
|
2776
3422
|
} catch (error) {
|
|
2777
|
-
return
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
3423
|
+
return {
|
|
3424
|
+
data: null,
|
|
3425
|
+
error: error instanceof ResultError ? error : new ResultError("Delete failed", 500, "STORAGE_ERROR")
|
|
3426
|
+
};
|
|
2781
3427
|
}
|
|
2782
3428
|
}
|
|
2783
3429
|
};
|
|
2784
|
-
var
|
|
3430
|
+
var Storage = class {
|
|
2785
3431
|
constructor(http) {
|
|
2786
3432
|
this.http = http;
|
|
2787
3433
|
}
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
} catch (error) {
|
|
2796
|
-
return wrapError(error, "Razorpay order creation failed");
|
|
2797
|
-
}
|
|
3434
|
+
http;
|
|
3435
|
+
/**
|
|
3436
|
+
* Get a bucket instance for operations
|
|
3437
|
+
* @param bucketName - Name of the bucket
|
|
3438
|
+
*/
|
|
3439
|
+
from(bucketName) {
|
|
3440
|
+
return new StorageBucket(bucketName, this.http);
|
|
2798
3441
|
}
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
3442
|
+
};
|
|
3443
|
+
|
|
3444
|
+
// src/modules/support.ts
|
|
3445
|
+
var DEFAULT_ENDPOINT2 = "https://beta.result.dev";
|
|
3446
|
+
var Support = class {
|
|
3447
|
+
handle;
|
|
3448
|
+
endpoint;
|
|
3449
|
+
// Fallback for non-browser runtimes and private mode.
|
|
3450
|
+
mem = { threads: [] };
|
|
3451
|
+
constructor(options) {
|
|
3452
|
+
this.handle = options?.handle ?? "";
|
|
3453
|
+
this.endpoint = (options?.endpoint || DEFAULT_ENDPOINT2).replace(/\/$/, "");
|
|
2809
3454
|
}
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
}
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
3455
|
+
/**
|
|
3456
|
+
* Start a new support conversation. The returned access key is stored
|
|
3457
|
+
* locally so `getThread`/`sendMessage` work without passing it around.
|
|
3458
|
+
*/
|
|
3459
|
+
async startThread(options) {
|
|
3460
|
+
const guard = this.requireHandle();
|
|
3461
|
+
if (guard) return { data: null, error: guard };
|
|
3462
|
+
const { data, error } = await this.request(
|
|
3463
|
+
"POST",
|
|
3464
|
+
"/api/support/threads",
|
|
3465
|
+
{
|
|
3466
|
+
username: this.handle,
|
|
3467
|
+
email: options.email,
|
|
3468
|
+
name: options.name,
|
|
3469
|
+
message: options.message
|
|
3470
|
+
}
|
|
3471
|
+
);
|
|
3472
|
+
if (data) {
|
|
3473
|
+
this.remember(data.id, data.accessKey, options);
|
|
3474
|
+
}
|
|
3475
|
+
return { data, error };
|
|
3476
|
+
}
|
|
3477
|
+
/** Fetch a thread (status + full message history) with its stored access key. */
|
|
3478
|
+
async getThread(threadId, accessKey) {
|
|
3479
|
+
const guard = this.requireHandle();
|
|
3480
|
+
if (guard) return { data: null, error: guard };
|
|
3481
|
+
const key = accessKey ?? this.keyFor(threadId);
|
|
3482
|
+
if (!key) {
|
|
3483
|
+
return { data: null, error: this.missingKey(threadId) };
|
|
3484
|
+
}
|
|
3485
|
+
const { data, error } = await this.request(
|
|
3486
|
+
"GET",
|
|
3487
|
+
`/api/support/threads/${encodeURIComponent(threadId)}?key=${encodeURIComponent(key)}`
|
|
3488
|
+
);
|
|
3489
|
+
return { data: data?.thread ?? null, error };
|
|
3490
|
+
}
|
|
3491
|
+
/** Send a visitor reply on an existing thread (reopens it if closed). */
|
|
3492
|
+
async sendMessage(threadId, message, accessKey) {
|
|
3493
|
+
const guard = this.requireHandle();
|
|
3494
|
+
if (guard) return { data: null, error: guard };
|
|
3495
|
+
const key = accessKey ?? this.keyFor(threadId);
|
|
3496
|
+
if (!key) {
|
|
3497
|
+
return { data: null, error: this.missingKey(threadId) };
|
|
3498
|
+
}
|
|
3499
|
+
const { data, error } = await this.request(
|
|
3500
|
+
"POST",
|
|
3501
|
+
`/api/support/threads/${encodeURIComponent(threadId)}/messages`,
|
|
3502
|
+
{ message, key }
|
|
3503
|
+
);
|
|
3504
|
+
return { data, error };
|
|
3505
|
+
}
|
|
3506
|
+
/** Threads started from this browser (ids only; fetch each with getThread). */
|
|
3507
|
+
listThreads() {
|
|
3508
|
+
return this.load().threads.map(({ id }) => ({ id }));
|
|
3509
|
+
}
|
|
3510
|
+
/** The contact details used when this browser last started a thread. */
|
|
3511
|
+
getContact() {
|
|
3512
|
+
return this.load().contact;
|
|
3513
|
+
}
|
|
3514
|
+
// -- internals -------------------------------------------------------------
|
|
3515
|
+
requireHandle() {
|
|
3516
|
+
if (this.handle) return null;
|
|
3517
|
+
return new ResultError(
|
|
3518
|
+
"Missing support handle. Pass support: { handle } to createClient().",
|
|
3519
|
+
400,
|
|
3520
|
+
"MISSING_FIELD"
|
|
3521
|
+
);
|
|
2823
3522
|
}
|
|
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
|
-
}
|
|
3523
|
+
missingKey(threadId) {
|
|
3524
|
+
return new ResultError(
|
|
3525
|
+
`No access key stored for thread ${threadId}. Pass the accessKey returned by startThread().`,
|
|
3526
|
+
401,
|
|
3527
|
+
"AUTH_UNAUTHORIZED"
|
|
3528
|
+
);
|
|
2837
3529
|
}
|
|
2838
|
-
async
|
|
3530
|
+
async request(method, path, body) {
|
|
2839
3531
|
try {
|
|
2840
|
-
const
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
)
|
|
2844
|
-
|
|
2845
|
-
);
|
|
2846
|
-
|
|
3532
|
+
const response = await fetch(`${this.endpoint}${path}`, {
|
|
3533
|
+
method,
|
|
3534
|
+
headers: body ? { "Content-Type": "application/json" } : void 0,
|
|
3535
|
+
body: body ? JSON.stringify(body) : void 0
|
|
3536
|
+
});
|
|
3537
|
+
const payload = await response.json().catch(() => null);
|
|
3538
|
+
if (!response.ok) {
|
|
3539
|
+
return {
|
|
3540
|
+
data: null,
|
|
3541
|
+
error: new ResultError(
|
|
3542
|
+
payload?.error?.message ?? `Support request failed (${response.status})`,
|
|
3543
|
+
response.status,
|
|
3544
|
+
payload?.error?.code ?? "UNKNOWN_ERROR"
|
|
3545
|
+
)
|
|
3546
|
+
};
|
|
3547
|
+
}
|
|
3548
|
+
return { data: payload, error: null };
|
|
2847
3549
|
} catch (error) {
|
|
2848
|
-
|
|
2849
|
-
error
|
|
2850
|
-
|
|
2851
|
-
|
|
3550
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
3551
|
+
throw error;
|
|
3552
|
+
}
|
|
3553
|
+
return {
|
|
3554
|
+
data: null,
|
|
3555
|
+
error: new ResultError(
|
|
3556
|
+
error instanceof Error ? error.message : "Support request failed",
|
|
3557
|
+
500,
|
|
3558
|
+
"UNKNOWN_ERROR"
|
|
3559
|
+
)
|
|
3560
|
+
};
|
|
2852
3561
|
}
|
|
2853
3562
|
}
|
|
2854
|
-
|
|
3563
|
+
storageKey() {
|
|
3564
|
+
return `rsup_${this.handle}`;
|
|
3565
|
+
}
|
|
3566
|
+
load() {
|
|
2855
3567
|
try {
|
|
2856
|
-
const
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
)
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
} catch
|
|
2864
|
-
return wrapError(
|
|
2865
|
-
error,
|
|
2866
|
-
"Razorpay subscription pause failed"
|
|
2867
|
-
);
|
|
3568
|
+
const raw = localStorage.getItem(this.storageKey());
|
|
3569
|
+
if (raw) {
|
|
3570
|
+
const parsed = JSON.parse(raw);
|
|
3571
|
+
if (parsed && Array.isArray(parsed.threads)) {
|
|
3572
|
+
return parsed;
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
} catch {
|
|
2868
3576
|
}
|
|
3577
|
+
return this.mem;
|
|
2869
3578
|
}
|
|
2870
|
-
|
|
3579
|
+
save(state) {
|
|
3580
|
+
this.mem = state;
|
|
2871
3581
|
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
|
-
);
|
|
3582
|
+
localStorage.setItem(this.storageKey(), JSON.stringify(state));
|
|
3583
|
+
} catch {
|
|
2884
3584
|
}
|
|
2885
3585
|
}
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
3586
|
+
keyFor(threadId) {
|
|
3587
|
+
return this.load().threads.find((thread) => thread.id === threadId)?.key;
|
|
3588
|
+
}
|
|
3589
|
+
remember(id, key, options) {
|
|
3590
|
+
const state = this.load();
|
|
3591
|
+
const threads = state.threads.filter((thread) => thread.id !== id);
|
|
3592
|
+
threads.unshift({ id, key });
|
|
3593
|
+
this.save({
|
|
3594
|
+
contact: {
|
|
3595
|
+
email: options.email,
|
|
3596
|
+
name: options.name ?? state.contact?.name
|
|
3597
|
+
},
|
|
3598
|
+
threads
|
|
3599
|
+
});
|
|
2891
3600
|
}
|
|
2892
3601
|
};
|
|
2893
|
-
|
|
3602
|
+
|
|
3603
|
+
// src/client.ts
|
|
3604
|
+
var ResultClient = class {
|
|
3605
|
+
http;
|
|
3606
|
+
tokenManager;
|
|
3607
|
+
auth;
|
|
3608
|
+
database;
|
|
3609
|
+
storage;
|
|
3610
|
+
ai;
|
|
3611
|
+
functions;
|
|
3612
|
+
realtime;
|
|
3613
|
+
emails;
|
|
3614
|
+
analytics;
|
|
3615
|
+
support;
|
|
2894
3616
|
constructor(config = {}) {
|
|
2895
3617
|
const logger = new Logger(config.debug);
|
|
2896
3618
|
this.tokenManager = new TokenManager();
|
|
@@ -2915,7 +3637,8 @@ var InsForgeClient = class {
|
|
|
2915
3637
|
() => this.http.getValidAccessToken()
|
|
2916
3638
|
);
|
|
2917
3639
|
this.emails = new Emails(this.http);
|
|
2918
|
-
this.
|
|
3640
|
+
this.analytics = new Analytics(config.analytics);
|
|
3641
|
+
this.support = new Support(config.support);
|
|
2919
3642
|
}
|
|
2920
3643
|
/**
|
|
2921
3644
|
* Get the underlying HTTP client for custom requests
|
|
@@ -2939,16 +3662,16 @@ var InsForgeClient = class {
|
|
|
2939
3662
|
*
|
|
2940
3663
|
* Use this when an external auth provider (Better Auth, Clerk, Auth0,
|
|
2941
3664
|
* WorkOS, Kinde, Stytch, …) issues the JWT and you need to keep the
|
|
2942
|
-
* long-lived
|
|
3665
|
+
* long-lived Result client in sync. Without this, you'd have to call
|
|
2943
3666
|
* `client.getHttpClient().setAuthToken(token)` AND reach into the private
|
|
2944
3667
|
* realtime token manager separately.
|
|
2945
3668
|
*
|
|
2946
3669
|
* @example
|
|
2947
3670
|
* ```typescript
|
|
2948
|
-
* import { AuthChangeEvent } from '@
|
|
3671
|
+
* import { AuthChangeEvent } from '@resultdev/sdk';
|
|
2949
3672
|
*
|
|
2950
3673
|
* // Refresh a third-party-issued JWT periodically
|
|
2951
|
-
* const { token } = await fetch('/api/
|
|
3674
|
+
* const { token } = await fetch('/api/backend-token').then((r) => r.json());
|
|
2952
3675
|
* client.setAccessToken(token, AuthChangeEvent.TOKEN_REFRESHED);
|
|
2953
3676
|
*
|
|
2954
3677
|
* // Sign-out
|
|
@@ -2963,193 +3686,142 @@ var InsForgeClient = class {
|
|
|
2963
3686
|
this.tokenManager.setAccessToken(token, event);
|
|
2964
3687
|
}
|
|
2965
3688
|
}
|
|
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
3689
|
};
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
}
|
|
2984
|
-
function getCookieValue(cookies, name) {
|
|
2985
|
-
if (!cookies) {
|
|
2986
|
-
return null;
|
|
2987
|
-
}
|
|
2988
|
-
const value = cookies.get(name);
|
|
2989
|
-
if (typeof value === "string") {
|
|
2990
|
-
return value || null;
|
|
2991
|
-
}
|
|
2992
|
-
if (value && typeof value.value === "string") {
|
|
2993
|
-
return value.value || null;
|
|
2994
|
-
}
|
|
2995
|
-
return null;
|
|
2996
|
-
}
|
|
2997
|
-
function getCookieValueFromHeader(cookieHeader, name) {
|
|
2998
|
-
if (!cookieHeader) {
|
|
2999
|
-
return null;
|
|
3000
|
-
}
|
|
3001
|
-
const parts = cookieHeader.split(";");
|
|
3002
|
-
for (const part of parts) {
|
|
3003
|
-
const [rawName, ...rawValue] = part.trim().split("=");
|
|
3004
|
-
if (rawName !== name) {
|
|
3005
|
-
continue;
|
|
3006
|
-
}
|
|
3007
|
-
try {
|
|
3008
|
-
return decodeURIComponent(rawValue.join("="));
|
|
3009
|
-
} catch {
|
|
3010
|
-
return rawValue.join("=");
|
|
3011
|
-
}
|
|
3012
|
-
}
|
|
3013
|
-
return null;
|
|
3014
|
-
}
|
|
3015
|
-
function getBrowserCookie(name) {
|
|
3016
|
-
if (typeof document === "undefined") {
|
|
3017
|
-
return null;
|
|
3018
|
-
}
|
|
3019
|
-
return getCookieValueFromHeader(document.cookie, name);
|
|
3020
|
-
}
|
|
3021
|
-
function defaultCookieOptions() {
|
|
3022
|
-
const secure = typeof process !== "undefined" ? process.env.NODE_ENV === "production" : typeof location !== "undefined" && location.protocol === "https:";
|
|
3023
|
-
return {
|
|
3024
|
-
path: "/",
|
|
3025
|
-
sameSite: "lax",
|
|
3026
|
-
secure
|
|
3027
|
-
};
|
|
3028
|
-
}
|
|
3029
|
-
function accessTokenCookieOptions(token, overrides) {
|
|
3030
|
-
return {
|
|
3031
|
-
...defaultCookieOptions(),
|
|
3032
|
-
httpOnly: false,
|
|
3033
|
-
expires: getJwtExpiration(token) ?? void 0,
|
|
3034
|
-
...overrides
|
|
3035
|
-
};
|
|
3036
|
-
}
|
|
3037
|
-
function refreshTokenCookieOptions(token, overrides) {
|
|
3038
|
-
return {
|
|
3039
|
-
...defaultCookieOptions(),
|
|
3040
|
-
httpOnly: true,
|
|
3041
|
-
expires: getJwtExpiration(token) ?? void 0,
|
|
3042
|
-
...overrides
|
|
3043
|
-
};
|
|
3044
|
-
}
|
|
3045
|
-
function expiredCookieOptions(overrides) {
|
|
3046
|
-
const { expires: _expires, maxAge: _maxAge, ...safeOverrides } = overrides ?? {};
|
|
3047
|
-
return {
|
|
3048
|
-
...defaultCookieOptions(),
|
|
3049
|
-
...safeOverrides,
|
|
3050
|
-
expires: EXPIRED_DATE,
|
|
3051
|
-
maxAge: 0
|
|
3052
|
-
};
|
|
3053
|
-
}
|
|
3054
|
-
function setCookie(cookies, name, value, options) {
|
|
3055
|
-
if (!cookies?.set) {
|
|
3056
|
-
return;
|
|
3057
|
-
}
|
|
3058
|
-
cookies.set(name, value, options);
|
|
3059
|
-
}
|
|
3060
|
-
function deleteCookie(cookies, name, options) {
|
|
3061
|
-
if (!cookies) {
|
|
3062
|
-
return;
|
|
3690
|
+
|
|
3691
|
+
// src/ssr/server-client.ts
|
|
3692
|
+
function createServerClient(options = {}) {
|
|
3693
|
+
let { baseUrl, anonKey } = options;
|
|
3694
|
+
try {
|
|
3695
|
+
baseUrl ||= process.env.NEXT_PUBLIC_BACKEND_URL;
|
|
3696
|
+
anonKey ||= process.env.NEXT_PUBLIC_BACKEND_ANON_KEY;
|
|
3697
|
+
} catch {
|
|
3063
3698
|
}
|
|
3064
|
-
if (
|
|
3065
|
-
|
|
3066
|
-
|
|
3699
|
+
if (!baseUrl || !anonKey) {
|
|
3700
|
+
throw new Error(
|
|
3701
|
+
"Missing Result baseUrl or anonKey. Pass baseUrl and anonKey to createServerClient() or set NEXT_PUBLIC_BACKEND_URL and NEXT_PUBLIC_BACKEND_ANON_KEY."
|
|
3702
|
+
);
|
|
3067
3703
|
}
|
|
3068
|
-
cookies.
|
|
3704
|
+
const accessToken = options.accessToken ?? getCookieValue(options.cookies, getAccessTokenCookieName(options.names));
|
|
3705
|
+
return new ResultClient({
|
|
3706
|
+
...options,
|
|
3707
|
+
baseUrl,
|
|
3708
|
+
anonKey,
|
|
3709
|
+
isServerMode: true,
|
|
3710
|
+
accessToken: accessToken ?? void 0,
|
|
3711
|
+
// The cookie/option token is the only credential source here; shadow any
|
|
3712
|
+
// untyped edgeFunctionToken smuggled through the options spread.
|
|
3713
|
+
edgeFunctionToken: void 0
|
|
3714
|
+
});
|
|
3069
3715
|
}
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
if (options.domain) {
|
|
3076
|
-
parts.push(`Domain=${options.domain}`);
|
|
3077
|
-
}
|
|
3078
|
-
if (options.path) {
|
|
3079
|
-
parts.push(`Path=${options.path}`);
|
|
3080
|
-
}
|
|
3081
|
-
if (options.expires) {
|
|
3082
|
-
parts.push(`Expires=${options.expires.toUTCString()}`);
|
|
3083
|
-
}
|
|
3084
|
-
if (options.httpOnly) {
|
|
3085
|
-
parts.push("HttpOnly");
|
|
3086
|
-
}
|
|
3087
|
-
if (options.secure) {
|
|
3088
|
-
parts.push("Secure");
|
|
3089
|
-
}
|
|
3090
|
-
if (options.sameSite) {
|
|
3091
|
-
const sameSite = options.sameSite.charAt(0).toUpperCase() + options.sameSite.slice(1);
|
|
3092
|
-
parts.push(`SameSite=${sameSite}`);
|
|
3716
|
+
|
|
3717
|
+
// src/ssr/auth-actions.ts
|
|
3718
|
+
function persistSessionCookies(cookies, data, settings) {
|
|
3719
|
+
if (!data?.accessToken) {
|
|
3720
|
+
return;
|
|
3093
3721
|
}
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3722
|
+
setAuthCookies(
|
|
3723
|
+
cookies,
|
|
3724
|
+
{
|
|
3725
|
+
accessToken: data.accessToken,
|
|
3726
|
+
refreshToken: data.refreshToken
|
|
3727
|
+
},
|
|
3728
|
+
settings
|
|
3729
|
+
);
|
|
3098
3730
|
}
|
|
3099
|
-
function
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
const accessOptions = accessTokenCookieOptions(tokens.accessToken, settings.options?.accessToken);
|
|
3103
|
-
setCookie(cookies, accessName, tokens.accessToken, accessOptions);
|
|
3104
|
-
if (tokens.refreshToken) {
|
|
3105
|
-
setCookie(
|
|
3106
|
-
cookies,
|
|
3107
|
-
refreshName,
|
|
3108
|
-
tokens.refreshToken,
|
|
3109
|
-
refreshTokenCookieOptions(tokens.refreshToken, settings.options?.refreshToken)
|
|
3110
|
-
);
|
|
3731
|
+
function sanitizeAuthData(data) {
|
|
3732
|
+
if (!data) {
|
|
3733
|
+
return null;
|
|
3111
3734
|
}
|
|
3735
|
+
const {
|
|
3736
|
+
accessToken: _accessToken,
|
|
3737
|
+
refreshToken: _refreshToken,
|
|
3738
|
+
csrfToken: _csrfToken,
|
|
3739
|
+
...safeData
|
|
3740
|
+
} = data;
|
|
3741
|
+
return safeData;
|
|
3112
3742
|
}
|
|
3113
|
-
function
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
deleteCookie(cookies, accessName, accessOptions);
|
|
3119
|
-
deleteCookie(cookies, refreshName, refreshOptions);
|
|
3743
|
+
function toSafeAuthResult(result) {
|
|
3744
|
+
return {
|
|
3745
|
+
data: sanitizeAuthData(result.data),
|
|
3746
|
+
error: result.error
|
|
3747
|
+
};
|
|
3120
3748
|
}
|
|
3121
|
-
function
|
|
3122
|
-
const
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
refreshTokenCookieOptions(tokens.refreshToken, settings.options?.refreshToken)
|
|
3749
|
+
function createAuthActions(options = {}) {
|
|
3750
|
+
const {
|
|
3751
|
+
cookies,
|
|
3752
|
+
requestCookies,
|
|
3753
|
+
responseCookies,
|
|
3754
|
+
names,
|
|
3755
|
+
options: cookieOptions,
|
|
3756
|
+
...clientOptions
|
|
3757
|
+
} = options;
|
|
3758
|
+
const readCookies = requestCookies ?? cookies;
|
|
3759
|
+
const writeCookies = responseCookies ?? cookies;
|
|
3760
|
+
if (!writeCookies?.set) {
|
|
3761
|
+
throw new Error(
|
|
3762
|
+
"createAuthActions() requires a writable cookie store. Pass cookies in Server Actions or responseCookies in Route Handlers."
|
|
3136
3763
|
);
|
|
3137
3764
|
}
|
|
3765
|
+
const cookieSettings = {
|
|
3766
|
+
names,
|
|
3767
|
+
options: cookieOptions
|
|
3768
|
+
};
|
|
3769
|
+
const createClient = () => createServerClient({
|
|
3770
|
+
...clientOptions,
|
|
3771
|
+
names,
|
|
3772
|
+
options: cookieOptions,
|
|
3773
|
+
cookies: readCookies
|
|
3774
|
+
});
|
|
3775
|
+
return {
|
|
3776
|
+
signUp: async (request) => {
|
|
3777
|
+
const result = await createClient().auth.signUp(request);
|
|
3778
|
+
persistSessionCookies(writeCookies, result.data, cookieSettings);
|
|
3779
|
+
return toSafeAuthResult(result);
|
|
3780
|
+
},
|
|
3781
|
+
signInWithPassword: async (request) => {
|
|
3782
|
+
const result = await createClient().auth.signInWithPassword(request);
|
|
3783
|
+
persistSessionCookies(writeCookies, result.data, cookieSettings);
|
|
3784
|
+
return toSafeAuthResult(
|
|
3785
|
+
result
|
|
3786
|
+
);
|
|
3787
|
+
},
|
|
3788
|
+
signInWithOAuth: async (providerOrOptions, signInOptions) => {
|
|
3789
|
+
return createClient().auth.signInWithOAuth(
|
|
3790
|
+
providerOrOptions,
|
|
3791
|
+
signInOptions
|
|
3792
|
+
);
|
|
3793
|
+
},
|
|
3794
|
+
signInWithIdToken: async (credentials) => {
|
|
3795
|
+
const result = await createClient().auth.signInWithIdToken(credentials);
|
|
3796
|
+
persistSessionCookies(writeCookies, result.data, cookieSettings);
|
|
3797
|
+
return toSafeAuthResult(
|
|
3798
|
+
result
|
|
3799
|
+
);
|
|
3800
|
+
},
|
|
3801
|
+
exchangeOAuthCode: async (code, codeVerifier) => {
|
|
3802
|
+
const result = await createClient().auth.exchangeOAuthCode(
|
|
3803
|
+
code,
|
|
3804
|
+
codeVerifier
|
|
3805
|
+
);
|
|
3806
|
+
persistSessionCookies(writeCookies, result.data, cookieSettings);
|
|
3807
|
+
return toSafeAuthResult(
|
|
3808
|
+
result
|
|
3809
|
+
);
|
|
3810
|
+
},
|
|
3811
|
+
verifyEmail: async (request) => {
|
|
3812
|
+
const result = await createClient().auth.verifyEmail(request);
|
|
3813
|
+
persistSessionCookies(writeCookies, result.data, cookieSettings);
|
|
3814
|
+
return toSafeAuthResult(result);
|
|
3815
|
+
},
|
|
3816
|
+
signOut: async () => {
|
|
3817
|
+
const result = await createClient().auth.signOut();
|
|
3818
|
+
clearAuthCookies(writeCookies, cookieSettings);
|
|
3819
|
+
return result;
|
|
3820
|
+
}
|
|
3821
|
+
};
|
|
3138
3822
|
}
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
headers,
|
|
3142
|
-
getAccessTokenCookieName(settings.names),
|
|
3143
|
-
"",
|
|
3144
|
-
expiredCookieOptions(settings.options?.accessToken)
|
|
3145
|
-
);
|
|
3146
|
-
appendSetCookie(
|
|
3147
|
-
headers,
|
|
3148
|
-
getRefreshTokenCookieName(settings.names),
|
|
3149
|
-
"",
|
|
3150
|
-
expiredCookieOptions(settings.options?.refreshToken)
|
|
3151
|
-
);
|
|
3152
|
-
}
|
|
3823
|
+
|
|
3824
|
+
// src/ssr/browser-client.ts
|
|
3153
3825
|
async function parseRefreshResponse(response) {
|
|
3154
3826
|
const contentType = response.headers.get("content-type");
|
|
3155
3827
|
if (contentType?.includes("json")) {
|
|
@@ -3160,13 +3832,13 @@ async function parseRefreshResponse(response) {
|
|
|
3160
3832
|
function toRefreshError(response, body) {
|
|
3161
3833
|
if (body && typeof body === "object") {
|
|
3162
3834
|
const errorBody = body;
|
|
3163
|
-
return new
|
|
3835
|
+
return new ResultError(
|
|
3164
3836
|
typeof errorBody.message === "string" ? errorBody.message : "Failed to refresh auth session",
|
|
3165
3837
|
typeof errorBody.statusCode === "number" ? errorBody.statusCode : response.status,
|
|
3166
3838
|
typeof errorBody.error === "string" ? errorBody.error : ERROR_CODES.UNKNOWN_ERROR
|
|
3167
3839
|
);
|
|
3168
3840
|
}
|
|
3169
|
-
return new
|
|
3841
|
+
return new ResultError(
|
|
3170
3842
|
typeof body === "string" && body ? body : "Failed to refresh auth session",
|
|
3171
3843
|
response.status,
|
|
3172
3844
|
ERROR_CODES.UNKNOWN_ERROR
|
|
@@ -3210,13 +3882,13 @@ function getRequestUrl(input) {
|
|
|
3210
3882
|
function createBrowserClient(options = {}) {
|
|
3211
3883
|
let { baseUrl, anonKey } = options;
|
|
3212
3884
|
try {
|
|
3213
|
-
baseUrl
|
|
3214
|
-
anonKey
|
|
3885
|
+
baseUrl ||= process.env.NEXT_PUBLIC_BACKEND_URL;
|
|
3886
|
+
anonKey ||= process.env.NEXT_PUBLIC_BACKEND_ANON_KEY;
|
|
3215
3887
|
} catch {
|
|
3216
3888
|
}
|
|
3217
3889
|
if (!baseUrl || !anonKey) {
|
|
3218
3890
|
throw new Error(
|
|
3219
|
-
"Missing
|
|
3891
|
+
"Missing Result baseUrl or anonKey. Pass baseUrl and anonKey to createBrowserClient() or set NEXT_PUBLIC_BACKEND_URL and NEXT_PUBLIC_BACKEND_ANON_KEY."
|
|
3220
3892
|
);
|
|
3221
3893
|
}
|
|
3222
3894
|
let accessToken = getBrowserCookie(getAccessTokenCookieName(options.names));
|
|
@@ -3231,7 +3903,9 @@ function createBrowserClient(options = {}) {
|
|
|
3231
3903
|
}
|
|
3232
3904
|
refreshPromise = (async () => {
|
|
3233
3905
|
if (!fetchImpl) {
|
|
3234
|
-
throw new Error(
|
|
3906
|
+
throw new Error(
|
|
3907
|
+
"Fetch is not available. Please provide a fetch implementation."
|
|
3908
|
+
);
|
|
3235
3909
|
}
|
|
3236
3910
|
const response = await fetchImpl(refreshUrl, {
|
|
3237
3911
|
method: "POST",
|
|
@@ -3256,7 +3930,10 @@ function createBrowserClient(options = {}) {
|
|
|
3256
3930
|
return null;
|
|
3257
3931
|
}
|
|
3258
3932
|
accessToken = refreshBody.accessToken;
|
|
3259
|
-
client?.setAccessToken(
|
|
3933
|
+
client?.setAccessToken(
|
|
3934
|
+
refreshBody.accessToken,
|
|
3935
|
+
AuthChangeEvent.TOKEN_REFRESHED
|
|
3936
|
+
);
|
|
3260
3937
|
return refreshBody;
|
|
3261
3938
|
})().finally(() => {
|
|
3262
3939
|
sessionChecked = true;
|
|
@@ -3270,7 +3947,9 @@ function createBrowserClient(options = {}) {
|
|
|
3270
3947
|
};
|
|
3271
3948
|
const ssrFetch = async (input, init) => {
|
|
3272
3949
|
if (!fetchImpl) {
|
|
3273
|
-
throw new Error(
|
|
3950
|
+
throw new Error(
|
|
3951
|
+
"Fetch is not available. Please provide a fetch implementation."
|
|
3952
|
+
);
|
|
3274
3953
|
}
|
|
3275
3954
|
if (shouldSkipRefresh(input)) {
|
|
3276
3955
|
return fetchImpl(input, init);
|
|
@@ -3294,7 +3973,7 @@ function createBrowserClient(options = {}) {
|
|
|
3294
3973
|
}
|
|
3295
3974
|
return fetchImpl(input, withAuthHeader(init, refreshed.accessToken));
|
|
3296
3975
|
};
|
|
3297
|
-
client = new
|
|
3976
|
+
client = new ResultClient({
|
|
3298
3977
|
...options,
|
|
3299
3978
|
baseUrl,
|
|
3300
3979
|
anonKey,
|
|
@@ -3319,30 +3998,8 @@ function createBrowserClient(options = {}) {
|
|
|
3319
3998
|
}
|
|
3320
3999
|
return client;
|
|
3321
4000
|
}
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
try {
|
|
3325
|
-
baseUrl || (baseUrl = process.env.NEXT_PUBLIC_INSFORGE_URL);
|
|
3326
|
-
anonKey || (anonKey = process.env.NEXT_PUBLIC_INSFORGE_ANON_KEY);
|
|
3327
|
-
} catch {
|
|
3328
|
-
}
|
|
3329
|
-
if (!baseUrl || !anonKey) {
|
|
3330
|
-
throw new Error(
|
|
3331
|
-
"Missing InsForge baseUrl or anonKey. Pass baseUrl and anonKey to createServerClient() or set NEXT_PUBLIC_INSFORGE_URL and NEXT_PUBLIC_INSFORGE_ANON_KEY."
|
|
3332
|
-
);
|
|
3333
|
-
}
|
|
3334
|
-
const accessToken = options.accessToken ?? getCookieValue(options.cookies, getAccessTokenCookieName(options.names));
|
|
3335
|
-
return new InsForgeClient({
|
|
3336
|
-
...options,
|
|
3337
|
-
baseUrl,
|
|
3338
|
-
anonKey,
|
|
3339
|
-
isServerMode: true,
|
|
3340
|
-
accessToken: accessToken ?? void 0,
|
|
3341
|
-
// The cookie/option token is the only credential source here; shadow any
|
|
3342
|
-
// untyped edgeFunctionToken smuggled through the options spread.
|
|
3343
|
-
edgeFunctionToken: void 0
|
|
3344
|
-
});
|
|
3345
|
-
}
|
|
4001
|
+
|
|
4002
|
+
// src/ssr/refresh.ts
|
|
3346
4003
|
function jsonResponse(body, init = {}, headers = new Headers(init.headers)) {
|
|
3347
4004
|
headers.set("Content-Type", "application/json");
|
|
3348
4005
|
return new Response(JSON.stringify(body), {
|
|
@@ -3351,18 +4008,18 @@ function jsonResponse(body, init = {}, headers = new Headers(init.headers)) {
|
|
|
3351
4008
|
});
|
|
3352
4009
|
}
|
|
3353
4010
|
function normalizeError(error) {
|
|
3354
|
-
if (error instanceof
|
|
4011
|
+
if (error instanceof ResultError) {
|
|
3355
4012
|
return error;
|
|
3356
4013
|
}
|
|
3357
4014
|
if (error && typeof error === "object") {
|
|
3358
4015
|
const body = error;
|
|
3359
|
-
return new
|
|
4016
|
+
return new ResultError(
|
|
3360
4017
|
typeof body.message === "string" ? body.message : "Failed to refresh auth session",
|
|
3361
4018
|
typeof body.statusCode === "number" ? body.statusCode : 500,
|
|
3362
4019
|
typeof body.error === "string" ? body.error : ERROR_CODES.UNKNOWN_ERROR
|
|
3363
4020
|
);
|
|
3364
4021
|
}
|
|
3365
|
-
return new
|
|
4022
|
+
return new ResultError(
|
|
3366
4023
|
error instanceof Error ? error.message : "Failed to refresh auth session",
|
|
3367
4024
|
500,
|
|
3368
4025
|
ERROR_CODES.UNKNOWN_ERROR
|
|
@@ -3384,14 +4041,17 @@ function readRefreshToken(options) {
|
|
|
3384
4041
|
if (cookieValue) {
|
|
3385
4042
|
return cookieValue;
|
|
3386
4043
|
}
|
|
3387
|
-
return getCookieValueFromHeader(
|
|
4044
|
+
return getCookieValueFromHeader(
|
|
4045
|
+
options.request?.headers.get("cookie"),
|
|
4046
|
+
refreshCookieName
|
|
4047
|
+
);
|
|
3388
4048
|
}
|
|
3389
4049
|
async function refreshAuth(options = {}) {
|
|
3390
4050
|
const headers = new Headers();
|
|
3391
4051
|
const refreshToken = readRefreshToken(options);
|
|
3392
4052
|
if (!refreshToken) {
|
|
3393
4053
|
clearAuthCookieHeaders(headers, options);
|
|
3394
|
-
const error2 = new
|
|
4054
|
+
const error2 = new ResultError(
|
|
3395
4055
|
"Refresh token cookie is missing",
|
|
3396
4056
|
401,
|
|
3397
4057
|
ERROR_CODES.AUTH_UNAUTHORIZED
|
|
@@ -3414,18 +4074,20 @@ async function refreshAuth(options = {}) {
|
|
|
3414
4074
|
}
|
|
3415
4075
|
let { baseUrl, anonKey } = options;
|
|
3416
4076
|
try {
|
|
3417
|
-
baseUrl
|
|
3418
|
-
anonKey
|
|
4077
|
+
baseUrl ||= process.env.NEXT_PUBLIC_BACKEND_URL;
|
|
4078
|
+
anonKey ||= process.env.NEXT_PUBLIC_BACKEND_ANON_KEY;
|
|
3419
4079
|
} catch {
|
|
3420
4080
|
}
|
|
3421
4081
|
if (!baseUrl || !anonKey) {
|
|
3422
4082
|
throw new Error(
|
|
3423
|
-
"Missing
|
|
4083
|
+
"Missing Result baseUrl or anonKey. Pass baseUrl and anonKey to refreshAuth() or set NEXT_PUBLIC_BACKEND_URL and NEXT_PUBLIC_BACKEND_ANON_KEY."
|
|
3424
4084
|
);
|
|
3425
4085
|
}
|
|
3426
4086
|
const fetchImpl = options.fetch ?? (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
|
|
3427
4087
|
if (!fetchImpl) {
|
|
3428
|
-
throw new Error(
|
|
4088
|
+
throw new Error(
|
|
4089
|
+
"Fetch is not available. Please provide a fetch implementation."
|
|
4090
|
+
);
|
|
3429
4091
|
}
|
|
3430
4092
|
const requestHeaders = new Headers(options.headers);
|
|
3431
4093
|
requestHeaders.set("Authorization", `Bearer ${anonKey}`);
|
|
@@ -3503,99 +4165,8 @@ function createRefreshAuthRouter(options = {}) {
|
|
|
3503
4165
|
POST: async (request) => (await refreshAuth({ ...options, request })).response
|
|
3504
4166
|
};
|
|
3505
4167
|
}
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
return;
|
|
3509
|
-
}
|
|
3510
|
-
setAuthCookies(
|
|
3511
|
-
cookies,
|
|
3512
|
-
{
|
|
3513
|
-
accessToken: data.accessToken,
|
|
3514
|
-
refreshToken: data.refreshToken
|
|
3515
|
-
},
|
|
3516
|
-
settings
|
|
3517
|
-
);
|
|
3518
|
-
}
|
|
3519
|
-
function sanitizeAuthData(data) {
|
|
3520
|
-
if (!data) {
|
|
3521
|
-
return null;
|
|
3522
|
-
}
|
|
3523
|
-
const {
|
|
3524
|
-
accessToken: _accessToken,
|
|
3525
|
-
refreshToken: _refreshToken,
|
|
3526
|
-
csrfToken: _csrfToken,
|
|
3527
|
-
...safeData
|
|
3528
|
-
} = data;
|
|
3529
|
-
return safeData;
|
|
3530
|
-
}
|
|
3531
|
-
function toSafeAuthResult(result) {
|
|
3532
|
-
return {
|
|
3533
|
-
data: sanitizeAuthData(result.data),
|
|
3534
|
-
error: result.error
|
|
3535
|
-
};
|
|
3536
|
-
}
|
|
3537
|
-
function createAuthActions(options = {}) {
|
|
3538
|
-
const {
|
|
3539
|
-
cookies,
|
|
3540
|
-
requestCookies,
|
|
3541
|
-
responseCookies,
|
|
3542
|
-
names,
|
|
3543
|
-
options: cookieOptions,
|
|
3544
|
-
...clientOptions
|
|
3545
|
-
} = options;
|
|
3546
|
-
const readCookies = requestCookies ?? cookies;
|
|
3547
|
-
const writeCookies = responseCookies ?? cookies;
|
|
3548
|
-
if (!writeCookies?.set) {
|
|
3549
|
-
throw new Error(
|
|
3550
|
-
"createAuthActions() requires a writable cookie store. Pass cookies in Server Actions or responseCookies in Route Handlers."
|
|
3551
|
-
);
|
|
3552
|
-
}
|
|
3553
|
-
const cookieSettings = {
|
|
3554
|
-
names,
|
|
3555
|
-
options: cookieOptions
|
|
3556
|
-
};
|
|
3557
|
-
const createClient = () => createServerClient({
|
|
3558
|
-
...clientOptions,
|
|
3559
|
-
names,
|
|
3560
|
-
options: cookieOptions,
|
|
3561
|
-
cookies: readCookies
|
|
3562
|
-
});
|
|
3563
|
-
return {
|
|
3564
|
-
signUp: async (request) => {
|
|
3565
|
-
const result = await createClient().auth.signUp(request);
|
|
3566
|
-
persistSessionCookies(writeCookies, result.data, cookieSettings);
|
|
3567
|
-
return toSafeAuthResult(result);
|
|
3568
|
-
},
|
|
3569
|
-
signInWithPassword: async (request) => {
|
|
3570
|
-
const result = await createClient().auth.signInWithPassword(request);
|
|
3571
|
-
persistSessionCookies(writeCookies, result.data, cookieSettings);
|
|
3572
|
-
return toSafeAuthResult(result);
|
|
3573
|
-
},
|
|
3574
|
-
signInWithOAuth: async (providerOrOptions, signInOptions) => {
|
|
3575
|
-
return createClient().auth.signInWithOAuth(providerOrOptions, signInOptions);
|
|
3576
|
-
},
|
|
3577
|
-
signInWithIdToken: async (credentials) => {
|
|
3578
|
-
const result = await createClient().auth.signInWithIdToken(credentials);
|
|
3579
|
-
persistSessionCookies(writeCookies, result.data, cookieSettings);
|
|
3580
|
-
return toSafeAuthResult(result);
|
|
3581
|
-
},
|
|
3582
|
-
exchangeOAuthCode: async (code, codeVerifier) => {
|
|
3583
|
-
const result = await createClient().auth.exchangeOAuthCode(code, codeVerifier);
|
|
3584
|
-
persistSessionCookies(writeCookies, result.data, cookieSettings);
|
|
3585
|
-
return toSafeAuthResult(result);
|
|
3586
|
-
},
|
|
3587
|
-
verifyEmail: async (request) => {
|
|
3588
|
-
const result = await createClient().auth.verifyEmail(request);
|
|
3589
|
-
persistSessionCookies(writeCookies, result.data, cookieSettings);
|
|
3590
|
-
return toSafeAuthResult(result);
|
|
3591
|
-
},
|
|
3592
|
-
signOut: async () => {
|
|
3593
|
-
const result = await createClient().auth.signOut();
|
|
3594
|
-
clearAuthCookies(writeCookies, cookieSettings);
|
|
3595
|
-
return result;
|
|
3596
|
-
}
|
|
3597
|
-
};
|
|
3598
|
-
}
|
|
4168
|
+
|
|
4169
|
+
// src/ssr/update-session.ts
|
|
3599
4170
|
async function updateSession(options) {
|
|
3600
4171
|
const accessCookieName = getAccessTokenCookieName(options.names);
|
|
3601
4172
|
const refreshCookieName = getRefreshTokenCookieName(options.names);
|
|
@@ -3607,7 +4178,10 @@ async function updateSession(options) {
|
|
|
3607
4178
|
error: null
|
|
3608
4179
|
};
|
|
3609
4180
|
}
|
|
3610
|
-
const refreshToken = getCookieValue(
|
|
4181
|
+
const refreshToken = getCookieValue(
|
|
4182
|
+
options.requestCookies,
|
|
4183
|
+
refreshCookieName
|
|
4184
|
+
);
|
|
3611
4185
|
if (!refreshToken) {
|
|
3612
4186
|
if (accessToken) {
|
|
3613
4187
|
clearAuthCookies(options.requestCookies, options);
|