@mitway/sdk 0.5.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -53
- package/dist/index.cjs +5 -2092
- package/dist/index.d.cts +229 -24
- package/dist/index.d.ts +229 -24
- package/dist/index.js +5 -2054
- package/package.json +1 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,2092 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
Auth: () => Auth,
|
|
24
|
-
Database: () => Database,
|
|
25
|
-
HttpClient: () => HttpClient,
|
|
26
|
-
Logger: () => Logger,
|
|
27
|
-
MitwayBaasClient: () => MitwayBaasClient,
|
|
28
|
-
MitwayBaasError: () => MitwayBaasError,
|
|
29
|
-
Realtime: () => Realtime,
|
|
30
|
-
RealtimeChannel: () => RealtimeChannel,
|
|
31
|
-
Storage: () => Storage,
|
|
32
|
-
StorageBucketClient: () => StorageBucketClient,
|
|
33
|
-
TokenManager: () => TokenManager,
|
|
34
|
-
createClient: () => createClient,
|
|
35
|
-
default: () => index_default
|
|
36
|
-
});
|
|
37
|
-
module.exports = __toCommonJS(index_exports);
|
|
38
|
-
|
|
39
|
-
// src/types.ts
|
|
40
|
-
var MitwayBaasError = class _MitwayBaasError extends Error {
|
|
41
|
-
statusCode;
|
|
42
|
-
error;
|
|
43
|
-
nextActions;
|
|
44
|
-
constructor(message, statusCode, error, nextActions) {
|
|
45
|
-
super(message);
|
|
46
|
-
this.name = "MitwayBaasError";
|
|
47
|
-
this.statusCode = statusCode;
|
|
48
|
-
this.error = error;
|
|
49
|
-
this.nextActions = nextActions;
|
|
50
|
-
}
|
|
51
|
-
static fromApiError(apiError) {
|
|
52
|
-
return new _MitwayBaasError(
|
|
53
|
-
apiError.message,
|
|
54
|
-
apiError.statusCode,
|
|
55
|
-
apiError.error,
|
|
56
|
-
apiError.nextActions
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
// src/lib/logger.ts
|
|
62
|
-
var SENSITIVE_HEADERS = ["authorization", "x-api-key", "cookie", "set-cookie"];
|
|
63
|
-
var SENSITIVE_BODY_KEYS = [
|
|
64
|
-
"password",
|
|
65
|
-
"token",
|
|
66
|
-
"accesstoken",
|
|
67
|
-
"refreshtoken",
|
|
68
|
-
"authorization",
|
|
69
|
-
"secret",
|
|
70
|
-
"apikey",
|
|
71
|
-
"api_key",
|
|
72
|
-
"email",
|
|
73
|
-
"ssn",
|
|
74
|
-
"creditcard",
|
|
75
|
-
"credit_card"
|
|
76
|
-
];
|
|
77
|
-
function redactHeaders(headers) {
|
|
78
|
-
const redacted = {};
|
|
79
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
80
|
-
if (SENSITIVE_HEADERS.includes(key.toLowerCase())) {
|
|
81
|
-
redacted[key] = "***REDACTED***";
|
|
82
|
-
} else {
|
|
83
|
-
redacted[key] = value;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return redacted;
|
|
87
|
-
}
|
|
88
|
-
function sanitizeBody(body) {
|
|
89
|
-
if (body === null || body === void 0) return body;
|
|
90
|
-
if (typeof body === "string") {
|
|
91
|
-
try {
|
|
92
|
-
const parsed = JSON.parse(body);
|
|
93
|
-
return sanitizeBody(parsed);
|
|
94
|
-
} catch {
|
|
95
|
-
return body;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
if (Array.isArray(body)) return body.map(sanitizeBody);
|
|
99
|
-
if (typeof body === "object") {
|
|
100
|
-
const sanitized = {};
|
|
101
|
-
for (const [key, value] of Object.entries(body)) {
|
|
102
|
-
if (SENSITIVE_BODY_KEYS.includes(key.toLowerCase().replace(/[-_]/g, ""))) {
|
|
103
|
-
sanitized[key] = "***REDACTED***";
|
|
104
|
-
} else {
|
|
105
|
-
sanitized[key] = sanitizeBody(value);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return sanitized;
|
|
109
|
-
}
|
|
110
|
-
return body;
|
|
111
|
-
}
|
|
112
|
-
function formatBody(body) {
|
|
113
|
-
if (body === void 0 || body === null) return "";
|
|
114
|
-
if (typeof body === "string") {
|
|
115
|
-
try {
|
|
116
|
-
return JSON.stringify(JSON.parse(body), null, 2);
|
|
117
|
-
} catch {
|
|
118
|
-
return body;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
if (typeof FormData !== "undefined" && body instanceof FormData) {
|
|
122
|
-
return "[FormData]";
|
|
123
|
-
}
|
|
124
|
-
try {
|
|
125
|
-
return JSON.stringify(body, null, 2);
|
|
126
|
-
} catch {
|
|
127
|
-
return "[Unserializable body]";
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
var Logger = class {
|
|
131
|
-
enabled;
|
|
132
|
-
customLog;
|
|
133
|
-
constructor(debug) {
|
|
134
|
-
if (typeof debug === "function") {
|
|
135
|
-
this.enabled = true;
|
|
136
|
-
this.customLog = debug;
|
|
137
|
-
} else {
|
|
138
|
-
this.enabled = !!debug;
|
|
139
|
-
this.customLog = null;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
log(message, ...args) {
|
|
143
|
-
if (!this.enabled) return;
|
|
144
|
-
const formatted = `[MITWAY-BaaS Debug] ${message}`;
|
|
145
|
-
if (this.customLog) {
|
|
146
|
-
this.customLog(formatted, ...args);
|
|
147
|
-
} else {
|
|
148
|
-
console.log(formatted, ...args);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
warn(message, ...args) {
|
|
152
|
-
if (!this.enabled) return;
|
|
153
|
-
const formatted = `[MITWAY-BaaS Debug] ${message}`;
|
|
154
|
-
if (this.customLog) {
|
|
155
|
-
this.customLog(formatted, ...args);
|
|
156
|
-
} else {
|
|
157
|
-
console.warn(formatted, ...args);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
error(message, ...args) {
|
|
161
|
-
if (!this.enabled) return;
|
|
162
|
-
const formatted = `[MITWAY-BaaS Debug] ${message}`;
|
|
163
|
-
if (this.customLog) {
|
|
164
|
-
this.customLog(formatted, ...args);
|
|
165
|
-
} else {
|
|
166
|
-
console.error(formatted, ...args);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
logRequest(method, url, headers, body) {
|
|
170
|
-
if (!this.enabled) return;
|
|
171
|
-
const parts = [`\u2192 ${method} ${url}`];
|
|
172
|
-
if (headers && Object.keys(headers).length > 0) {
|
|
173
|
-
parts.push(` Headers: ${JSON.stringify(redactHeaders(headers))}`);
|
|
174
|
-
}
|
|
175
|
-
const formattedBody = formatBody(sanitizeBody(body));
|
|
176
|
-
if (formattedBody) {
|
|
177
|
-
const truncated = formattedBody.length > 1e3 ? formattedBody.slice(0, 1e3) + "... [truncated]" : formattedBody;
|
|
178
|
-
parts.push(` Body: ${truncated}`);
|
|
179
|
-
}
|
|
180
|
-
this.log(parts.join("\n"));
|
|
181
|
-
}
|
|
182
|
-
logResponse(method, url, status, durationMs, body) {
|
|
183
|
-
if (!this.enabled) return;
|
|
184
|
-
const parts = [
|
|
185
|
-
`\u2190 ${method} ${url} ${status} (${durationMs}ms)`
|
|
186
|
-
];
|
|
187
|
-
const formattedBody = formatBody(sanitizeBody(body));
|
|
188
|
-
if (formattedBody) {
|
|
189
|
-
const truncated = formattedBody.length > 1e3 ? formattedBody.slice(0, 1e3) + "... [truncated]" : formattedBody;
|
|
190
|
-
parts.push(` Body: ${truncated}`);
|
|
191
|
-
}
|
|
192
|
-
if (status >= 400) {
|
|
193
|
-
this.error(parts.join("\n"));
|
|
194
|
-
} else {
|
|
195
|
-
this.log(parts.join("\n"));
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
// src/lib/token-manager.ts
|
|
201
|
-
var CSRF_TOKEN_COOKIE = "mitway_baas_csrf_token";
|
|
202
|
-
var DEFAULT_STORAGE_KEY = "mitway_baas_session";
|
|
203
|
-
function getCsrfToken() {
|
|
204
|
-
if (typeof document === "undefined") return null;
|
|
205
|
-
const match = document.cookie.split(";").find((c) => c.trim().startsWith(`${CSRF_TOKEN_COOKIE}=`));
|
|
206
|
-
if (!match) return null;
|
|
207
|
-
return match.split("=")[1] || null;
|
|
208
|
-
}
|
|
209
|
-
function setCsrfToken(token) {
|
|
210
|
-
if (typeof document === "undefined") return;
|
|
211
|
-
const maxAge = 7 * 24 * 60 * 60;
|
|
212
|
-
const secure = typeof window !== "undefined" && window.location.protocol === "https:" ? "; Secure" : "";
|
|
213
|
-
document.cookie = `${CSRF_TOKEN_COOKIE}=${encodeURIComponent(token)}; path=/; max-age=${maxAge}; SameSite=Lax${secure}`;
|
|
214
|
-
}
|
|
215
|
-
function clearCsrfToken() {
|
|
216
|
-
if (typeof document === "undefined") return;
|
|
217
|
-
const secure = typeof window !== "undefined" && window.location.protocol === "https:" ? "; Secure" : "";
|
|
218
|
-
document.cookie = `${CSRF_TOKEN_COOKIE}=; path=/; max-age=0; SameSite=Lax${secure}`;
|
|
219
|
-
}
|
|
220
|
-
var TokenManager = class {
|
|
221
|
-
accessToken = null;
|
|
222
|
-
refreshToken = null;
|
|
223
|
-
user = null;
|
|
224
|
-
persistSession;
|
|
225
|
-
storageKey;
|
|
226
|
-
/** Fired when the access token changes (used by long-lived consumers). */
|
|
227
|
-
onTokenChange = null;
|
|
228
|
-
constructor(opts) {
|
|
229
|
-
this.persistSession = opts?.persistSession ?? true;
|
|
230
|
-
this.storageKey = opts?.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
231
|
-
}
|
|
232
|
-
saveSession(session) {
|
|
233
|
-
const tokenChanged = session.accessToken !== this.accessToken;
|
|
234
|
-
this.accessToken = session.accessToken;
|
|
235
|
-
this.user = session.user;
|
|
236
|
-
if (session.refreshToken !== void 0) {
|
|
237
|
-
this.refreshToken = session.refreshToken ?? null;
|
|
238
|
-
}
|
|
239
|
-
this.persist();
|
|
240
|
-
if (tokenChanged && this.onTokenChange) {
|
|
241
|
-
this.onTokenChange();
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
getSession() {
|
|
245
|
-
if (!this.accessToken || !this.user) return null;
|
|
246
|
-
return {
|
|
247
|
-
accessToken: this.accessToken,
|
|
248
|
-
refreshToken: this.refreshToken ?? void 0,
|
|
249
|
-
user: this.user
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
getAccessToken() {
|
|
253
|
-
return this.accessToken;
|
|
254
|
-
}
|
|
255
|
-
setAccessToken(token) {
|
|
256
|
-
const tokenChanged = token !== this.accessToken;
|
|
257
|
-
this.accessToken = token;
|
|
258
|
-
this.persist();
|
|
259
|
-
if (tokenChanged && this.onTokenChange) {
|
|
260
|
-
this.onTokenChange();
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
getRefreshToken() {
|
|
264
|
-
return this.refreshToken;
|
|
265
|
-
}
|
|
266
|
-
setRefreshToken(token) {
|
|
267
|
-
this.refreshToken = token;
|
|
268
|
-
this.persist();
|
|
269
|
-
}
|
|
270
|
-
getUser() {
|
|
271
|
-
return this.user;
|
|
272
|
-
}
|
|
273
|
-
setUser(user) {
|
|
274
|
-
this.user = user;
|
|
275
|
-
this.persist();
|
|
276
|
-
}
|
|
277
|
-
clearSession() {
|
|
278
|
-
const hadToken = this.accessToken !== null;
|
|
279
|
-
this.accessToken = null;
|
|
280
|
-
this.refreshToken = null;
|
|
281
|
-
this.user = null;
|
|
282
|
-
this.removePersisted();
|
|
283
|
-
if (hadToken && this.onTokenChange) {
|
|
284
|
-
this.onTokenChange();
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
/**
|
|
288
|
-
* Restore the session from localStorage. Returns true if a persisted
|
|
289
|
-
* session was found and loaded into memory.
|
|
290
|
-
*/
|
|
291
|
-
restoreSession() {
|
|
292
|
-
if (!this.persistSession || typeof localStorage === "undefined") return false;
|
|
293
|
-
try {
|
|
294
|
-
const raw = localStorage.getItem(this.storageKey);
|
|
295
|
-
if (!raw) return false;
|
|
296
|
-
const stored = JSON.parse(raw);
|
|
297
|
-
if (!stored.accessToken || !stored.user) return false;
|
|
298
|
-
this.accessToken = stored.accessToken;
|
|
299
|
-
this.refreshToken = stored.refreshToken ?? null;
|
|
300
|
-
this.user = stored.user;
|
|
301
|
-
return true;
|
|
302
|
-
} catch {
|
|
303
|
-
return false;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
persist() {
|
|
307
|
-
if (!this.persistSession || typeof localStorage === "undefined") return;
|
|
308
|
-
if (!this.accessToken || !this.user) return;
|
|
309
|
-
try {
|
|
310
|
-
const data = {
|
|
311
|
-
accessToken: this.accessToken,
|
|
312
|
-
user: this.user
|
|
313
|
-
};
|
|
314
|
-
if (this.refreshToken) {
|
|
315
|
-
data.refreshToken = this.refreshToken;
|
|
316
|
-
}
|
|
317
|
-
localStorage.setItem(this.storageKey, JSON.stringify(data));
|
|
318
|
-
} catch {
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
removePersisted() {
|
|
322
|
-
if (!this.persistSession || typeof localStorage === "undefined") return;
|
|
323
|
-
try {
|
|
324
|
-
localStorage.removeItem(this.storageKey);
|
|
325
|
-
} catch {
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
// src/lib/auth-envelope.ts
|
|
331
|
-
function normalizeAuthPayload(raw) {
|
|
332
|
-
if (!raw || typeof raw !== "object") return raw;
|
|
333
|
-
const src = raw;
|
|
334
|
-
const out = { ...src };
|
|
335
|
-
let mutated = false;
|
|
336
|
-
if ("access_token" in src && !("accessToken" in src)) {
|
|
337
|
-
out.accessToken = src.access_token;
|
|
338
|
-
delete out.access_token;
|
|
339
|
-
mutated = true;
|
|
340
|
-
}
|
|
341
|
-
if ("csrf_token" in src && !("csrfToken" in src)) {
|
|
342
|
-
out.csrfToken = src.csrf_token;
|
|
343
|
-
delete out.csrf_token;
|
|
344
|
-
mutated = true;
|
|
345
|
-
}
|
|
346
|
-
if ("refresh_token" in src && !("refreshToken" in src)) {
|
|
347
|
-
out.refreshToken = src.refresh_token;
|
|
348
|
-
delete out.refresh_token;
|
|
349
|
-
mutated = true;
|
|
350
|
-
}
|
|
351
|
-
return mutated ? out : raw;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// src/lib/http-client.ts
|
|
355
|
-
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([500, 502, 503, 504]);
|
|
356
|
-
var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
357
|
-
var HttpClient = class {
|
|
358
|
-
baseUrl;
|
|
359
|
-
fetch;
|
|
360
|
-
defaultHeaders;
|
|
361
|
-
anonKey;
|
|
362
|
-
userToken = null;
|
|
363
|
-
logger;
|
|
364
|
-
autoRefreshToken = true;
|
|
365
|
-
isRefreshing = false;
|
|
366
|
-
refreshPromise = null;
|
|
367
|
-
tokenManager;
|
|
368
|
-
refreshToken = null;
|
|
369
|
-
timeout;
|
|
370
|
-
retryCount;
|
|
371
|
-
retryDelay;
|
|
372
|
-
constructor(config, tokenManager, logger) {
|
|
373
|
-
this.baseUrl = config.baseUrl || "http://localhost:7130";
|
|
374
|
-
this.autoRefreshToken = config.autoRefreshToken ?? true;
|
|
375
|
-
this.fetch = config.fetch || (globalThis.fetch ? globalThis.fetch.bind(globalThis) : void 0);
|
|
376
|
-
this.anonKey = config.anonKey;
|
|
377
|
-
this.defaultHeaders = {
|
|
378
|
-
...config.headers
|
|
379
|
-
};
|
|
380
|
-
this.tokenManager = tokenManager ?? new TokenManager();
|
|
381
|
-
this.logger = logger || new Logger(false);
|
|
382
|
-
this.timeout = config.timeout ?? 3e4;
|
|
383
|
-
this.retryCount = config.retryCount ?? 3;
|
|
384
|
-
this.retryDelay = config.retryDelay ?? 500;
|
|
385
|
-
if (!this.fetch) {
|
|
386
|
-
throw new Error(
|
|
387
|
-
"Fetch is not available. Provide a fetch implementation in the SDK config."
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
buildUrl(path, params) {
|
|
392
|
-
const url = new URL(path, this.baseUrl);
|
|
393
|
-
if (params) {
|
|
394
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
395
|
-
if (key === "select") {
|
|
396
|
-
let normalizedValue = value.replace(/\s+/g, " ").trim();
|
|
397
|
-
normalizedValue = normalizedValue.replace(/\s*\(\s*/g, "(").replace(/\s*\)\s*/g, ")").replace(/\(\s+/g, "(").replace(/\s+\)/g, ")").replace(/,\s+(?=[^()]*\))/g, ",");
|
|
398
|
-
url.searchParams.append(key, normalizedValue);
|
|
399
|
-
} else {
|
|
400
|
-
url.searchParams.append(key, value);
|
|
401
|
-
}
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
return url.toString();
|
|
405
|
-
}
|
|
406
|
-
isRetryableStatus(status) {
|
|
407
|
-
return RETRYABLE_STATUS_CODES.has(status);
|
|
408
|
-
}
|
|
409
|
-
computeRetryDelay(attempt) {
|
|
410
|
-
const base = this.retryDelay * Math.pow(2, attempt - 1);
|
|
411
|
-
const jitter = base * (0.85 + Math.random() * 0.3);
|
|
412
|
-
return Math.round(jitter);
|
|
413
|
-
}
|
|
414
|
-
async handleRequest(method, path, options = {}) {
|
|
415
|
-
const {
|
|
416
|
-
params,
|
|
417
|
-
headers = {},
|
|
418
|
-
body,
|
|
419
|
-
signal: callerSignal,
|
|
420
|
-
...fetchOptions
|
|
421
|
-
} = options;
|
|
422
|
-
const url = this.buildUrl(path, params);
|
|
423
|
-
const startTime = Date.now();
|
|
424
|
-
const canRetry = IDEMPOTENT_METHODS.has(method.toUpperCase()) || options.idempotent === true;
|
|
425
|
-
const maxAttempts = canRetry ? this.retryCount : 0;
|
|
426
|
-
const requestHeaders = {
|
|
427
|
-
...this.defaultHeaders
|
|
428
|
-
};
|
|
429
|
-
const authToken = this.userToken || this.anonKey;
|
|
430
|
-
if (authToken) {
|
|
431
|
-
requestHeaders["Authorization"] = `Bearer ${authToken}`;
|
|
432
|
-
}
|
|
433
|
-
let processedBody;
|
|
434
|
-
if (body !== void 0) {
|
|
435
|
-
if (typeof FormData !== "undefined" && body instanceof FormData) {
|
|
436
|
-
processedBody = body;
|
|
437
|
-
} else {
|
|
438
|
-
if (method !== "GET") {
|
|
439
|
-
requestHeaders["Content-Type"] = "application/json;charset=UTF-8";
|
|
440
|
-
}
|
|
441
|
-
processedBody = JSON.stringify(body);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
if (headers instanceof Headers) {
|
|
445
|
-
headers.forEach((value, key) => {
|
|
446
|
-
requestHeaders[key] = value;
|
|
447
|
-
});
|
|
448
|
-
} else if (Array.isArray(headers)) {
|
|
449
|
-
headers.forEach(([key, value]) => {
|
|
450
|
-
requestHeaders[key] = value;
|
|
451
|
-
});
|
|
452
|
-
} else {
|
|
453
|
-
Object.assign(requestHeaders, headers);
|
|
454
|
-
}
|
|
455
|
-
this.logger.logRequest(method, url, requestHeaders, processedBody);
|
|
456
|
-
let lastError;
|
|
457
|
-
for (let attempt = 0; attempt <= maxAttempts; attempt++) {
|
|
458
|
-
if (attempt > 0) {
|
|
459
|
-
const delay = this.computeRetryDelay(attempt);
|
|
460
|
-
this.logger.warn(
|
|
461
|
-
`Retry ${attempt}/${maxAttempts} for ${method} ${url} in ${delay}ms`
|
|
462
|
-
);
|
|
463
|
-
if (callerSignal?.aborted) throw callerSignal.reason;
|
|
464
|
-
await new Promise((resolve, reject) => {
|
|
465
|
-
const onAbort = () => {
|
|
466
|
-
clearTimeout(timer2);
|
|
467
|
-
reject(callerSignal.reason);
|
|
468
|
-
};
|
|
469
|
-
const timer2 = setTimeout(() => {
|
|
470
|
-
if (callerSignal)
|
|
471
|
-
callerSignal.removeEventListener("abort", onAbort);
|
|
472
|
-
resolve();
|
|
473
|
-
}, delay);
|
|
474
|
-
if (callerSignal) {
|
|
475
|
-
callerSignal.addEventListener("abort", onAbort, { once: true });
|
|
476
|
-
}
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
let controller;
|
|
480
|
-
let timer;
|
|
481
|
-
if (this.timeout > 0 || callerSignal) {
|
|
482
|
-
controller = new AbortController();
|
|
483
|
-
if (this.timeout > 0) {
|
|
484
|
-
timer = setTimeout(() => controller.abort(), this.timeout);
|
|
485
|
-
}
|
|
486
|
-
if (callerSignal) {
|
|
487
|
-
if (callerSignal.aborted) {
|
|
488
|
-
controller.abort(callerSignal.reason);
|
|
489
|
-
} else {
|
|
490
|
-
const onCallerAbort = () => controller.abort(callerSignal.reason);
|
|
491
|
-
callerSignal.addEventListener("abort", onCallerAbort, { once: true });
|
|
492
|
-
controller.signal.addEventListener(
|
|
493
|
-
"abort",
|
|
494
|
-
() => {
|
|
495
|
-
callerSignal.removeEventListener("abort", onCallerAbort);
|
|
496
|
-
},
|
|
497
|
-
{ once: true }
|
|
498
|
-
);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
try {
|
|
503
|
-
const response = await this.fetch(url, {
|
|
504
|
-
method,
|
|
505
|
-
headers: requestHeaders,
|
|
506
|
-
body: processedBody,
|
|
507
|
-
...fetchOptions,
|
|
508
|
-
...controller ? { signal: controller.signal } : {}
|
|
509
|
-
});
|
|
510
|
-
if (this.isRetryableStatus(response.status) && attempt < maxAttempts) {
|
|
511
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
512
|
-
await response.body?.cancel();
|
|
513
|
-
lastError = new MitwayBaasError(
|
|
514
|
-
`Server error: ${response.status} ${response.statusText}`,
|
|
515
|
-
response.status,
|
|
516
|
-
"SERVER_ERROR"
|
|
517
|
-
);
|
|
518
|
-
continue;
|
|
519
|
-
}
|
|
520
|
-
if (response.status === 204) {
|
|
521
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
522
|
-
return void 0;
|
|
523
|
-
}
|
|
524
|
-
let data;
|
|
525
|
-
const contentType = response.headers.get("content-type");
|
|
526
|
-
try {
|
|
527
|
-
if (contentType?.includes("json")) {
|
|
528
|
-
data = await response.json();
|
|
529
|
-
} else {
|
|
530
|
-
data = await response.text();
|
|
531
|
-
}
|
|
532
|
-
} catch (parseErr) {
|
|
533
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
534
|
-
throw new MitwayBaasError(
|
|
535
|
-
`Failed to parse response body: ${parseErr?.message || "Unknown error"}`,
|
|
536
|
-
response.status,
|
|
537
|
-
response.ok ? "PARSE_ERROR" : "REQUEST_FAILED"
|
|
538
|
-
);
|
|
539
|
-
}
|
|
540
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
541
|
-
if (!response.ok) {
|
|
542
|
-
this.logger.logResponse(
|
|
543
|
-
method,
|
|
544
|
-
url,
|
|
545
|
-
response.status,
|
|
546
|
-
Date.now() - startTime,
|
|
547
|
-
data
|
|
548
|
-
);
|
|
549
|
-
if (data && typeof data === "object" && "error" in data && data.error !== null && typeof data.error === "object") {
|
|
550
|
-
const envErr = data.error;
|
|
551
|
-
throw new MitwayBaasError(
|
|
552
|
-
envErr.message || response.statusText || "Request failed",
|
|
553
|
-
envErr.statusCode || response.status,
|
|
554
|
-
envErr.code || envErr.error || "REQUEST_FAILED",
|
|
555
|
-
envErr.nextActions
|
|
556
|
-
);
|
|
557
|
-
}
|
|
558
|
-
throw new MitwayBaasError(
|
|
559
|
-
`Request failed: ${response.statusText}`,
|
|
560
|
-
response.status,
|
|
561
|
-
"REQUEST_FAILED"
|
|
562
|
-
);
|
|
563
|
-
}
|
|
564
|
-
this.logger.logResponse(
|
|
565
|
-
method,
|
|
566
|
-
url,
|
|
567
|
-
response.status,
|
|
568
|
-
Date.now() - startTime,
|
|
569
|
-
data
|
|
570
|
-
);
|
|
571
|
-
if (data && typeof data === "object" && "data" in data && "error" in data && data.error === null) {
|
|
572
|
-
return data.data;
|
|
573
|
-
}
|
|
574
|
-
return data;
|
|
575
|
-
} catch (err) {
|
|
576
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
577
|
-
if (err?.name === "AbortError") {
|
|
578
|
-
if (controller && controller.signal.aborted && this.timeout > 0 && !callerSignal?.aborted) {
|
|
579
|
-
throw new MitwayBaasError(
|
|
580
|
-
`Request timed out after ${this.timeout}ms`,
|
|
581
|
-
408,
|
|
582
|
-
"REQUEST_TIMEOUT"
|
|
583
|
-
);
|
|
584
|
-
}
|
|
585
|
-
throw err;
|
|
586
|
-
}
|
|
587
|
-
if (err instanceof MitwayBaasError) {
|
|
588
|
-
throw err;
|
|
589
|
-
}
|
|
590
|
-
if (attempt < maxAttempts) {
|
|
591
|
-
lastError = err;
|
|
592
|
-
continue;
|
|
593
|
-
}
|
|
594
|
-
throw new MitwayBaasError(
|
|
595
|
-
`Network request failed: ${err?.message || "Unknown error"}`,
|
|
596
|
-
0,
|
|
597
|
-
"NETWORK_ERROR"
|
|
598
|
-
);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
throw lastError || new MitwayBaasError(
|
|
602
|
-
"Request failed after all retry attempts",
|
|
603
|
-
0,
|
|
604
|
-
"NETWORK_ERROR"
|
|
605
|
-
);
|
|
606
|
-
}
|
|
607
|
-
async request(method, path, options = {}) {
|
|
608
|
-
try {
|
|
609
|
-
return await this.handleRequest(method, path, { ...options });
|
|
610
|
-
} catch (error) {
|
|
611
|
-
if (error instanceof MitwayBaasError && error.statusCode === 401 && error.error === "INVALID_TOKEN" && this.autoRefreshToken) {
|
|
612
|
-
try {
|
|
613
|
-
const newTokenData = await this.handleTokenRefresh();
|
|
614
|
-
this.setAuthToken(newTokenData.accessToken);
|
|
615
|
-
this.tokenManager.saveSession(newTokenData);
|
|
616
|
-
if (newTokenData.csrfToken) {
|
|
617
|
-
setCsrfToken(newTokenData.csrfToken);
|
|
618
|
-
}
|
|
619
|
-
if (newTokenData.refreshToken) {
|
|
620
|
-
this.setRefreshToken(newTokenData.refreshToken);
|
|
621
|
-
}
|
|
622
|
-
return await this.handleRequest(method, path, { ...options });
|
|
623
|
-
} catch (refreshError) {
|
|
624
|
-
this.tokenManager.clearSession();
|
|
625
|
-
this.userToken = null;
|
|
626
|
-
this.refreshToken = null;
|
|
627
|
-
clearCsrfToken();
|
|
628
|
-
throw refreshError;
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
throw error;
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
/**
|
|
635
|
-
* Low-level fetch helper for binary bodies (uploads) and streamed responses
|
|
636
|
-
* (downloads). Applies the current Bearer token (user session → anon key
|
|
637
|
-
* fallback) plus any configured default headers, resolves `path` against
|
|
638
|
-
* `baseUrl`, and returns the raw `Response` — it does NOT unwrap the
|
|
639
|
-
* `{ data, error }` envelope, so the caller is responsible for status
|
|
640
|
-
* checking and parsing.
|
|
641
|
-
*
|
|
642
|
-
* Used by the storage module for object upload/download paths where the
|
|
643
|
-
* body or response is not JSON.
|
|
644
|
-
*/
|
|
645
|
-
async rawFetch(path, init = {}) {
|
|
646
|
-
const url = this.buildUrl(path);
|
|
647
|
-
const headers = new Headers(init.headers ?? {});
|
|
648
|
-
for (const [k, v] of Object.entries(this.defaultHeaders)) {
|
|
649
|
-
if (!headers.has(k)) headers.set(k, v);
|
|
650
|
-
}
|
|
651
|
-
if (!headers.has("Authorization")) {
|
|
652
|
-
const token = this.userToken ?? this.anonKey;
|
|
653
|
-
if (token) headers.set("Authorization", `Bearer ${token}`);
|
|
654
|
-
}
|
|
655
|
-
return this.fetch(url, { ...init, headers });
|
|
656
|
-
}
|
|
657
|
-
get(path, options) {
|
|
658
|
-
return this.request("GET", path, options);
|
|
659
|
-
}
|
|
660
|
-
post(path, body, options) {
|
|
661
|
-
return this.request("POST", path, { ...options, body });
|
|
662
|
-
}
|
|
663
|
-
put(path, body, options) {
|
|
664
|
-
return this.request("PUT", path, { ...options, body });
|
|
665
|
-
}
|
|
666
|
-
patch(path, body, options) {
|
|
667
|
-
return this.request("PATCH", path, { ...options, body });
|
|
668
|
-
}
|
|
669
|
-
delete(path, options) {
|
|
670
|
-
return this.request("DELETE", path, options);
|
|
671
|
-
}
|
|
672
|
-
setAuthToken(token) {
|
|
673
|
-
this.userToken = token;
|
|
674
|
-
}
|
|
675
|
-
setRefreshToken(token) {
|
|
676
|
-
this.refreshToken = token;
|
|
677
|
-
}
|
|
678
|
-
getHeaders() {
|
|
679
|
-
const headers = { ...this.defaultHeaders };
|
|
680
|
-
const authToken = this.userToken || this.anonKey;
|
|
681
|
-
if (authToken) {
|
|
682
|
-
headers["Authorization"] = `Bearer ${authToken}`;
|
|
683
|
-
}
|
|
684
|
-
return headers;
|
|
685
|
-
}
|
|
686
|
-
/**
|
|
687
|
-
* Refresh the current session by calling the MITWAY-BaaS refresh endpoint.
|
|
688
|
-
* Note: the route is `/api/auth/refresh` (POST), not the InsForge
|
|
689
|
-
* `/api/auth/sessions/current` route. Returns the new access token + user.
|
|
690
|
-
*/
|
|
691
|
-
async handleTokenRefresh() {
|
|
692
|
-
if (this.isRefreshing) {
|
|
693
|
-
return this.refreshPromise;
|
|
694
|
-
}
|
|
695
|
-
this.isRefreshing = true;
|
|
696
|
-
this.refreshPromise = (async () => {
|
|
697
|
-
try {
|
|
698
|
-
const csrfToken = getCsrfToken();
|
|
699
|
-
const body = this.refreshToken ? { refreshToken: this.refreshToken } : void 0;
|
|
700
|
-
const response = await this.handleRequest(
|
|
701
|
-
"POST",
|
|
702
|
-
"/api/auth/refresh",
|
|
703
|
-
{
|
|
704
|
-
body,
|
|
705
|
-
headers: csrfToken ? { "X-CSRF-Token": csrfToken } : {},
|
|
706
|
-
credentials: "include"
|
|
707
|
-
}
|
|
708
|
-
);
|
|
709
|
-
return normalizeAuthPayload(response);
|
|
710
|
-
} finally {
|
|
711
|
-
this.isRefreshing = false;
|
|
712
|
-
this.refreshPromise = null;
|
|
713
|
-
}
|
|
714
|
-
})();
|
|
715
|
-
return this.refreshPromise;
|
|
716
|
-
}
|
|
717
|
-
};
|
|
718
|
-
|
|
719
|
-
// src/modules/auth.ts
|
|
720
|
-
function wrapError(error, fallbackMessage) {
|
|
721
|
-
if (error instanceof MitwayBaasError) {
|
|
722
|
-
return { data: null, error };
|
|
723
|
-
}
|
|
724
|
-
return {
|
|
725
|
-
data: null,
|
|
726
|
-
error: new MitwayBaasError(
|
|
727
|
-
error instanceof Error ? error.message : fallbackMessage,
|
|
728
|
-
500,
|
|
729
|
-
"AUTH_ERROR"
|
|
730
|
-
)
|
|
731
|
-
};
|
|
732
|
-
}
|
|
733
|
-
var Auth = class {
|
|
734
|
-
constructor(http, tokenManager) {
|
|
735
|
-
this.http = http;
|
|
736
|
-
this.tokenManager = tokenManager;
|
|
737
|
-
}
|
|
738
|
-
http;
|
|
739
|
-
tokenManager;
|
|
740
|
-
/**
|
|
741
|
-
* Persist the session in memory + HttpClient defaults so subsequent
|
|
742
|
-
* requests carry the new bearer token automatically.
|
|
743
|
-
*/
|
|
744
|
-
saveSessionFromResponse(response) {
|
|
745
|
-
const session = {
|
|
746
|
-
accessToken: response.accessToken,
|
|
747
|
-
refreshToken: response.refreshToken,
|
|
748
|
-
user: response.user
|
|
749
|
-
};
|
|
750
|
-
if (response.csrfToken) {
|
|
751
|
-
setCsrfToken(response.csrfToken);
|
|
752
|
-
}
|
|
753
|
-
this.tokenManager.saveSession(session);
|
|
754
|
-
this.http.setAuthToken(response.accessToken);
|
|
755
|
-
this.http.setRefreshToken(response.refreshToken ?? null);
|
|
756
|
-
}
|
|
757
|
-
/**
|
|
758
|
-
* Create a new user account and start a session.
|
|
759
|
-
*
|
|
760
|
-
* @example
|
|
761
|
-
* const { data, error } = await client.auth.signUp({
|
|
762
|
-
* email: 'a@b.com',
|
|
763
|
-
* password: 'a-strong-password',
|
|
764
|
-
* name: 'Alice'
|
|
765
|
-
* });
|
|
766
|
-
*/
|
|
767
|
-
async signUp(request) {
|
|
768
|
-
try {
|
|
769
|
-
const raw = await this.http.post(
|
|
770
|
-
"/api/auth/register",
|
|
771
|
-
request,
|
|
772
|
-
{ credentials: "include" }
|
|
773
|
-
);
|
|
774
|
-
const response = normalizeAuthPayload(raw);
|
|
775
|
-
if (response?.accessToken && response.user) {
|
|
776
|
-
this.saveSessionFromResponse(response);
|
|
777
|
-
}
|
|
778
|
-
return { data: response, error: null };
|
|
779
|
-
} catch (error) {
|
|
780
|
-
return wrapError(error, "Sign up failed");
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
/**
|
|
784
|
-
* Sign in with email + password and start a session.
|
|
785
|
-
*/
|
|
786
|
-
async signInWithPassword(request) {
|
|
787
|
-
try {
|
|
788
|
-
const raw = await this.http.post(
|
|
789
|
-
"/api/auth/login",
|
|
790
|
-
request,
|
|
791
|
-
{ credentials: "include" }
|
|
792
|
-
);
|
|
793
|
-
const response = normalizeAuthPayload(raw);
|
|
794
|
-
if (response?.accessToken && response.user) {
|
|
795
|
-
this.saveSessionFromResponse(response);
|
|
796
|
-
}
|
|
797
|
-
return { data: response, error: null };
|
|
798
|
-
} catch (error) {
|
|
799
|
-
return wrapError(error, "Sign in failed");
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
/**
|
|
803
|
-
* End the current session. Clears in-memory state even if the backend
|
|
804
|
-
* call fails (network/offline).
|
|
805
|
-
*/
|
|
806
|
-
async signOut() {
|
|
807
|
-
try {
|
|
808
|
-
try {
|
|
809
|
-
await this.http.post("/api/auth/logout", void 0, {
|
|
810
|
-
credentials: "include"
|
|
811
|
-
});
|
|
812
|
-
} catch {
|
|
813
|
-
}
|
|
814
|
-
this.tokenManager.clearSession();
|
|
815
|
-
this.http.setAuthToken(null);
|
|
816
|
-
this.http.setRefreshToken(null);
|
|
817
|
-
clearCsrfToken();
|
|
818
|
-
return { error: null };
|
|
819
|
-
} catch {
|
|
820
|
-
return {
|
|
821
|
-
error: new MitwayBaasError("Failed to sign out", 500, "SIGNOUT_ERROR")
|
|
822
|
-
};
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
/**
|
|
826
|
-
* Manually refresh the current session. The HttpClient will call this
|
|
827
|
-
* automatically on 401 INVALID_TOKEN responses; consumers usually do
|
|
828
|
-
* not need to call it directly.
|
|
829
|
-
*/
|
|
830
|
-
async refreshSession() {
|
|
831
|
-
try {
|
|
832
|
-
const response = await this.http.handleTokenRefresh();
|
|
833
|
-
if (response?.accessToken && response.user) {
|
|
834
|
-
this.saveSessionFromResponse(response);
|
|
835
|
-
}
|
|
836
|
-
return { data: response, error: null };
|
|
837
|
-
} catch (error) {
|
|
838
|
-
return wrapError(error, "Session refresh failed");
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
/**
|
|
842
|
-
* Restore the session from localStorage and validate it with the backend.
|
|
843
|
-
* Call this once on app startup (e.g. in a React AuthProvider useEffect).
|
|
844
|
-
*
|
|
845
|
-
* Flow:
|
|
846
|
-
* 1. Read persisted session from localStorage.
|
|
847
|
-
* 2. Populate in-memory state (TokenManager + HttpClient).
|
|
848
|
-
* 3. Validate with `GET /api/auth/sessions/current`.
|
|
849
|
-
* - If the access token expired, the HttpClient auto-refresh kicks in
|
|
850
|
-
* using the persisted refresh token (sent in the POST body, not
|
|
851
|
-
* cookies — works cross-site).
|
|
852
|
-
* 4. Return the validated user or an error.
|
|
853
|
-
*
|
|
854
|
-
* If no persisted session exists, returns `{ data: null, error }` — the
|
|
855
|
-
* app should show the login page.
|
|
856
|
-
*/
|
|
857
|
-
async initialize() {
|
|
858
|
-
const restored = this.tokenManager.restoreSession();
|
|
859
|
-
if (!restored) {
|
|
860
|
-
return {
|
|
861
|
-
data: null,
|
|
862
|
-
error: new MitwayBaasError("No persisted session", 0, "NO_SESSION")
|
|
863
|
-
};
|
|
864
|
-
}
|
|
865
|
-
const session = this.tokenManager.getSession();
|
|
866
|
-
if (!session) {
|
|
867
|
-
return {
|
|
868
|
-
data: null,
|
|
869
|
-
error: new MitwayBaasError("No persisted session", 0, "NO_SESSION")
|
|
870
|
-
};
|
|
871
|
-
}
|
|
872
|
-
this.http.setAuthToken(session.accessToken);
|
|
873
|
-
const refreshToken = this.tokenManager.getRefreshToken();
|
|
874
|
-
if (refreshToken) {
|
|
875
|
-
this.http.setRefreshToken(refreshToken);
|
|
876
|
-
}
|
|
877
|
-
try {
|
|
878
|
-
const response = await this.http.get(
|
|
879
|
-
"/api/auth/sessions/current"
|
|
880
|
-
);
|
|
881
|
-
if (response?.user) {
|
|
882
|
-
this.tokenManager.setUser(response.user);
|
|
883
|
-
return {
|
|
884
|
-
data: {
|
|
885
|
-
user: response.user,
|
|
886
|
-
accessToken: session.accessToken
|
|
887
|
-
},
|
|
888
|
-
error: null
|
|
889
|
-
};
|
|
890
|
-
}
|
|
891
|
-
this.tokenManager.clearSession();
|
|
892
|
-
this.http.setAuthToken(null);
|
|
893
|
-
this.http.setRefreshToken(null);
|
|
894
|
-
return {
|
|
895
|
-
data: null,
|
|
896
|
-
error: new MitwayBaasError("Invalid session", 401, "INVALID_SESSION")
|
|
897
|
-
};
|
|
898
|
-
} catch (error) {
|
|
899
|
-
this.tokenManager.clearSession();
|
|
900
|
-
this.http.setAuthToken(null);
|
|
901
|
-
this.http.setRefreshToken(null);
|
|
902
|
-
return wrapError(error, "Session restore failed");
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
/**
|
|
906
|
-
* Get the current in-memory session, or null if the user is not signed in.
|
|
907
|
-
* Synchronous — does not hit the network.
|
|
908
|
-
*/
|
|
909
|
-
getSession() {
|
|
910
|
-
return this.tokenManager.getSession();
|
|
911
|
-
}
|
|
912
|
-
/**
|
|
913
|
-
* Get the current in-memory user, or null if not signed in.
|
|
914
|
-
*/
|
|
915
|
-
getUser() {
|
|
916
|
-
return this.tokenManager.getUser();
|
|
917
|
-
}
|
|
918
|
-
/**
|
|
919
|
-
* Fetch the current user from the backend. Unlike getUser() which reads
|
|
920
|
-
* from memory, this makes a network request and returns the latest data.
|
|
921
|
-
*/
|
|
922
|
-
async getCurrentUser() {
|
|
923
|
-
try {
|
|
924
|
-
const response = await this.http.get(
|
|
925
|
-
"/api/auth/sessions/current"
|
|
926
|
-
);
|
|
927
|
-
if (response?.user) {
|
|
928
|
-
const session = {
|
|
929
|
-
accessToken: this.tokenManager.getSession()?.accessToken ?? "",
|
|
930
|
-
user: response.user
|
|
931
|
-
};
|
|
932
|
-
this.tokenManager.saveSession(session);
|
|
933
|
-
}
|
|
934
|
-
return { data: response, error: null };
|
|
935
|
-
} catch (error) {
|
|
936
|
-
return wrapError(error, "Failed to get current user");
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
/**
|
|
940
|
-
* Get a user's profile by ID. Requires authentication.
|
|
941
|
-
*/
|
|
942
|
-
async getProfile(userId) {
|
|
943
|
-
try {
|
|
944
|
-
const response = await this.http.get(`/api/auth/profiles/${encodeURIComponent(userId)}`);
|
|
945
|
-
return { data: response, error: null };
|
|
946
|
-
} catch (error) {
|
|
947
|
-
return wrapError(
|
|
948
|
-
error,
|
|
949
|
-
"Failed to get profile"
|
|
950
|
-
);
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
/**
|
|
954
|
-
* Update the current user's profile. Merges with existing profile data —
|
|
955
|
-
* only the fields you pass are updated, existing fields are preserved.
|
|
956
|
-
*/
|
|
957
|
-
async setProfile(profile) {
|
|
958
|
-
try {
|
|
959
|
-
const response = await this.http.patch("/api/auth/profiles/current", { profile });
|
|
960
|
-
return { data: response, error: null };
|
|
961
|
-
} catch (error) {
|
|
962
|
-
return wrapError(
|
|
963
|
-
error,
|
|
964
|
-
"Failed to update profile"
|
|
965
|
-
);
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
};
|
|
969
|
-
|
|
970
|
-
// src/modules/database.ts
|
|
971
|
-
var import_postgrest_js = require("@supabase/postgrest-js");
|
|
972
|
-
function createMitwayBaasFetch(httpClient, tokenManager, anonKey) {
|
|
973
|
-
return async (input, init) => {
|
|
974
|
-
const url = typeof input === "string" ? input : input.toString();
|
|
975
|
-
const urlObj = new URL(url);
|
|
976
|
-
const pathname = urlObj.pathname.startsWith("/") ? urlObj.pathname.slice(1) : urlObj.pathname;
|
|
977
|
-
const rpcMatch = pathname.match(/^rpc\/(.+)$/);
|
|
978
|
-
const endpoint = rpcMatch ? `/api/database/rpc/${rpcMatch[1]}` : `/api/database/records/${pathname}`;
|
|
979
|
-
const targetUrl = `${httpClient.baseUrl}${endpoint}${urlObj.search}`;
|
|
980
|
-
const headers = new Headers(init?.headers);
|
|
981
|
-
if (!headers.has("Authorization")) {
|
|
982
|
-
const token = tokenManager.getAccessToken() ?? anonKey;
|
|
983
|
-
if (token) {
|
|
984
|
-
headers.set("Authorization", `Bearer ${token}`);
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
return fetch(targetUrl, { ...init, headers });
|
|
988
|
-
};
|
|
989
|
-
}
|
|
990
|
-
var Database = class {
|
|
991
|
-
postgrest;
|
|
992
|
-
httpClient;
|
|
993
|
-
constructor(httpClient, tokenManager, anonKey) {
|
|
994
|
-
this.httpClient = httpClient;
|
|
995
|
-
this.postgrest = new import_postgrest_js.PostgrestClient("http://dummy", {
|
|
996
|
-
fetch: createMitwayBaasFetch(httpClient, tokenManager, anonKey),
|
|
997
|
-
headers: {}
|
|
998
|
-
});
|
|
999
|
-
}
|
|
1000
|
-
/**
|
|
1001
|
-
* Build a PostgREST query against a table.
|
|
1002
|
-
*
|
|
1003
|
-
* @example
|
|
1004
|
-
* const { data, error } = await client.database
|
|
1005
|
-
* .from('posts')
|
|
1006
|
-
* .select('*')
|
|
1007
|
-
* .eq('user_id', userId)
|
|
1008
|
-
* .order('created_at', { ascending: false })
|
|
1009
|
-
* .limit(10);
|
|
1010
|
-
*
|
|
1011
|
-
* @example
|
|
1012
|
-
* const { data, error } = await client.database
|
|
1013
|
-
* .from('posts')
|
|
1014
|
-
* .insert({ title: 'Hello', content: 'World' })
|
|
1015
|
-
* .select()
|
|
1016
|
-
* .single();
|
|
1017
|
-
*/
|
|
1018
|
-
from(table) {
|
|
1019
|
-
if (!table || typeof table !== "string") {
|
|
1020
|
-
throw new MitwayBaasError(
|
|
1021
|
-
"Database.from(table) requires a non-empty string",
|
|
1022
|
-
400,
|
|
1023
|
-
"INVALID_TABLE_NAME"
|
|
1024
|
-
);
|
|
1025
|
-
}
|
|
1026
|
-
return this.postgrest.from(table);
|
|
1027
|
-
}
|
|
1028
|
-
/**
|
|
1029
|
-
* Call a PostgreSQL stored function (RPC).
|
|
1030
|
-
*
|
|
1031
|
-
* @example
|
|
1032
|
-
* const { data, error } = await client.database
|
|
1033
|
-
* .rpc('get_user_stats', { user_id: 123 });
|
|
1034
|
-
*/
|
|
1035
|
-
rpc(fn, args, options) {
|
|
1036
|
-
return this.postgrest.rpc(fn, args, options);
|
|
1037
|
-
}
|
|
1038
|
-
/**
|
|
1039
|
-
* The backend base URL the database client is targeting. Useful for
|
|
1040
|
-
* debugging — the actual PostgREST instance is internal and not
|
|
1041
|
-
* reachable by the SDK directly.
|
|
1042
|
-
*/
|
|
1043
|
-
getUrl() {
|
|
1044
|
-
return this.httpClient.baseUrl;
|
|
1045
|
-
}
|
|
1046
|
-
};
|
|
1047
|
-
|
|
1048
|
-
// src/modules/realtime.ts
|
|
1049
|
-
var import_socket = require("socket.io-client");
|
|
1050
|
-
var PRESENCE_HEARTBEAT_MS = 2e4;
|
|
1051
|
-
function makeChannelError(code, message) {
|
|
1052
|
-
const err = new Error(message);
|
|
1053
|
-
err.code = code;
|
|
1054
|
-
return err;
|
|
1055
|
-
}
|
|
1056
|
-
var DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
|
|
1057
|
-
var RealtimeChannel = class {
|
|
1058
|
-
constructor(topic, realtime, options = {}) {
|
|
1059
|
-
this.topic = topic;
|
|
1060
|
-
this.realtime = realtime;
|
|
1061
|
-
this.options = options;
|
|
1062
|
-
}
|
|
1063
|
-
topic;
|
|
1064
|
-
realtime;
|
|
1065
|
-
bindings = [];
|
|
1066
|
-
state = "closed";
|
|
1067
|
-
statusCallback = null;
|
|
1068
|
-
/** Local presence state mirror — populated from `presence_state` /
|
|
1069
|
-
* `presence_join` / `presence_leave` events. Read via `presenceState()`. */
|
|
1070
|
-
presence = {};
|
|
1071
|
-
/** Latest state this client has tracked. Non-null means the heartbeat
|
|
1072
|
-
* timer is active and we'll re-emit this state every TTL/2. */
|
|
1073
|
-
trackedState = null;
|
|
1074
|
-
presenceHeartbeat = null;
|
|
1075
|
-
/** Timestamp of the most recently received broadcast on this channel —
|
|
1076
|
-
* used as the `since` anchor on replay after reconnect. ISO-8601. */
|
|
1077
|
-
lastBroadcastTimestamp = null;
|
|
1078
|
-
/** Configuration from `channel(topic, opts)`. Frozen at construction. */
|
|
1079
|
-
options;
|
|
1080
|
-
/** Whether this channel was opened as `private: true`. */
|
|
1081
|
-
get isPrivate() {
|
|
1082
|
-
return this.options.config?.private === true;
|
|
1083
|
-
}
|
|
1084
|
-
/** The user-supplied presence key, if any. */
|
|
1085
|
-
get presenceKey() {
|
|
1086
|
-
return this.options.config?.presence?.key;
|
|
1087
|
-
}
|
|
1088
|
-
/** Internal — exposed for Realtime to drive resubscription after a
|
|
1089
|
-
* network hiccup. Returns the current lifecycle state. */
|
|
1090
|
-
_state() {
|
|
1091
|
-
return this.state;
|
|
1092
|
-
}
|
|
1093
|
-
/** Internal — called by `Realtime` when Socket.IO reconnects after a
|
|
1094
|
-
* drop. Re-runs the registration flow; the backend assigns fresh
|
|
1095
|
-
* subscription_ids and Socket.IO rejoins the per-subscription rooms,
|
|
1096
|
-
* so events resume without developer intervention. The user-provided
|
|
1097
|
-
* statusCallback (from the original subscribe()) fires again with
|
|
1098
|
-
* 'SUBSCRIBED' or 'CHANNEL_ERROR' so the app can reflect state. */
|
|
1099
|
-
async _rejoinAfterReconnect() {
|
|
1100
|
-
if (this.state === "closed") {
|
|
1101
|
-
return;
|
|
1102
|
-
}
|
|
1103
|
-
for (const b of this.bindings) {
|
|
1104
|
-
if (b.type === "postgres_changes") {
|
|
1105
|
-
b.subscriptionId = void 0;
|
|
1106
|
-
}
|
|
1107
|
-
}
|
|
1108
|
-
this.state = "joining";
|
|
1109
|
-
try {
|
|
1110
|
-
await this.registerAllBindings();
|
|
1111
|
-
if (this.trackedState) {
|
|
1112
|
-
const socket = this.realtime._getSocket();
|
|
1113
|
-
const key = this.presenceKey;
|
|
1114
|
-
socket?.emit(
|
|
1115
|
-
"realtime:presence:track",
|
|
1116
|
-
key !== void 0 ? { channel: this.topic, state: this.trackedState, key } : { channel: this.topic, state: this.trackedState }
|
|
1117
|
-
);
|
|
1118
|
-
}
|
|
1119
|
-
if (this.lastBroadcastTimestamp && this.bindings.some((b) => b.type === "broadcast")) {
|
|
1120
|
-
void this.replay({ since: this.lastBroadcastTimestamp }).catch(() => void 0);
|
|
1121
|
-
}
|
|
1122
|
-
this.state = "joined";
|
|
1123
|
-
this.statusCallback?.("SUBSCRIBED");
|
|
1124
|
-
} catch (err) {
|
|
1125
|
-
this.state = "errored";
|
|
1126
|
-
this.statusCallback?.(
|
|
1127
|
-
"CHANNEL_ERROR",
|
|
1128
|
-
makeChannelError("REJOIN_FAILED", err instanceof Error ? err.message : String(err))
|
|
1129
|
-
);
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
// ── implementation signature (not in public type surface).
|
|
1133
|
-
// The callback type is intentionally broad: each overload above pins a
|
|
1134
|
-
// specific payload shape, but TypeScript overload resolution needs the
|
|
1135
|
-
// implementation to accept the union of every narrow callback without
|
|
1136
|
-
// the contravariance conflict (TS2394). `any` is the standard escape
|
|
1137
|
-
// hatch for this exact pattern and is confined to this one line — the
|
|
1138
|
-
// public surface users see is strictly typed via the overloads.
|
|
1139
|
-
on(type, filter, callback) {
|
|
1140
|
-
if (type === "postgres_changes") {
|
|
1141
|
-
this.bindings.push({
|
|
1142
|
-
type: "postgres_changes",
|
|
1143
|
-
filter,
|
|
1144
|
-
callback
|
|
1145
|
-
});
|
|
1146
|
-
} else if (type === "broadcast") {
|
|
1147
|
-
this.bindings.push({
|
|
1148
|
-
type: "broadcast",
|
|
1149
|
-
filter,
|
|
1150
|
-
callback
|
|
1151
|
-
});
|
|
1152
|
-
} else {
|
|
1153
|
-
this.bindings.push({
|
|
1154
|
-
type: "presence",
|
|
1155
|
-
filter,
|
|
1156
|
-
callback
|
|
1157
|
-
});
|
|
1158
|
-
}
|
|
1159
|
-
return this;
|
|
1160
|
-
}
|
|
1161
|
-
// -------------------------------------------------------------------------
|
|
1162
|
-
// Presence — track / untrack / state accessor
|
|
1163
|
-
// -------------------------------------------------------------------------
|
|
1164
|
-
/**
|
|
1165
|
-
* Register or refresh this client's presence entry on the channel. Safe
|
|
1166
|
-
* to call many times — state replaces (not merges). Starts a heartbeat
|
|
1167
|
-
* timer at TTL/2 so the entry stays alive while the socket is open.
|
|
1168
|
-
* The channel must be `subscribe()`d first (the server enforces this).
|
|
1169
|
-
*/
|
|
1170
|
-
async track(state) {
|
|
1171
|
-
const socket = this.realtime._getSocket();
|
|
1172
|
-
if (!socket) {
|
|
1173
|
-
throw new MitwayBaasError("Socket not connected", 503, "NOT_CONNECTED");
|
|
1174
|
-
}
|
|
1175
|
-
this.trackedState = state;
|
|
1176
|
-
const key = this.presenceKey;
|
|
1177
|
-
socket.emit(
|
|
1178
|
-
"realtime:presence:track",
|
|
1179
|
-
key !== void 0 ? { channel: this.topic, state, key } : { channel: this.topic, state }
|
|
1180
|
-
);
|
|
1181
|
-
if (!this.presenceHeartbeat) {
|
|
1182
|
-
this.presenceHeartbeat = setInterval(() => {
|
|
1183
|
-
const s = this.realtime._getSocket();
|
|
1184
|
-
if (s && this.trackedState) {
|
|
1185
|
-
s.emit(
|
|
1186
|
-
"realtime:presence:track",
|
|
1187
|
-
key !== void 0 ? { channel: this.topic, state: this.trackedState, key } : { channel: this.topic, state: this.trackedState }
|
|
1188
|
-
);
|
|
1189
|
-
}
|
|
1190
|
-
}, PRESENCE_HEARTBEAT_MS);
|
|
1191
|
-
const h = this.presenceHeartbeat;
|
|
1192
|
-
h.unref?.();
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
/**
|
|
1196
|
-
* Remove this client's presence entry immediately, stopping the
|
|
1197
|
-
* heartbeat. Safe if the client never called `track`.
|
|
1198
|
-
*/
|
|
1199
|
-
untrack() {
|
|
1200
|
-
this.trackedState = null;
|
|
1201
|
-
if (this.presenceHeartbeat) {
|
|
1202
|
-
clearInterval(this.presenceHeartbeat);
|
|
1203
|
-
this.presenceHeartbeat = null;
|
|
1204
|
-
}
|
|
1205
|
-
const socket = this.realtime._getSocket();
|
|
1206
|
-
socket?.emit("realtime:presence:untrack", { channel: this.topic });
|
|
1207
|
-
}
|
|
1208
|
-
/** Snapshot of the current presence state on this channel. Keys are
|
|
1209
|
-
* opaque client identifiers (server-assigned). Re-read inside your
|
|
1210
|
-
* `.on('presence', { event: 'sync' }, ...)` handler. */
|
|
1211
|
-
presenceState() {
|
|
1212
|
-
return this.presence;
|
|
1213
|
-
}
|
|
1214
|
-
/**
|
|
1215
|
-
* Register all bindings with the server:
|
|
1216
|
-
* * For each `broadcast` binding, ensure we're subscribed to the topic
|
|
1217
|
-
* (one `realtime:subscribe` for the channel as a whole).
|
|
1218
|
-
* * For each `postgres_changes` binding, emit
|
|
1219
|
-
* `realtime:postgres_changes:subscribe` and record the assigned
|
|
1220
|
-
* subscription_id.
|
|
1221
|
-
*
|
|
1222
|
-
* `statusCallback` is invoked with `'SUBSCRIBED'` when every binding
|
|
1223
|
-
* has ack'd, or with `'CHANNEL_ERROR' | 'TIMED_OUT'` on failure.
|
|
1224
|
-
*/
|
|
1225
|
-
subscribe(statusCallback) {
|
|
1226
|
-
this.statusCallback = statusCallback ?? null;
|
|
1227
|
-
if (this.state === "joining" || this.state === "joined") {
|
|
1228
|
-
return this;
|
|
1229
|
-
}
|
|
1230
|
-
this.state = "joining";
|
|
1231
|
-
void this.realtime.connect().then(
|
|
1232
|
-
async () => {
|
|
1233
|
-
try {
|
|
1234
|
-
await this.registerAllBindings();
|
|
1235
|
-
this.state = "joined";
|
|
1236
|
-
this.statusCallback?.("SUBSCRIBED");
|
|
1237
|
-
} catch (err) {
|
|
1238
|
-
this.state = "errored";
|
|
1239
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1240
|
-
this.statusCallback?.(
|
|
1241
|
-
"CHANNEL_ERROR",
|
|
1242
|
-
makeChannelError("SUBSCRIBE_FAILED", message)
|
|
1243
|
-
);
|
|
1244
|
-
}
|
|
1245
|
-
},
|
|
1246
|
-
(err) => {
|
|
1247
|
-
this.state = "errored";
|
|
1248
|
-
this.statusCallback?.(
|
|
1249
|
-
"CHANNEL_ERROR",
|
|
1250
|
-
makeChannelError("CONNECT_FAILED", err.message)
|
|
1251
|
-
);
|
|
1252
|
-
}
|
|
1253
|
-
);
|
|
1254
|
-
return this;
|
|
1255
|
-
}
|
|
1256
|
-
/**
|
|
1257
|
-
* Tear down every binding: unsubscribe from the broadcast topic (if
|
|
1258
|
-
* any broadcast bindings exist) and remove every postgres_changes
|
|
1259
|
-
* subscription by id. Safe to call when state is already closed.
|
|
1260
|
-
*/
|
|
1261
|
-
async unsubscribe() {
|
|
1262
|
-
if (this.state === "closed") {
|
|
1263
|
-
return;
|
|
1264
|
-
}
|
|
1265
|
-
const socket = this.realtime._getSocket();
|
|
1266
|
-
if (this.presenceHeartbeat) {
|
|
1267
|
-
clearInterval(this.presenceHeartbeat);
|
|
1268
|
-
this.presenceHeartbeat = null;
|
|
1269
|
-
}
|
|
1270
|
-
if (!socket) {
|
|
1271
|
-
this.trackedState = null;
|
|
1272
|
-
this.state = "closed";
|
|
1273
|
-
return;
|
|
1274
|
-
}
|
|
1275
|
-
if (this.trackedState) {
|
|
1276
|
-
socket.emit("realtime:presence:untrack", { channel: this.topic });
|
|
1277
|
-
this.trackedState = null;
|
|
1278
|
-
}
|
|
1279
|
-
const pcBindings = this.bindings.filter(
|
|
1280
|
-
(b) => b.type === "postgres_changes"
|
|
1281
|
-
);
|
|
1282
|
-
for (const b of pcBindings) {
|
|
1283
|
-
if (b.subscriptionId) {
|
|
1284
|
-
socket.emit("realtime:postgres_changes:unsubscribe", {
|
|
1285
|
-
subscription_id: b.subscriptionId
|
|
1286
|
-
});
|
|
1287
|
-
b.subscriptionId = void 0;
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
if (this.bindings.some((b) => b.type === "broadcast" || b.type === "presence")) {
|
|
1291
|
-
socket.emit("realtime:unsubscribe", { channel: this.topic });
|
|
1292
|
-
}
|
|
1293
|
-
this.realtime._detachChannel(this);
|
|
1294
|
-
this.state = "closed";
|
|
1295
|
-
this.statusCallback?.("CLOSED");
|
|
1296
|
-
}
|
|
1297
|
-
/**
|
|
1298
|
-
* Publish a broadcast event to the topic. Broadcasts are always
|
|
1299
|
-
* ephemeral — the server fans out to every subscribed socket in memory,
|
|
1300
|
-
* with no DB persistence or webhook fan-out. For audited / durable
|
|
1301
|
-
* events, write to your own application table and enable
|
|
1302
|
-
* `postgres_changes` on it; the SDK will surface the INSERT as a
|
|
1303
|
-
* `postgres_changes` event without a separate channel.send call.
|
|
1304
|
-
*
|
|
1305
|
-
* The returned promise always resolves with the server ack, so callers
|
|
1306
|
-
* can `await channel.send(...)` to confirm delivery + get the server-
|
|
1307
|
-
* assigned `message_id`. There's no performance cost — Socket.IO piggy-
|
|
1308
|
-
* backs the ack on the same frame. Callers that don't need it just
|
|
1309
|
-
* don't await.
|
|
1310
|
-
*/
|
|
1311
|
-
async send(args) {
|
|
1312
|
-
if (args.type !== "broadcast") {
|
|
1313
|
-
throw new MitwayBaasError(
|
|
1314
|
-
'Only "broadcast" sends are supported \u2014 DB changes flow via your DB writes, not channel.send()',
|
|
1315
|
-
400,
|
|
1316
|
-
"UNSUPPORTED_SEND_TYPE"
|
|
1317
|
-
);
|
|
1318
|
-
}
|
|
1319
|
-
const RESERVED_BROADCAST_NAMES = /* @__PURE__ */ new Set([
|
|
1320
|
-
"postgres_changes",
|
|
1321
|
-
"presence_state",
|
|
1322
|
-
"presence_join",
|
|
1323
|
-
"presence_leave"
|
|
1324
|
-
]);
|
|
1325
|
-
if (RESERVED_BROADCAST_NAMES.has(args.event)) {
|
|
1326
|
-
throw new MitwayBaasError(
|
|
1327
|
-
`"${args.event}" is a reserved event name \u2014 pick a different name for broadcast events`,
|
|
1328
|
-
400,
|
|
1329
|
-
"RESERVED_EVENT_NAME"
|
|
1330
|
-
);
|
|
1331
|
-
}
|
|
1332
|
-
const socket = this.realtime._getSocket();
|
|
1333
|
-
if (!socket) {
|
|
1334
|
-
throw new MitwayBaasError("Socket not connected", 503, "NOT_CONNECTED");
|
|
1335
|
-
}
|
|
1336
|
-
const self = this.options.config?.broadcast?.self;
|
|
1337
|
-
const wirePayload = {
|
|
1338
|
-
channel: this.topic,
|
|
1339
|
-
event: args.event,
|
|
1340
|
-
payload: args.payload
|
|
1341
|
-
};
|
|
1342
|
-
if (self === false) {
|
|
1343
|
-
wirePayload.self = false;
|
|
1344
|
-
}
|
|
1345
|
-
return await new Promise((resolve) => {
|
|
1346
|
-
socket.emit(
|
|
1347
|
-
"realtime:publish",
|
|
1348
|
-
wirePayload,
|
|
1349
|
-
(ack) => {
|
|
1350
|
-
resolve(ack);
|
|
1351
|
-
}
|
|
1352
|
-
);
|
|
1353
|
-
});
|
|
1354
|
-
}
|
|
1355
|
-
/**
|
|
1356
|
-
* Replay SQL-originated broadcasts on this topic since the given
|
|
1357
|
-
* timestamp. Delivered only to this socket (same envelope format as
|
|
1358
|
-
* live broadcasts; the SDK routes them through `.on('broadcast', ...)`
|
|
1359
|
-
* bindings just like the real-time path). Backend caps the window at
|
|
1360
|
-
* 24 h and the limit at 1000.
|
|
1361
|
-
*/
|
|
1362
|
-
async replay(args) {
|
|
1363
|
-
const socket = this.realtime._getSocket();
|
|
1364
|
-
if (!socket) {
|
|
1365
|
-
throw new MitwayBaasError("Socket not connected", 503, "NOT_CONNECTED");
|
|
1366
|
-
}
|
|
1367
|
-
socket.emit("realtime:broadcast:replay", {
|
|
1368
|
-
channel: this.topic,
|
|
1369
|
-
since: args.since,
|
|
1370
|
-
limit: args.limit,
|
|
1371
|
-
private: this.isPrivate
|
|
1372
|
-
});
|
|
1373
|
-
}
|
|
1374
|
-
/** Internal — called by Realtime's event router on every incoming event. */
|
|
1375
|
-
_dispatch(event, envelope) {
|
|
1376
|
-
if (event === "postgres_changes") {
|
|
1377
|
-
const pcEvent = envelope;
|
|
1378
|
-
for (const b of this.bindings) {
|
|
1379
|
-
if (b.type !== "postgres_changes") {
|
|
1380
|
-
continue;
|
|
1381
|
-
}
|
|
1382
|
-
if (!b.subscriptionId || !pcEvent.ids.includes(b.subscriptionId)) {
|
|
1383
|
-
continue;
|
|
1384
|
-
}
|
|
1385
|
-
const matchesEvent = b.filter.event === "*" || b.filter.event === pcEvent.data.eventType;
|
|
1386
|
-
if (!matchesEvent) {
|
|
1387
|
-
continue;
|
|
1388
|
-
}
|
|
1389
|
-
try {
|
|
1390
|
-
b.callback(pcEvent.data);
|
|
1391
|
-
} catch {
|
|
1392
|
-
}
|
|
1393
|
-
}
|
|
1394
|
-
return;
|
|
1395
|
-
}
|
|
1396
|
-
if (event === "presence_state" || event === "presence_join" || event === "presence_leave") {
|
|
1397
|
-
const e = envelope;
|
|
1398
|
-
if (e.channel !== this.topic) {
|
|
1399
|
-
return;
|
|
1400
|
-
}
|
|
1401
|
-
if (event === "presence_state" && e.state) {
|
|
1402
|
-
this.presence = { ...e.state };
|
|
1403
|
-
this.firePresence({ event: "sync", state: this.presence });
|
|
1404
|
-
} else if (event === "presence_join" && e.joins) {
|
|
1405
|
-
Object.assign(this.presence, e.joins);
|
|
1406
|
-
this.firePresence({ event: "join", joins: e.joins });
|
|
1407
|
-
} else if (event === "presence_leave" && e.leaves) {
|
|
1408
|
-
for (const key of Object.keys(e.leaves)) {
|
|
1409
|
-
delete this.presence[key];
|
|
1410
|
-
}
|
|
1411
|
-
this.firePresence({ event: "leave", leaves: e.leaves });
|
|
1412
|
-
}
|
|
1413
|
-
return;
|
|
1414
|
-
}
|
|
1415
|
-
const meta = envelope.meta;
|
|
1416
|
-
if (meta?.timestamp) {
|
|
1417
|
-
if (!this.lastBroadcastTimestamp || meta.timestamp > this.lastBroadcastTimestamp) {
|
|
1418
|
-
this.lastBroadcastTimestamp = meta.timestamp;
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
for (const b of this.bindings) {
|
|
1422
|
-
if (b.type !== "broadcast") {
|
|
1423
|
-
continue;
|
|
1424
|
-
}
|
|
1425
|
-
if (b.filter.event !== event) {
|
|
1426
|
-
continue;
|
|
1427
|
-
}
|
|
1428
|
-
const { meta: _meta, ...payload } = envelope;
|
|
1429
|
-
try {
|
|
1430
|
-
b.callback({ type: "broadcast", event, payload });
|
|
1431
|
-
} catch {
|
|
1432
|
-
}
|
|
1433
|
-
}
|
|
1434
|
-
}
|
|
1435
|
-
firePresence(payload) {
|
|
1436
|
-
for (const b of this.bindings) {
|
|
1437
|
-
if (b.type !== "presence") {
|
|
1438
|
-
continue;
|
|
1439
|
-
}
|
|
1440
|
-
if (b.filter.event !== payload.event) {
|
|
1441
|
-
continue;
|
|
1442
|
-
}
|
|
1443
|
-
try {
|
|
1444
|
-
if (payload.event === "sync") {
|
|
1445
|
-
b.callback();
|
|
1446
|
-
} else if (payload.event === "join") {
|
|
1447
|
-
b.callback(payload);
|
|
1448
|
-
} else {
|
|
1449
|
-
b.callback(payload);
|
|
1450
|
-
}
|
|
1451
|
-
} catch {
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
|
-
async registerAllBindings() {
|
|
1456
|
-
const socket = this.realtime._getSocket();
|
|
1457
|
-
if (!socket) {
|
|
1458
|
-
throw new Error("Socket not available");
|
|
1459
|
-
}
|
|
1460
|
-
const needsRoomJoin = this.bindings.some(
|
|
1461
|
-
(b) => b.type === "broadcast" || b.type === "presence"
|
|
1462
|
-
);
|
|
1463
|
-
if (needsRoomJoin) {
|
|
1464
|
-
await new Promise((resolve, reject) => {
|
|
1465
|
-
socket.emit(
|
|
1466
|
-
"realtime:subscribe",
|
|
1467
|
-
{ channel: this.topic, private: this.isPrivate },
|
|
1468
|
-
(ack) => {
|
|
1469
|
-
if (ack.status === "ok") {
|
|
1470
|
-
resolve();
|
|
1471
|
-
} else {
|
|
1472
|
-
reject(new Error(ack.error?.message ?? "subscribe failed"));
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
);
|
|
1476
|
-
});
|
|
1477
|
-
}
|
|
1478
|
-
const pcBindings = this.bindings.filter(
|
|
1479
|
-
(b) => b.type === "postgres_changes"
|
|
1480
|
-
);
|
|
1481
|
-
await Promise.all(
|
|
1482
|
-
pcBindings.map(
|
|
1483
|
-
(b) => new Promise((resolve, reject) => {
|
|
1484
|
-
socket.emit(
|
|
1485
|
-
"realtime:postgres_changes:subscribe",
|
|
1486
|
-
{
|
|
1487
|
-
event: b.filter.event,
|
|
1488
|
-
schema: b.filter.schema ?? "public",
|
|
1489
|
-
table: b.filter.table,
|
|
1490
|
-
filter: b.filter.filter
|
|
1491
|
-
},
|
|
1492
|
-
(ack) => {
|
|
1493
|
-
if (ack.status === "ok" && ack.subscription_id) {
|
|
1494
|
-
b.subscriptionId = ack.subscription_id;
|
|
1495
|
-
resolve();
|
|
1496
|
-
} else {
|
|
1497
|
-
reject(
|
|
1498
|
-
new Error(ack.error?.message ?? "postgres_changes subscribe failed")
|
|
1499
|
-
);
|
|
1500
|
-
}
|
|
1501
|
-
}
|
|
1502
|
-
);
|
|
1503
|
-
})
|
|
1504
|
-
)
|
|
1505
|
-
);
|
|
1506
|
-
}
|
|
1507
|
-
};
|
|
1508
|
-
var Realtime = class {
|
|
1509
|
-
socket = null;
|
|
1510
|
-
baseUrl;
|
|
1511
|
-
options;
|
|
1512
|
-
anonKey;
|
|
1513
|
-
tokenManager;
|
|
1514
|
-
channels = /* @__PURE__ */ new Map();
|
|
1515
|
-
connecting = null;
|
|
1516
|
-
/** Flips to `true` once the initial handshake resolves. Differentiates
|
|
1517
|
-
* the first `connect` event (part of `openSocket`) from subsequent
|
|
1518
|
-
* reconnect events (which should trigger auto-resubscribe). */
|
|
1519
|
-
firstConnected = false;
|
|
1520
|
-
constructor(baseUrl, tokenManager, anonKey, options = {}) {
|
|
1521
|
-
this.baseUrl = baseUrl;
|
|
1522
|
-
this.tokenManager = tokenManager;
|
|
1523
|
-
this.anonKey = anonKey;
|
|
1524
|
-
this.options = options;
|
|
1525
|
-
}
|
|
1526
|
-
get isConnected() {
|
|
1527
|
-
return this.socket?.connected === true;
|
|
1528
|
-
}
|
|
1529
|
-
get socketId() {
|
|
1530
|
-
return this.socket?.id;
|
|
1531
|
-
}
|
|
1532
|
-
/**
|
|
1533
|
-
* Get (or create) a channel for `topic`. Channels are cached so multiple
|
|
1534
|
-
* `.channel('same')` calls return the same instance.
|
|
1535
|
-
*
|
|
1536
|
-
* The optional `opts` argument lets the caller configure the channel:
|
|
1537
|
-
* * `config.private` — enable subscribe-side authorization against
|
|
1538
|
-
* `realtime.authorize_subscribe(...)` on the tenant DB.
|
|
1539
|
-
* * `config.broadcast.self` — `false` excludes the sender from the
|
|
1540
|
-
* fan-out (defaults to `true`).
|
|
1541
|
-
* * `config.presence.key` — stable presence key to group multiple
|
|
1542
|
-
* tabs of the same user under one entry.
|
|
1543
|
-
*
|
|
1544
|
-
* `channel.send()` always resolves with the server ack (see its own
|
|
1545
|
-
* docstring); there is no separate opt-in needed.
|
|
1546
|
-
*
|
|
1547
|
-
* Options are locked in when the channel is first created; subsequent
|
|
1548
|
-
* `.channel('same')` calls with different opts are ignored. Pass a
|
|
1549
|
-
* different topic to get a different-configured channel.
|
|
1550
|
-
*/
|
|
1551
|
-
channel(topic, opts) {
|
|
1552
|
-
const existing = this.channels.get(topic);
|
|
1553
|
-
if (existing) {
|
|
1554
|
-
return existing;
|
|
1555
|
-
}
|
|
1556
|
-
const channel = new RealtimeChannel(topic, this, opts);
|
|
1557
|
-
this.channels.set(topic, channel);
|
|
1558
|
-
return channel;
|
|
1559
|
-
}
|
|
1560
|
-
connect() {
|
|
1561
|
-
if (this.isConnected) {
|
|
1562
|
-
return Promise.resolve();
|
|
1563
|
-
}
|
|
1564
|
-
if (this.connecting) {
|
|
1565
|
-
return this.connecting;
|
|
1566
|
-
}
|
|
1567
|
-
this.connecting = this.openSocket();
|
|
1568
|
-
return this.connecting;
|
|
1569
|
-
}
|
|
1570
|
-
/**
|
|
1571
|
-
* Close the socket. Channels are left as-is so they can re-subscribe
|
|
1572
|
-
* on the next `connect()` — useful for auth token refresh flows.
|
|
1573
|
-
*/
|
|
1574
|
-
disconnect() {
|
|
1575
|
-
if (!this.socket) {
|
|
1576
|
-
return;
|
|
1577
|
-
}
|
|
1578
|
-
this.socket.disconnect();
|
|
1579
|
-
this.socket = null;
|
|
1580
|
-
this.firstConnected = false;
|
|
1581
|
-
}
|
|
1582
|
-
// -------------------------------------------------------------------------
|
|
1583
|
-
// internals used by RealtimeChannel
|
|
1584
|
-
// -------------------------------------------------------------------------
|
|
1585
|
-
/* istanbul ignore next — tested via channel integration */
|
|
1586
|
-
_getSocket() {
|
|
1587
|
-
return this.socket;
|
|
1588
|
-
}
|
|
1589
|
-
_detachChannel(channel) {
|
|
1590
|
-
this.channels.delete(channel.topic);
|
|
1591
|
-
}
|
|
1592
|
-
openSocket() {
|
|
1593
|
-
const token = this.tokenManager.getAccessToken() ?? this.anonKey;
|
|
1594
|
-
if (!token) {
|
|
1595
|
-
const err = new MitwayBaasError(
|
|
1596
|
-
"Realtime requires an access token or anonKey",
|
|
1597
|
-
401,
|
|
1598
|
-
"AUTH_INVALID_API_KEY"
|
|
1599
|
-
);
|
|
1600
|
-
this.connecting = null;
|
|
1601
|
-
return Promise.reject(err);
|
|
1602
|
-
}
|
|
1603
|
-
const timeoutMs = this.options.timeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
|
|
1604
|
-
const socket = (0, import_socket.io)(this.baseUrl, {
|
|
1605
|
-
path: this.options.path,
|
|
1606
|
-
transports: this.options.transports ?? ["websocket"],
|
|
1607
|
-
auth: { token, ...this.options.extraAuth ?? {} },
|
|
1608
|
-
reconnection: true,
|
|
1609
|
-
timeout: timeoutMs
|
|
1610
|
-
});
|
|
1611
|
-
this.socket = socket;
|
|
1612
|
-
socket.onAny((event, ...args) => this.dispatch(event, args));
|
|
1613
|
-
socket.on("connect", () => {
|
|
1614
|
-
if (!this.firstConnected) {
|
|
1615
|
-
return;
|
|
1616
|
-
}
|
|
1617
|
-
for (const channel of this.channels.values()) {
|
|
1618
|
-
const state = channel._state();
|
|
1619
|
-
if (state === "joined" || state === "errored") {
|
|
1620
|
-
void channel._rejoinAfterReconnect();
|
|
1621
|
-
}
|
|
1622
|
-
}
|
|
1623
|
-
});
|
|
1624
|
-
return new Promise((resolve, reject) => {
|
|
1625
|
-
const timer = setTimeout(() => {
|
|
1626
|
-
socket.off("connect", onConnect);
|
|
1627
|
-
socket.off("connect_error", onConnectError);
|
|
1628
|
-
this.connecting = null;
|
|
1629
|
-
reject(
|
|
1630
|
-
new MitwayBaasError(
|
|
1631
|
-
`Realtime connection timeout after ${timeoutMs}ms`,
|
|
1632
|
-
408,
|
|
1633
|
-
"CONNECTION_TIMEOUT"
|
|
1634
|
-
)
|
|
1635
|
-
);
|
|
1636
|
-
}, timeoutMs);
|
|
1637
|
-
const clear = () => {
|
|
1638
|
-
clearTimeout(timer);
|
|
1639
|
-
socket.off("connect", onConnect);
|
|
1640
|
-
socket.off("connect_error", onConnectError);
|
|
1641
|
-
};
|
|
1642
|
-
const onConnect = () => {
|
|
1643
|
-
clear();
|
|
1644
|
-
this.connecting = null;
|
|
1645
|
-
this.firstConnected = true;
|
|
1646
|
-
resolve();
|
|
1647
|
-
};
|
|
1648
|
-
const onConnectError = (err) => {
|
|
1649
|
-
clear();
|
|
1650
|
-
this.connecting = null;
|
|
1651
|
-
reject(new MitwayBaasError(err.message, 0, "CONNECTION_FAILED"));
|
|
1652
|
-
};
|
|
1653
|
-
socket.once("connect", onConnect);
|
|
1654
|
-
socket.once("connect_error", onConnectError);
|
|
1655
|
-
});
|
|
1656
|
-
}
|
|
1657
|
-
dispatch(event, args) {
|
|
1658
|
-
if (event === "postgres_changes") {
|
|
1659
|
-
const envelope2 = args[0] ?? {};
|
|
1660
|
-
this.channels.forEach((ch) => ch._dispatch("postgres_changes", envelope2));
|
|
1661
|
-
return;
|
|
1662
|
-
}
|
|
1663
|
-
if (event === "connect" || event === "disconnect" || event === "connect_error" || event === "error" || event === "realtime:error" || event === "realtime:shutdown") {
|
|
1664
|
-
return;
|
|
1665
|
-
}
|
|
1666
|
-
const envelope = args[0] ?? {};
|
|
1667
|
-
this.channels.forEach((ch) => ch._dispatch(event, envelope));
|
|
1668
|
-
}
|
|
1669
|
-
};
|
|
1670
|
-
|
|
1671
|
-
// src/modules/storage.ts
|
|
1672
|
-
function bucketFromWire(row) {
|
|
1673
|
-
return {
|
|
1674
|
-
id: row.id,
|
|
1675
|
-
name: row.name,
|
|
1676
|
-
public: row.public,
|
|
1677
|
-
fileSizeLimitBytes: row.file_size_limit_bytes,
|
|
1678
|
-
allowedMimeTypes: row.allowed_mime_types,
|
|
1679
|
-
createdAt: row.created_at,
|
|
1680
|
-
updatedAt: row.updated_at
|
|
1681
|
-
};
|
|
1682
|
-
}
|
|
1683
|
-
function objectFromWire(row, bucketName) {
|
|
1684
|
-
return {
|
|
1685
|
-
id: row.id,
|
|
1686
|
-
bucket: bucketName,
|
|
1687
|
-
key: row.key,
|
|
1688
|
-
size: row.size,
|
|
1689
|
-
mimeType: row.mime_type,
|
|
1690
|
-
etag: row.etag,
|
|
1691
|
-
cacheControl: row.cache_control,
|
|
1692
|
-
contentDisposition: row.content_disposition,
|
|
1693
|
-
uploadedBy: row.uploaded_by,
|
|
1694
|
-
uploadedAt: row.uploaded_at,
|
|
1695
|
-
updatedAt: row.updated_at
|
|
1696
|
-
};
|
|
1697
|
-
}
|
|
1698
|
-
function configFromWire(row) {
|
|
1699
|
-
return {
|
|
1700
|
-
defaultFileSizeLimitBytes: row.default_file_size_limit_bytes,
|
|
1701
|
-
maxFileSizeLimitBytes: row.max_file_size_limit_bytes,
|
|
1702
|
-
tenantStorageQuotaBytes: row.tenant_storage_quota_bytes,
|
|
1703
|
-
reservedSpaceBytes: row.reserved_space_bytes,
|
|
1704
|
-
signedUrlDefaultTtlSec: row.signed_url_default_ttl_sec,
|
|
1705
|
-
signedUrlMaxTtlSec: row.signed_url_max_ttl_sec
|
|
1706
|
-
};
|
|
1707
|
-
}
|
|
1708
|
-
function encodeKey(key) {
|
|
1709
|
-
return key.split("/").map(encodeURIComponent).join("/");
|
|
1710
|
-
}
|
|
1711
|
-
function wrapError2(err, fallback) {
|
|
1712
|
-
if (err instanceof MitwayBaasError) return { data: null, error: err };
|
|
1713
|
-
return {
|
|
1714
|
-
data: null,
|
|
1715
|
-
error: new MitwayBaasError(
|
|
1716
|
-
err instanceof Error ? err.message : fallback,
|
|
1717
|
-
0,
|
|
1718
|
-
"STORAGE_ERROR"
|
|
1719
|
-
)
|
|
1720
|
-
};
|
|
1721
|
-
}
|
|
1722
|
-
async function readEnvelopeError(response) {
|
|
1723
|
-
let code = "STORAGE_ERROR";
|
|
1724
|
-
let message = `HTTP ${response.status}`;
|
|
1725
|
-
try {
|
|
1726
|
-
const body = await response.json();
|
|
1727
|
-
if (body && body.error) {
|
|
1728
|
-
code = body.error.code ?? code;
|
|
1729
|
-
message = body.error.message ?? message;
|
|
1730
|
-
}
|
|
1731
|
-
} catch {
|
|
1732
|
-
}
|
|
1733
|
-
return new MitwayBaasError(message, response.status, code);
|
|
1734
|
-
}
|
|
1735
|
-
var StorageBucketClient = class {
|
|
1736
|
-
constructor(http, bucketName) {
|
|
1737
|
-
this.http = http;
|
|
1738
|
-
this.bucketName = bucketName;
|
|
1739
|
-
}
|
|
1740
|
-
http;
|
|
1741
|
-
bucketName;
|
|
1742
|
-
bucketBase() {
|
|
1743
|
-
return `/api/storage/buckets/${encodeURIComponent(this.bucketName)}`;
|
|
1744
|
-
}
|
|
1745
|
-
objectPath(key) {
|
|
1746
|
-
return `${this.bucketBase()}/objects/${encodeKey(key)}`;
|
|
1747
|
-
}
|
|
1748
|
-
async upload(key, body, opts = {}) {
|
|
1749
|
-
try {
|
|
1750
|
-
const method = opts.upsert ? "PUT" : "POST";
|
|
1751
|
-
const headers = {
|
|
1752
|
-
"Content-Type": opts.contentType ?? "application/octet-stream"
|
|
1753
|
-
};
|
|
1754
|
-
if (opts.cacheControl) headers["Cache-Control"] = opts.cacheControl;
|
|
1755
|
-
if (opts.contentDisposition)
|
|
1756
|
-
headers["Content-Disposition"] = opts.contentDisposition;
|
|
1757
|
-
const response = await this.http.rawFetch(this.objectPath(key), {
|
|
1758
|
-
method,
|
|
1759
|
-
headers,
|
|
1760
|
-
body,
|
|
1761
|
-
signal: opts.abortSignal
|
|
1762
|
-
});
|
|
1763
|
-
if (!response.ok) {
|
|
1764
|
-
return { data: null, error: await readEnvelopeError(response) };
|
|
1765
|
-
}
|
|
1766
|
-
const parsed = await response.json();
|
|
1767
|
-
if (parsed.error || !parsed.data) {
|
|
1768
|
-
return {
|
|
1769
|
-
data: null,
|
|
1770
|
-
error: new MitwayBaasError(
|
|
1771
|
-
parsed.error?.message ?? "Upload failed",
|
|
1772
|
-
response.status,
|
|
1773
|
-
parsed.error?.code ?? "STORAGE_ERROR"
|
|
1774
|
-
)
|
|
1775
|
-
};
|
|
1776
|
-
}
|
|
1777
|
-
return { data: objectFromWire(parsed.data, this.bucketName), error: null };
|
|
1778
|
-
} catch (err) {
|
|
1779
|
-
return wrapError2(err, "Upload failed");
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
|
-
async download(key, opts = {}) {
|
|
1783
|
-
try {
|
|
1784
|
-
const headers = {};
|
|
1785
|
-
if (opts.range) {
|
|
1786
|
-
headers["Range"] = `bytes=${opts.range.start}-${opts.range.end}`;
|
|
1787
|
-
}
|
|
1788
|
-
const response = await this.http.rawFetch(this.objectPath(key), {
|
|
1789
|
-
method: "GET",
|
|
1790
|
-
headers,
|
|
1791
|
-
signal: opts.abortSignal
|
|
1792
|
-
});
|
|
1793
|
-
if (!response.ok) {
|
|
1794
|
-
return { data: null, error: await readEnvelopeError(response) };
|
|
1795
|
-
}
|
|
1796
|
-
const blob = await response.blob();
|
|
1797
|
-
return { data: blob, error: null };
|
|
1798
|
-
} catch (err) {
|
|
1799
|
-
return wrapError2(err, "Download failed");
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
async getStream(key, opts = {}) {
|
|
1803
|
-
try {
|
|
1804
|
-
const headers = {};
|
|
1805
|
-
if (opts.range) {
|
|
1806
|
-
headers["Range"] = `bytes=${opts.range.start}-${opts.range.end}`;
|
|
1807
|
-
}
|
|
1808
|
-
const response = await this.http.rawFetch(this.objectPath(key), {
|
|
1809
|
-
method: "GET",
|
|
1810
|
-
headers,
|
|
1811
|
-
signal: opts.abortSignal
|
|
1812
|
-
});
|
|
1813
|
-
if (!response.ok) {
|
|
1814
|
-
return { data: null, error: await readEnvelopeError(response) };
|
|
1815
|
-
}
|
|
1816
|
-
if (!response.body) {
|
|
1817
|
-
return {
|
|
1818
|
-
data: null,
|
|
1819
|
-
error: new MitwayBaasError(
|
|
1820
|
-
"Response body is not a stream",
|
|
1821
|
-
response.status,
|
|
1822
|
-
"STORAGE_ERROR"
|
|
1823
|
-
)
|
|
1824
|
-
};
|
|
1825
|
-
}
|
|
1826
|
-
return {
|
|
1827
|
-
data: response.body,
|
|
1828
|
-
error: null
|
|
1829
|
-
};
|
|
1830
|
-
} catch (err) {
|
|
1831
|
-
return wrapError2(err, "Download failed");
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
async remove(keys) {
|
|
1835
|
-
try {
|
|
1836
|
-
const results = await Promise.allSettled(
|
|
1837
|
-
keys.map(
|
|
1838
|
-
(key) => this.http.rawFetch(this.objectPath(key), { method: "DELETE" })
|
|
1839
|
-
)
|
|
1840
|
-
);
|
|
1841
|
-
const removed = [];
|
|
1842
|
-
const errors = [];
|
|
1843
|
-
for (let i = 0; i < keys.length; i++) {
|
|
1844
|
-
const key = keys[i];
|
|
1845
|
-
const r = results[i];
|
|
1846
|
-
if (r.status === "fulfilled" && r.value.ok) {
|
|
1847
|
-
removed.push(key);
|
|
1848
|
-
} else if (r.status === "fulfilled") {
|
|
1849
|
-
errors.push(`${key}: HTTP ${r.value.status}`);
|
|
1850
|
-
} else {
|
|
1851
|
-
const msg = r.reason instanceof Error ? r.reason.message : String(r.reason);
|
|
1852
|
-
errors.push(`${key}: ${msg}`);
|
|
1853
|
-
}
|
|
1854
|
-
}
|
|
1855
|
-
if (errors.length > 0) {
|
|
1856
|
-
return {
|
|
1857
|
-
data: null,
|
|
1858
|
-
error: new MitwayBaasError(
|
|
1859
|
-
`Failed to delete some objects: ${errors.join("; ")}`,
|
|
1860
|
-
0,
|
|
1861
|
-
"STORAGE_ERROR"
|
|
1862
|
-
)
|
|
1863
|
-
};
|
|
1864
|
-
}
|
|
1865
|
-
return { data: { removed }, error: null };
|
|
1866
|
-
} catch (err) {
|
|
1867
|
-
return wrapError2(err, "Delete failed");
|
|
1868
|
-
}
|
|
1869
|
-
}
|
|
1870
|
-
async list(opts = {}) {
|
|
1871
|
-
try {
|
|
1872
|
-
const params = {};
|
|
1873
|
-
if (opts.prefix !== void 0) params.prefix = opts.prefix;
|
|
1874
|
-
if (opts.limit !== void 0) params.limit = String(opts.limit);
|
|
1875
|
-
if (opts.startAfter !== void 0) params.start_after = opts.startAfter;
|
|
1876
|
-
const rows = await this.http.get(
|
|
1877
|
-
`${this.bucketBase()}/objects`,
|
|
1878
|
-
{ params }
|
|
1879
|
-
);
|
|
1880
|
-
return {
|
|
1881
|
-
data: rows.map((r) => objectFromWire(r, this.bucketName)),
|
|
1882
|
-
error: null
|
|
1883
|
-
};
|
|
1884
|
-
} catch (err) {
|
|
1885
|
-
return wrapError2(err, "List failed");
|
|
1886
|
-
}
|
|
1887
|
-
}
|
|
1888
|
-
async copy(fromKey, toKey, toBucket) {
|
|
1889
|
-
try {
|
|
1890
|
-
const row = await this.http.post(
|
|
1891
|
-
`${this.objectPath(fromKey)}/copy`,
|
|
1892
|
-
{
|
|
1893
|
-
dest_bucket: toBucket ?? this.bucketName,
|
|
1894
|
-
dest_key: toKey
|
|
1895
|
-
}
|
|
1896
|
-
);
|
|
1897
|
-
return {
|
|
1898
|
-
data: objectFromWire(row, toBucket ?? this.bucketName),
|
|
1899
|
-
error: null
|
|
1900
|
-
};
|
|
1901
|
-
} catch (err) {
|
|
1902
|
-
return wrapError2(err, "Copy failed");
|
|
1903
|
-
}
|
|
1904
|
-
}
|
|
1905
|
-
async move(fromKey, toKey, toBucket) {
|
|
1906
|
-
try {
|
|
1907
|
-
const row = await this.http.post(
|
|
1908
|
-
`${this.objectPath(fromKey)}/move`,
|
|
1909
|
-
{
|
|
1910
|
-
dest_bucket: toBucket ?? this.bucketName,
|
|
1911
|
-
dest_key: toKey
|
|
1912
|
-
}
|
|
1913
|
-
);
|
|
1914
|
-
return {
|
|
1915
|
-
data: objectFromWire(row, toBucket ?? this.bucketName),
|
|
1916
|
-
error: null
|
|
1917
|
-
};
|
|
1918
|
-
} catch (err) {
|
|
1919
|
-
return wrapError2(err, "Move failed");
|
|
1920
|
-
}
|
|
1921
|
-
}
|
|
1922
|
-
async createSignedUrl(key, opts = {}) {
|
|
1923
|
-
try {
|
|
1924
|
-
const body = {};
|
|
1925
|
-
if (opts.expiresIn !== void 0) body.expires_in = opts.expiresIn;
|
|
1926
|
-
const wire = await this.http.post(`${this.objectPath(key)}/sign`, body);
|
|
1927
|
-
return {
|
|
1928
|
-
data: {
|
|
1929
|
-
url: wire.url,
|
|
1930
|
-
token: wire.token,
|
|
1931
|
-
expiresAt: wire.expiresAt
|
|
1932
|
-
},
|
|
1933
|
-
error: null
|
|
1934
|
-
};
|
|
1935
|
-
} catch (err) {
|
|
1936
|
-
return wrapError2(err, "Sign failed");
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1939
|
-
getPublicUrl(key) {
|
|
1940
|
-
const url = `${this.http.baseUrl.replace(/\/$/, "")}${this.objectPath(key)}`;
|
|
1941
|
-
return { data: { url } };
|
|
1942
|
-
}
|
|
1943
|
-
};
|
|
1944
|
-
var Storage = class {
|
|
1945
|
-
constructor(http) {
|
|
1946
|
-
this.http = http;
|
|
1947
|
-
}
|
|
1948
|
-
http;
|
|
1949
|
-
/** Scope subsequent operations to a single bucket. */
|
|
1950
|
-
from(bucketName) {
|
|
1951
|
-
return new StorageBucketClient(this.http, bucketName);
|
|
1952
|
-
}
|
|
1953
|
-
// --- Admin (require service_role) ---
|
|
1954
|
-
async listBuckets() {
|
|
1955
|
-
try {
|
|
1956
|
-
const rows = await this.http.get("/api/storage/buckets");
|
|
1957
|
-
return { data: rows.map(bucketFromWire), error: null };
|
|
1958
|
-
} catch (err) {
|
|
1959
|
-
return wrapError2(err, "listBuckets failed");
|
|
1960
|
-
}
|
|
1961
|
-
}
|
|
1962
|
-
async getBucket(name) {
|
|
1963
|
-
try {
|
|
1964
|
-
const row = await this.http.get(
|
|
1965
|
-
`/api/storage/buckets/${encodeURIComponent(name)}`
|
|
1966
|
-
);
|
|
1967
|
-
return { data: bucketFromWire(row), error: null };
|
|
1968
|
-
} catch (err) {
|
|
1969
|
-
return wrapError2(err, "getBucket failed");
|
|
1970
|
-
}
|
|
1971
|
-
}
|
|
1972
|
-
async createBucket(name, opts = {}) {
|
|
1973
|
-
try {
|
|
1974
|
-
const body = { name };
|
|
1975
|
-
if (opts.public !== void 0) body.public = opts.public;
|
|
1976
|
-
if (opts.fileSizeLimitBytes !== void 0)
|
|
1977
|
-
body.file_size_limit_bytes = opts.fileSizeLimitBytes;
|
|
1978
|
-
if (opts.allowedMimeTypes !== void 0)
|
|
1979
|
-
body.allowed_mime_types = opts.allowedMimeTypes;
|
|
1980
|
-
const row = await this.http.post(
|
|
1981
|
-
"/api/storage/buckets",
|
|
1982
|
-
body
|
|
1983
|
-
);
|
|
1984
|
-
return { data: bucketFromWire(row), error: null };
|
|
1985
|
-
} catch (err) {
|
|
1986
|
-
return wrapError2(err, "createBucket failed");
|
|
1987
|
-
}
|
|
1988
|
-
}
|
|
1989
|
-
async updateBucket(name, opts) {
|
|
1990
|
-
try {
|
|
1991
|
-
const body = {};
|
|
1992
|
-
if (opts.public !== void 0) body.public = opts.public;
|
|
1993
|
-
if (opts.fileSizeLimitBytes !== void 0)
|
|
1994
|
-
body.file_size_limit_bytes = opts.fileSizeLimitBytes;
|
|
1995
|
-
if (opts.allowedMimeTypes !== void 0)
|
|
1996
|
-
body.allowed_mime_types = opts.allowedMimeTypes;
|
|
1997
|
-
const row = await this.http.patch(
|
|
1998
|
-
`/api/storage/buckets/${encodeURIComponent(name)}`,
|
|
1999
|
-
body
|
|
2000
|
-
);
|
|
2001
|
-
return { data: bucketFromWire(row), error: null };
|
|
2002
|
-
} catch (err) {
|
|
2003
|
-
return wrapError2(err, "updateBucket failed");
|
|
2004
|
-
}
|
|
2005
|
-
}
|
|
2006
|
-
async deleteBucket(name) {
|
|
2007
|
-
try {
|
|
2008
|
-
await this.http.delete(
|
|
2009
|
-
`/api/storage/buckets/${encodeURIComponent(name)}`
|
|
2010
|
-
);
|
|
2011
|
-
return { data: null, error: null };
|
|
2012
|
-
} catch (err) {
|
|
2013
|
-
return wrapError2(err, "deleteBucket failed");
|
|
2014
|
-
}
|
|
2015
|
-
}
|
|
2016
|
-
async emptyBucket(name) {
|
|
2017
|
-
try {
|
|
2018
|
-
const result = await this.http.post(
|
|
2019
|
-
`/api/storage/buckets/${encodeURIComponent(name)}/empty`,
|
|
2020
|
-
{}
|
|
2021
|
-
);
|
|
2022
|
-
return { data: result, error: null };
|
|
2023
|
-
} catch (err) {
|
|
2024
|
-
return wrapError2(err, "emptyBucket failed");
|
|
2025
|
-
}
|
|
2026
|
-
}
|
|
2027
|
-
// --- Storage config (service_role) ---
|
|
2028
|
-
async getConfig() {
|
|
2029
|
-
try {
|
|
2030
|
-
const row = await this.http.get("/api/storage/config");
|
|
2031
|
-
return { data: configFromWire(row), error: null };
|
|
2032
|
-
} catch (err) {
|
|
2033
|
-
return wrapError2(err, "getConfig failed");
|
|
2034
|
-
}
|
|
2035
|
-
}
|
|
2036
|
-
};
|
|
2037
|
-
|
|
2038
|
-
// src/client.ts
|
|
2039
|
-
var MitwayBaasClient = class {
|
|
2040
|
-
http;
|
|
2041
|
-
tokenManager;
|
|
2042
|
-
auth;
|
|
2043
|
-
database;
|
|
2044
|
-
realtime;
|
|
2045
|
-
storage;
|
|
2046
|
-
constructor(config = {}) {
|
|
2047
|
-
const logger = new Logger(config.debug);
|
|
2048
|
-
this.tokenManager = new TokenManager({
|
|
2049
|
-
persistSession: config.persistSession,
|
|
2050
|
-
storageKey: config.storageKey
|
|
2051
|
-
});
|
|
2052
|
-
this.http = new HttpClient(config, this.tokenManager, logger);
|
|
2053
|
-
this.auth = new Auth(this.http, this.tokenManager);
|
|
2054
|
-
this.database = new Database(this.http, this.tokenManager, config.anonKey);
|
|
2055
|
-
this.realtime = new Realtime(
|
|
2056
|
-
this.http.baseUrl,
|
|
2057
|
-
this.tokenManager,
|
|
2058
|
-
config.anonKey,
|
|
2059
|
-
config.realtime
|
|
2060
|
-
);
|
|
2061
|
-
this.storage = new Storage(this.http);
|
|
2062
|
-
}
|
|
2063
|
-
/**
|
|
2064
|
-
* Escape hatch for callers that need to make custom requests against the
|
|
2065
|
-
* backend without going through `auth` or `database`.
|
|
2066
|
-
*/
|
|
2067
|
-
getHttpClient() {
|
|
2068
|
-
return this.http;
|
|
2069
|
-
}
|
|
2070
|
-
};
|
|
2071
|
-
|
|
2072
|
-
// src/index.ts
|
|
2073
|
-
function createClient(config) {
|
|
2074
|
-
return new MitwayBaasClient(config);
|
|
2075
|
-
}
|
|
2076
|
-
var index_default = MitwayBaasClient;
|
|
2077
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
2078
|
-
0 && (module.exports = {
|
|
2079
|
-
Auth,
|
|
2080
|
-
Database,
|
|
2081
|
-
HttpClient,
|
|
2082
|
-
Logger,
|
|
2083
|
-
MitwayBaasClient,
|
|
2084
|
-
MitwayBaasError,
|
|
2085
|
-
Realtime,
|
|
2086
|
-
RealtimeChannel,
|
|
2087
|
-
Storage,
|
|
2088
|
-
StorageBucketClient,
|
|
2089
|
-
TokenManager,
|
|
2090
|
-
createClient
|
|
2091
|
-
});
|
|
2092
|
-
//# sourceMappingURL=index.cjs.map
|
|
1
|
+
'use strict';Object.defineProperty(exports,'__esModule',{value:true});var postgrestJs=require('@supabase/postgrest-js'),socket_ioClient=require('socket.io-client');var c=class n extends Error{statusCode;error;nextActions;constructor(e,t,r,s){super(e),this.name="MitwayBaasError",this.statusCode=t,this.error=r,this.nextActions=s;}static fromApiError(e){return new n(e.message,e.statusCode,e.error,e.nextActions)}};var X=["authorization","x-api-key","cookie","set-cookie"],Z=["password","token","accesstoken","refreshtoken","authorization","secret","apikey","api_key","email","ssn","creditcard","credit_card"];function ee(n){let e={};for(let[t,r]of Object.entries(n))X.includes(t.toLowerCase())?e[t]="***REDACTED***":e[t]=r;return e}function C(n){if(n==null)return n;if(typeof n=="string")try{let e=JSON.parse(n);return C(e)}catch{return n}if(Array.isArray(n))return n.map(C);if(typeof n=="object"){let e={};for(let[t,r]of Object.entries(n))Z.includes(t.toLowerCase().replace(/[-_]/g,""))?e[t]="***REDACTED***":e[t]=C(r);return e}return n}function G(n){if(n==null)return "";if(typeof n=="string")try{return JSON.stringify(JSON.parse(n),null,2)}catch{return n}if(typeof FormData<"u"&&n instanceof FormData)return "[FormData]";try{return JSON.stringify(n,null,2)}catch{return "[Unserializable body]"}}var R=class{enabled;customLog;constructor(e){typeof e=="function"?(this.enabled=true,this.customLog=e):(this.enabled=!!e,this.customLog=null);}log(e,...t){if(!this.enabled)return;let r=`[MITWAY-BaaS Debug] ${e}`;this.customLog?this.customLog(r,...t):console.log(r,...t);}warn(e,...t){if(!this.enabled)return;let r=`[MITWAY-BaaS Debug] ${e}`;this.customLog?this.customLog(r,...t):console.warn(r,...t);}error(e,...t){if(!this.enabled)return;let r=`[MITWAY-BaaS Debug] ${e}`;this.customLog?this.customLog(r,...t):console.error(r,...t);}logRequest(e,t,r,s){if(!this.enabled)return;let i=[`\u2192 ${e} ${t}`];r&&Object.keys(r).length>0&&i.push(` Headers: ${JSON.stringify(ee(r))}`);let a=G(C(s));if(a){let o=a.length>1e3?a.slice(0,1e3)+"... [truncated]":a;i.push(` Body: ${o}`);}this.log(i.join(`
|
|
2
|
+
`));}logResponse(e,t,r,s,i){if(!this.enabled)return;let a=[`\u2190 ${e} ${t} ${r} (${s}ms)`],o=G(C(i));if(o){let g=o.length>1e3?o.slice(0,1e3)+"... [truncated]":o;a.push(` Body: ${g}`);}r>=400?this.error(a.join(`
|
|
3
|
+
`)):this.log(a.join(`
|
|
4
|
+
`));}};var K="mitway_baas_csrf_token",te="mitway_baas_session";function V(){return typeof localStorage<"u"?{getItem:e=>{try{return localStorage.getItem(e)}catch{return null}},setItem:(e,t)=>{try{localStorage.setItem(e,t);}catch{}},removeItem:e=>{try{localStorage.removeItem(e);}catch{}}}:{getItem:()=>null,setItem:()=>{},removeItem:()=>{}}}var re={getItem:()=>null,setItem:()=>{},removeItem:()=>{}};function Y(){if(typeof document>"u")return null;let n=document.cookie.split(";").find(e=>e.trim().startsWith(`${K}=`));return n&&n.split("=")[1]||null}function F(n){if(typeof document>"u")return;let e=10080*60,t=typeof window<"u"&&window.location.protocol==="https:"?"; Secure":"";document.cookie=`${K}=${encodeURIComponent(n)}; path=/; max-age=${e}; SameSite=Lax${t}`;}function D(){if(typeof document>"u")return;let n=typeof window<"u"&&window.location.protocol==="https:"?"; Secure":"";document.cookie=`${K}=; path=/; max-age=0; SameSite=Lax${n}`;}var S=class{accessToken=null;refreshToken=null;user=null;persistSession;storageKey;storage;onTokenChange=null;constructor(e){this.persistSession=e?.persistSession??true,this.storageKey=e?.storageKey??te,this.storage=this.persistSession?e?.storage??V():re,(e?.multiTab??true)&&this.persistSession&&typeof window<"u"&&typeof localStorage<"u"&&window.addEventListener("storage",this.handleStorageEvent),this.restoreSession();}handleStorageEvent=e=>{e.key===this.storageKey&&(typeof localStorage<"u"&&e.storageArea!==localStorage||this.syncFromStorage(e.newValue));};syncFromStorage(e){let t=this.accessToken;if(e===null)this.accessToken=null,this.refreshToken=null,this.user=null;else try{let r=JSON.parse(e);if(!r.accessToken||!r.user)return;this.accessToken=r.accessToken,this.refreshToken=r.refreshToken??null,this.user=r.user;}catch{return}t!==this.accessToken&&this.onTokenChange&&this.onTokenChange();}saveSession(e){let t=e.accessToken!==this.accessToken;this.accessToken=e.accessToken,this.user=e.user,e.refreshToken!==void 0&&(this.refreshToken=e.refreshToken??null),this.persist(),t&&this.onTokenChange&&this.onTokenChange();}getSession(){return !this.accessToken||!this.user?null:{accessToken:this.accessToken,refreshToken:this.refreshToken??void 0,user:this.user}}getAccessToken(){return this.accessToken}setAccessToken(e){let t=e!==this.accessToken;this.accessToken=e,this.persist(),t&&this.onTokenChange&&this.onTokenChange();}getRefreshToken(){return this.refreshToken}setRefreshToken(e){this.refreshToken=e,this.persist();}getUser(){return this.user}setUser(e){this.user=e,this.persist();}clearSession(){let e=this.accessToken!==null;this.accessToken=null,this.refreshToken=null,this.user=null,this.removePersisted(),e&&this.onTokenChange&&this.onTokenChange();}restoreSession(){if(!this.persistSession)return false;try{let e=this.storage.getItem(this.storageKey);if(!e)return !1;let t=JSON.parse(e);return !t.accessToken||!t.user?!1:(this.accessToken=t.accessToken,this.refreshToken=t.refreshToken??null,this.user=t.user,!0)}catch{return false}}persist(){if(!this.persistSession||!this.accessToken||!this.user)return;let e={accessToken:this.accessToken,user:this.user};this.refreshToken&&(e.refreshToken=this.refreshToken),this.storage.setItem(this.storageKey,JSON.stringify(e));}removePersisted(){this.persistSession&&this.storage.removeItem(this.storageKey);}};function P(n){if(!n||typeof n!="object")return n;let e=n,t={...e},r=false;return "access_token"in e&&!("accessToken"in e)&&(t.accessToken=e.access_token,delete t.access_token,r=true),"csrf_token"in e&&!("csrfToken"in e)&&(t.csrfToken=e.csrf_token,delete t.csrf_token,r=true),"refresh_token"in e&&!("refreshToken"in e)&&(t.refreshToken=e.refresh_token,delete t.refresh_token,r=true),r?t:n}var se=new Set([500,502,503,504]),ne=new Set(["GET","HEAD","PUT","DELETE","OPTIONS"]),_=class{baseUrl;fetch;defaultHeaders;anonKey;userToken=null;logger;autoRefreshToken=true;isRefreshing=false;refreshPromise=null;tokenManager;refreshToken=null;timeout;retryCount;retryDelay;constructor(e,t,r){if(this.baseUrl=e.baseUrl||"http://localhost:7130",this.autoRefreshToken=e.autoRefreshToken??true,this.fetch=e.fetch||(globalThis.fetch?globalThis.fetch.bind(globalThis):void 0),this.anonKey=e.anonKey,this.defaultHeaders={...e.headers},this.tokenManager=t??new S,this.logger=r||new R(false),this.timeout=e.timeout??3e4,this.retryCount=e.retryCount??3,this.retryDelay=e.retryDelay??500,!this.fetch)throw new Error("Fetch is not available. Provide a fetch implementation in the SDK config.")}buildUrl(e,t){let r=new URL(e,this.baseUrl);return t&&Object.entries(t).forEach(([s,i])=>{if(s==="select"){let a=i.replace(/\s+/g," ").trim();a=a.replace(/\s*\(\s*/g,"(").replace(/\s*\)\s*/g,")").replace(/\(\s+/g,"(").replace(/\s+\)/g,")").replace(/,\s+(?=[^()]*\))/g,","),r.searchParams.append(s,a);}else r.searchParams.append(s,i);}),r.toString()}isRetryableStatus(e){return se.has(e)}computeRetryDelay(e){let r=this.retryDelay*Math.pow(2,e-1)*(.85+Math.random()*.3);return Math.round(r)}async handleRequest(e,t,r={}){let{params:s,headers:i={},body:a,signal:o,...g}=r,p=this.buildUrl(t,s),E=Date.now(),T=ne.has(e.toUpperCase())||r.idempotent===true?this.retryCount:0,b={...this.defaultHeaders},J=this.userToken||this.anonKey;J&&(b.Authorization=`Bearer ${J}`);let M;a!==void 0&&(typeof FormData<"u"&&a instanceof FormData?M=a:(e!=="GET"&&(b["Content-Type"]="application/json;charset=UTF-8"),M=JSON.stringify(a))),i instanceof Headers?i.forEach((f,h)=>{b[h]=f;}):Array.isArray(i)?i.forEach(([f,h])=>{b[f]=h;}):Object.assign(b,i),this.logger.logRequest(e,p,b,M);let H;for(let f=0;f<=T;f++){if(f>0){let l=this.computeRetryDelay(f);if(this.logger.warn(`Retry ${f}/${T} for ${e} ${p} in ${l}ms`),o?.aborted)throw o.reason;await new Promise((u,q)=>{let y=()=>{clearTimeout(Q),q(o.reason);},Q=setTimeout(()=>{o&&o.removeEventListener("abort",y),u();},l);o&&o.addEventListener("abort",y,{once:true});});}let h,m;if((this.timeout>0||o)&&(h=new AbortController,this.timeout>0&&(m=setTimeout(()=>h.abort(),this.timeout)),o))if(o.aborted)h.abort(o.reason);else {let l=()=>h.abort(o.reason);o.addEventListener("abort",l,{once:true}),h.signal.addEventListener("abort",()=>{o.removeEventListener("abort",l);},{once:true});}try{let l=await this.fetch(p,{method:e,headers:b,body:M,...g,...h?{signal:h.signal}:{}});if(this.isRetryableStatus(l.status)&&f<T){m!==void 0&&clearTimeout(m),await l.body?.cancel(),H=new c(`Server error: ${l.status} ${l.statusText}`,l.status,"SERVER_ERROR");continue}if(l.status===204){m!==void 0&&clearTimeout(m);return}let u,q=l.headers.get("content-type");try{q?.includes("json")?u=await l.json():u=await l.text();}catch(y){throw m!==void 0&&clearTimeout(m),new c(`Failed to parse response body: ${y?.message||"Unknown error"}`,l.status,l.ok?"PARSE_ERROR":"REQUEST_FAILED")}if(m!==void 0&&clearTimeout(m),!l.ok){if(this.logger.logResponse(e,p,l.status,Date.now()-E,u),u&&typeof u=="object"&&"error"in u&&u.error!==null&&typeof u.error=="object"){let y=u.error;throw new c(y.message||l.statusText||"Request failed",y.statusCode||l.status,y.code||y.error||"REQUEST_FAILED",y.nextActions)}throw new c(`Request failed: ${l.statusText}`,l.status,"REQUEST_FAILED")}return this.logger.logResponse(e,p,l.status,Date.now()-E,u),u&&typeof u=="object"&&"data"in u&&"error"in u&&u.error===null?u.data:u}catch(l){if(m!==void 0&&clearTimeout(m),l?.name==="AbortError")throw h&&h.signal.aborted&&this.timeout>0&&!o?.aborted?new c(`Request timed out after ${this.timeout}ms`,408,"REQUEST_TIMEOUT"):l;if(l instanceof c)throw l;if(f<T){H=l;continue}throw new c(`Network request failed: ${l?.message||"Unknown error"}`,0,"NETWORK_ERROR")}}throw H||new c("Request failed after all retry attempts",0,"NETWORK_ERROR")}async request(e,t,r={}){try{return await this.handleRequest(e,t,{...r})}catch(s){if(s instanceof c&&s.statusCode===401&&s.error==="INVALID_TOKEN"&&this.autoRefreshToken)try{let i=await this.handleTokenRefresh();return this.setAuthToken(i.accessToken),this.tokenManager.saveSession(i),i.csrfToken&&F(i.csrfToken),i.refreshToken&&this.setRefreshToken(i.refreshToken),await this.handleRequest(e,t,{...r})}catch(i){throw this.tokenManager.clearSession(),this.userToken=null,this.refreshToken=null,D(),i}throw s}}async rawFetch(e,t={}){let r=this.buildUrl(e),s=new Headers(t.headers??{});for(let[i,a]of Object.entries(this.defaultHeaders))s.has(i)||s.set(i,a);if(!s.has("Authorization")){let i=this.userToken??this.anonKey;i&&s.set("Authorization",`Bearer ${i}`);}return this.fetch(r,{...t,headers:s})}get(e,t){return this.request("GET",e,t)}post(e,t,r){return this.request("POST",e,{...r,body:t})}put(e,t,r){return this.request("PUT",e,{...r,body:t})}patch(e,t,r){return this.request("PATCH",e,{...r,body:t})}delete(e,t){return this.request("DELETE",e,t)}setAuthToken(e){this.userToken=e;}setRefreshToken(e){this.refreshToken=e;}getHeaders(){let e={...this.defaultHeaders},t=this.userToken||this.anonKey;return t&&(e.Authorization=`Bearer ${t}`),e}async handleTokenRefresh(){return this.isRefreshing?this.refreshPromise:(this.isRefreshing=true,this.refreshPromise=(async()=>{try{let e=Y(),t=this.refreshToken?{refreshToken:this.refreshToken}:void 0,r=await this.handleRequest("POST","/api/auth/refresh",{body:t,headers:e?{"X-CSRF-Token":e}:{},credentials:"include"});return P(r)}finally{this.isRefreshing=false,this.refreshPromise=null;}})(),this.refreshPromise)}};function w(n,e){return n instanceof c?{data:null,error:n}:{data:null,error:new c(n instanceof Error?n.message:e,500,"AUTH_ERROR")}}var A=class{constructor(e,t){this.http=e;this.tokenManager=t;this.tokenManager.onTokenChange=()=>this._emitFromTokenChange();let r=this.tokenManager.getSession();if(r){this.http.setAuthToken(r.accessToken);let s=this.tokenManager.getRefreshToken();s&&this.http.setRefreshToken(s),this.lastEmittedUserId=r.user.id,this.lastEmittedAccessToken=r.accessToken;}}http;tokenManager;stateChangeListeners=new Set;lastEmittedUserId=null;lastEmittedAccessToken=null;onAuthStateChange(e){return this.stateChangeListeners.add(e),{unsubscribe:()=>{this.stateChangeListeners.delete(e);}}}emit(e,t){for(let r of this.stateChangeListeners)try{r(e,t);}catch{}}_emitFromTokenChange(){let e=this.tokenManager.getSession(),t=this.lastEmittedUserId,r=this.lastEmittedAccessToken;if(!e){t!==null&&(this.lastEmittedUserId=null,this.lastEmittedAccessToken=null,this.emit("SIGNED_OUT",null));return}let s=e.user.id;if(t===null){this.lastEmittedUserId=s,this.lastEmittedAccessToken=e.accessToken,this.emit("SIGNED_IN",e);return}if(t!==s){this.lastEmittedUserId=null,this.lastEmittedAccessToken=null,this.emit("SIGNED_OUT",null),this.lastEmittedUserId=s,this.lastEmittedAccessToken=e.accessToken,this.emit("SIGNED_IN",e);return}r!==e.accessToken&&(this.lastEmittedAccessToken=e.accessToken,this.emit("TOKEN_REFRESHED",e));}saveSessionFromResponse(e){let t={accessToken:e.accessToken,refreshToken:e.refreshToken,user:e.user};e.csrfToken&&F(e.csrfToken),this.tokenManager.saveSession(t),this.http.setAuthToken(e.accessToken),this.http.setRefreshToken(e.refreshToken??null);}async signUp(e){try{let t=await this.http.post("/api/auth/register",e,{credentials:"include"}),r=P(t);return r?.accessToken&&r.user&&this.saveSessionFromResponse(r),{data:r,error:null}}catch(t){return w(t,"Sign up failed")}}async signInWithPassword(e){try{let t=await this.http.post("/api/auth/login",e,{credentials:"include"}),r=P(t);return r?.accessToken&&r.user&&this.saveSessionFromResponse(r),{data:r,error:null}}catch(t){return w(t,"Sign in failed")}}async signOut(){try{try{await this.http.post("/api/auth/logout",void 0,{credentials:"include"});}catch{}return this.tokenManager.clearSession(),this.http.setAuthToken(null),this.http.setRefreshToken(null),D(),{error:null}}catch{return {error:new c("Failed to sign out",500,"SIGNOUT_ERROR")}}}async refreshSession(){try{let e=await this.http.handleTokenRefresh();return e?.accessToken&&e.user&&this.saveSessionFromResponse(e),{data:e,error:null}}catch(e){return w(e,"Session refresh failed")}}async initialize(){let e=this.tokenManager.getSession();if(!e)return this.emit("INITIAL_SESSION",null),{data:null,error:new c("No persisted session",0,"NO_SESSION")};try{let t=await this.http.get("/api/auth/sessions/current");if(t?.user){this.tokenManager.setUser(t.user);let r=this.tokenManager.getSession();return this.emit("INITIAL_SESSION",r),{data:{user:t.user,accessToken:e.accessToken},error:null}}return this.tokenManager.clearSession(),this.http.setAuthToken(null),this.http.setRefreshToken(null),this.emit("INITIAL_SESSION",null),{data:null,error:new c("Invalid session",401,"INVALID_SESSION")}}catch(t){return this.tokenManager.clearSession(),this.http.setAuthToken(null),this.http.setRefreshToken(null),this.emit("INITIAL_SESSION",null),w(t,"Session restore failed")}}getSession(){return this.tokenManager.getSession()}getUser(){return this.tokenManager.getUser()}async getCurrentUser(){try{let e=await this.http.get("/api/auth/sessions/current");if(e?.user){let t={accessToken:this.tokenManager.getSession()?.accessToken??"",user:e.user};this.tokenManager.saveSession(t);}return {data:e,error:null}}catch(e){return w(e,"Failed to get current user")}}async getProfile(e){try{return {data:await this.http.get(`/api/auth/profiles/${encodeURIComponent(e)}`),error:null}}catch(t){return w(t,"Failed to get profile")}}async setProfile(e){try{let t=await this.http.patch("/api/auth/profiles/current",{profile:e}),r=this.tokenManager.getUser();if(t?.profile&&r){let s={...r,profile:t.profile};this.tokenManager.setUser(s),this.emit("USER_UPDATED",this.tokenManager.getSession());}return {data:t,error:null}}catch(t){return w(t,"Failed to update profile")}}};function oe(n,e,t){return async(r,s)=>{let i=typeof r=="string"?r:r.toString(),a=new URL(i),o=a.pathname.startsWith("/")?a.pathname.slice(1):a.pathname,g=o.match(/^rpc\/(.+)$/),p=g?`/api/database/rpc/${g[1]}`:`/api/database/records/${o}`,E=`${n.baseUrl}${p}${a.search}`,x=new Headers(s?.headers);if(!x.has("Authorization")){let T=e.getAccessToken()??t;T&&x.set("Authorization",`Bearer ${T}`);}return fetch(E,{...s,headers:x})}}var O=class{postgrest;httpClient;constructor(e,t,r){this.httpClient=e,this.postgrest=new postgrestJs.PostgrestClient("http://dummy",{fetch:oe(e,t,r),headers:{}});}from(e){if(!e||typeof e!="string")throw new c("Database.from(table) requires a non-empty string",400,"INVALID_TABLE_NAME");return this.postgrest.from(e)}rpc(e,t,r){return this.postgrest.rpc(e,t,r)}getUrl(){return this.httpClient.baseUrl}};var ce=2e4;function z(n,e){let t=new Error(e);return t.code=n,t}var le=1e4,N=class{constructor(e,t,r={}){this.topic=e;this.realtime=t;this.options=r;}topic;realtime;bindings=[];state="closed";statusCallback=null;presence={};trackedState=null;presenceHeartbeat=null;lastBroadcastTimestamp=null;options;get isPrivate(){return this.options.config?.private===true}get presenceKey(){return this.options.config?.presence?.key}_state(){return this.state}async _rejoinAfterReconnect(){if(this.state!=="closed"){for(let e of this.bindings)e.type==="postgres_changes"&&(e.subscriptionId=void 0);this.state="joining";try{if(await this.registerAllBindings(),this.trackedState){let e=this.realtime._getSocket(),t=this.presenceKey;e?.emit("realtime:presence:track",t!==void 0?{channel:this.topic,state:this.trackedState,key:t}:{channel:this.topic,state:this.trackedState});}this.lastBroadcastTimestamp&&this.bindings.some(e=>e.type==="broadcast")&&this.replay({since:this.lastBroadcastTimestamp}).catch(()=>{}),this.state="joined",this.statusCallback?.("SUBSCRIBED");}catch(e){this.state="errored",this.statusCallback?.("CHANNEL_ERROR",z("REJOIN_FAILED",e instanceof Error?e.message:String(e)));}}}on(e,t,r){return e==="postgres_changes"?this.bindings.push({type:"postgres_changes",filter:t,callback:r}):e==="broadcast"?this.bindings.push({type:"broadcast",filter:t,callback:r}):this.bindings.push({type:"presence",filter:t,callback:r}),this}async track(e){let t=this.realtime._getSocket();if(!t)throw new c("Socket not connected",503,"NOT_CONNECTED");this.trackedState=e;let r=this.presenceKey;t.emit("realtime:presence:track",r!==void 0?{channel:this.topic,state:e,key:r}:{channel:this.topic,state:e}),this.presenceHeartbeat||(this.presenceHeartbeat=setInterval(()=>{let i=this.realtime._getSocket();i&&this.trackedState&&i.emit("realtime:presence:track",r!==void 0?{channel:this.topic,state:this.trackedState,key:r}:{channel:this.topic,state:this.trackedState});},ce),this.presenceHeartbeat.unref?.());}untrack(){this.trackedState=null,this.presenceHeartbeat&&(clearInterval(this.presenceHeartbeat),this.presenceHeartbeat=null),this.realtime._getSocket()?.emit("realtime:presence:untrack",{channel:this.topic});}presenceState(){return this.presence}subscribe(e){return this.statusCallback=e??null,this.state==="joining"||this.state==="joined"?this:(this.state="joining",this.realtime.connect().then(async()=>{try{await this.registerAllBindings(),this.state="joined",this.statusCallback?.("SUBSCRIBED");}catch(t){this.state="errored";let r=t instanceof Error?t.message:String(t);this.statusCallback?.("CHANNEL_ERROR",z("SUBSCRIBE_FAILED",r));}},t=>{this.state="errored",this.statusCallback?.("CHANNEL_ERROR",z("CONNECT_FAILED",t.message));}),this)}async unsubscribe(){if(this.state==="closed")return;let e=this.realtime._getSocket();if(this.presenceHeartbeat&&(clearInterval(this.presenceHeartbeat),this.presenceHeartbeat=null),!e){this.trackedState=null,this.state="closed";return}this.trackedState&&(e.emit("realtime:presence:untrack",{channel:this.topic}),this.trackedState=null);let t=this.bindings.filter(r=>r.type==="postgres_changes");for(let r of t)r.subscriptionId&&(e.emit("realtime:postgres_changes:unsubscribe",{subscription_id:r.subscriptionId}),r.subscriptionId=void 0);this.bindings.some(r=>r.type==="broadcast"||r.type==="presence")&&e.emit("realtime:unsubscribe",{channel:this.topic}),this.realtime._detachChannel(this),this.state="closed",this.statusCallback?.("CLOSED");}async send(e){if(e.type!=="broadcast")throw new c('Only "broadcast" sends are supported \u2014 DB changes flow via your DB writes, not channel.send()',400,"UNSUPPORTED_SEND_TYPE");if(new Set(["postgres_changes","presence_state","presence_join","presence_leave"]).has(e.event))throw new c(`"${e.event}" is a reserved event name \u2014 pick a different name for broadcast events`,400,"RESERVED_EVENT_NAME");let r=this.realtime._getSocket();if(!r)throw new c("Socket not connected",503,"NOT_CONNECTED");let s=this.options.config?.broadcast?.self,i={channel:this.topic,event:e.event,payload:e.payload};return s===false&&(i.self=false),await new Promise(a=>{r.emit("realtime:publish",i,o=>{a(o);});})}async replay(e){let t=this.realtime._getSocket();if(!t)throw new c("Socket not connected",503,"NOT_CONNECTED");t.emit("realtime:broadcast:replay",{channel:this.topic,since:e.since,limit:e.limit,private:this.isPrivate});}_dispatch(e,t){if(e==="postgres_changes"){let s=t;for(let i of this.bindings)if(!(i.type!=="postgres_changes"||!i.subscriptionId||!s.ids.includes(i.subscriptionId)||!(i.filter.event==="*"||i.filter.event===s.data.eventType)))try{i.callback(s.data);}catch{}return}if(e==="presence_state"||e==="presence_join"||e==="presence_leave"){let s=t;if(s.channel!==this.topic)return;if(e==="presence_state"&&s.state)this.presence={...s.state},this.firePresence({event:"sync",state:this.presence});else if(e==="presence_join"&&s.joins)Object.assign(this.presence,s.joins),this.firePresence({event:"join",joins:s.joins});else if(e==="presence_leave"&&s.leaves){for(let i of Object.keys(s.leaves))delete this.presence[i];this.firePresence({event:"leave",leaves:s.leaves});}return}let r=t.meta;r?.timestamp&&(!this.lastBroadcastTimestamp||r.timestamp>this.lastBroadcastTimestamp)&&(this.lastBroadcastTimestamp=r.timestamp);for(let s of this.bindings){if(s.type!=="broadcast"||s.filter.event!==e)continue;let{meta:i,...a}=t;try{s.callback({type:"broadcast",event:e,payload:a});}catch{}}}firePresence(e){for(let t of this.bindings)if(t.type==="presence"&&t.filter.event===e.event)try{e.event==="sync"?t.callback():(e.event,t.callback(e));}catch{}}async registerAllBindings(){let e=this.realtime._getSocket();if(!e)throw new Error("Socket not available");this.bindings.some(s=>s.type==="broadcast"||s.type==="presence")&&await new Promise((s,i)=>{e.emit("realtime:subscribe",{channel:this.topic,private:this.isPrivate},a=>{a.status==="ok"?s():i(new Error(a.error?.message??"subscribe failed"));});});let r=this.bindings.filter(s=>s.type==="postgres_changes");await Promise.all(r.map(s=>new Promise((i,a)=>{e.emit("realtime:postgres_changes:subscribe",{event:s.filter.event,schema:s.filter.schema??"public",table:s.filter.table,filter:s.filter.filter},o=>{o.status==="ok"&&o.subscription_id?(s.subscriptionId=o.subscription_id,i()):a(new Error(o.error?.message??"postgres_changes subscribe failed"));});})));}},B=class{socket=null;baseUrl;options;anonKey;tokenManager;channels=new Map;connecting=null;firstConnected=false;constructor(e,t,r,s={}){this.baseUrl=e,this.tokenManager=t,this.anonKey=r,this.options=s;}get isConnected(){return this.socket?.connected===true}get socketId(){return this.socket?.id}channel(e,t){let r=this.channels.get(e);if(r)return r;let s=new N(e,this,t);return this.channels.set(e,s),s}connect(){return this.isConnected?Promise.resolve():this.connecting?this.connecting:(this.connecting=this.openSocket(),this.connecting)}disconnect(){this.socket&&(this.socket.disconnect(),this.socket=null,this.firstConnected=false);}_getSocket(){return this.socket}_detachChannel(e){this.channels.delete(e.topic);}openSocket(){let e=this.tokenManager.getAccessToken()??this.anonKey;if(!e){let s=new c("Realtime requires an access token or anonKey",401,"AUTH_INVALID_API_KEY");return this.connecting=null,Promise.reject(s)}let t=this.options.timeoutMs??le,r=socket_ioClient.io(this.baseUrl,{path:this.options.path,transports:this.options.transports??["websocket"],auth:{token:e,...this.options.extraAuth??{}},reconnection:true,timeout:t});return this.socket=r,r.onAny((s,...i)=>this.dispatch(s,i)),r.on("connect",()=>{if(this.firstConnected)for(let s of this.channels.values()){let i=s._state();(i==="joined"||i==="errored")&&s._rejoinAfterReconnect();}}),new Promise((s,i)=>{let a=setTimeout(()=>{r.off("connect",g),r.off("connect_error",p),this.connecting=null,i(new c(`Realtime connection timeout after ${t}ms`,408,"CONNECTION_TIMEOUT"));},t),o=()=>{clearTimeout(a),r.off("connect",g),r.off("connect_error",p);},g=()=>{o(),this.connecting=null,this.firstConnected=true,s();},p=E=>{o(),this.connecting=null,i(new c(E.message,0,"CONNECTION_FAILED"));};r.once("connect",g),r.once("connect_error",p);})}dispatch(e,t){if(e==="postgres_changes"){let s=t[0]??{};this.channels.forEach(i=>i._dispatch("postgres_changes",s));return}if(e==="connect"||e==="disconnect"||e==="connect_error"||e==="error"||e==="realtime:error"||e==="realtime:shutdown")return;let r=t[0]??{};this.channels.forEach(s=>s._dispatch(e,r));}};function L(n){return {id:n.id,name:n.name,public:n.public,fileSizeLimitBytes:n.file_size_limit_bytes,allowedMimeTypes:n.allowed_mime_types,createdAt:n.created_at,updatedAt:n.updated_at}}function j(n,e){return {id:n.id,bucket:e,key:n.key,size:n.size,mimeType:n.mime_type,etag:n.etag,cacheControl:n.cache_control,contentDisposition:n.content_disposition,uploadedBy:n.uploaded_by,uploadedAt:n.uploaded_at,updatedAt:n.updated_at}}function ue(n){return {defaultFileSizeLimitBytes:n.default_file_size_limit_bytes,maxFileSizeLimitBytes:n.max_file_size_limit_bytes,tenantStorageQuotaBytes:n.tenant_storage_quota_bytes,reservedSpaceBytes:n.reserved_space_bytes,signedUrlDefaultTtlSec:n.signed_url_default_ttl_sec,signedUrlMaxTtlSec:n.signed_url_max_ttl_sec}}function de(n){return n.split("/").map(encodeURIComponent).join("/")}function d(n,e){return n instanceof c?{data:null,error:n}:{data:null,error:new c(n instanceof Error?n.message:e,0,"STORAGE_ERROR")}}async function W(n){let e="STORAGE_ERROR",t=`HTTP ${n.status}`;try{let r=await n.json();r&&r.error&&(e=r.error.code??e,t=r.error.message??t);}catch{}return new c(t,n.status,e)}var $=class{constructor(e,t){this.http=e;this.bucketName=t;}http;bucketName;bucketBase(){return `/api/storage/buckets/${encodeURIComponent(this.bucketName)}`}objectPath(e){return `${this.bucketBase()}/objects/${de(e)}`}async upload(e,t,r={}){try{let s=r.upsert?"PUT":"POST",i={"Content-Type":r.contentType??"application/octet-stream"};r.cacheControl&&(i["Cache-Control"]=r.cacheControl),r.contentDisposition&&(i["Content-Disposition"]=r.contentDisposition);let a=await this.http.rawFetch(this.objectPath(e),{method:s,headers:i,body:t,signal:r.abortSignal});if(!a.ok)return {data:null,error:await W(a)};let o=await a.json();return o.error||!o.data?{data:null,error:new c(o.error?.message??"Upload failed",a.status,o.error?.code??"STORAGE_ERROR")}:{data:j(o.data,this.bucketName),error:null}}catch(s){return d(s,"Upload failed")}}async download(e,t={}){try{let r={};t.range&&(r.Range=`bytes=${t.range.start}-${t.range.end}`);let s=await this.http.rawFetch(this.objectPath(e),{method:"GET",headers:r,signal:t.abortSignal});return s.ok?{data:await s.blob(),error:null}:{data:null,error:await W(s)}}catch(r){return d(r,"Download failed")}}async getStream(e,t={}){try{let r={};t.range&&(r.Range=`bytes=${t.range.start}-${t.range.end}`);let s=await this.http.rawFetch(this.objectPath(e),{method:"GET",headers:r,signal:t.abortSignal});return s.ok?s.body?{data:s.body,error:null}:{data:null,error:new c("Response body is not a stream",s.status,"STORAGE_ERROR")}:{data:null,error:await W(s)}}catch(r){return d(r,"Download failed")}}async remove(e){try{let t=await Promise.allSettled(e.map(i=>this.http.rawFetch(this.objectPath(i),{method:"DELETE"}))),r=[],s=[];for(let i=0;i<e.length;i++){let a=e[i],o=t[i];if(o.status==="fulfilled"&&o.value.ok)r.push(a);else if(o.status==="fulfilled")s.push(`${a}: HTTP ${o.value.status}`);else {let g=o.reason instanceof Error?o.reason.message:String(o.reason);s.push(`${a}: ${g}`);}}return s.length>0?{data:null,error:new c(`Failed to delete some objects: ${s.join("; ")}`,0,"STORAGE_ERROR")}:{data:{removed:r},error:null}}catch(t){return d(t,"Delete failed")}}async list(e={}){try{let t={};return e.prefix!==void 0&&(t.prefix=e.prefix),e.limit!==void 0&&(t.limit=String(e.limit)),e.startAfter!==void 0&&(t.start_after=e.startAfter),{data:(await this.http.get(`${this.bucketBase()}/objects`,{params:t})).map(s=>j(s,this.bucketName)),error:null}}catch(t){return d(t,"List failed")}}async copy(e,t,r){try{let s=await this.http.post(`${this.objectPath(e)}/copy`,{dest_bucket:r??this.bucketName,dest_key:t});return {data:j(s,r??this.bucketName),error:null}}catch(s){return d(s,"Copy failed")}}async move(e,t,r){try{let s=await this.http.post(`${this.objectPath(e)}/move`,{dest_bucket:r??this.bucketName,dest_key:t});return {data:j(s,r??this.bucketName),error:null}}catch(s){return d(s,"Move failed")}}async createSignedUrl(e,t={}){try{let r={};t.expiresIn!==void 0&&(r.expires_in=t.expiresIn);let s=await this.http.post(`${this.objectPath(e)}/sign`,r);return {data:{url:s.url,token:s.token,expiresAt:s.expiresAt},error:null}}catch(r){return d(r,"Sign failed")}}getPublicUrl(e){return {data:{url:`${this.http.baseUrl.replace(/\/$/,"")}${this.objectPath(e)}`}}}},I=class{constructor(e){this.http=e;}http;from(e){return new $(this.http,e)}async listBuckets(){try{return {data:(await this.http.get("/api/storage/buckets")).map(L),error:null}}catch(e){return d(e,"listBuckets failed")}}async getBucket(e){try{let t=await this.http.get(`/api/storage/buckets/${encodeURIComponent(e)}`);return {data:L(t),error:null}}catch(t){return d(t,"getBucket failed")}}async createBucket(e,t={}){try{let r={name:e};t.public!==void 0&&(r.public=t.public),t.fileSizeLimitBytes!==void 0&&(r.file_size_limit_bytes=t.fileSizeLimitBytes),t.allowedMimeTypes!==void 0&&(r.allowed_mime_types=t.allowedMimeTypes);let s=await this.http.post("/api/storage/buckets",r);return {data:L(s),error:null}}catch(r){return d(r,"createBucket failed")}}async updateBucket(e,t){try{let r={};t.public!==void 0&&(r.public=t.public),t.fileSizeLimitBytes!==void 0&&(r.file_size_limit_bytes=t.fileSizeLimitBytes),t.allowedMimeTypes!==void 0&&(r.allowed_mime_types=t.allowedMimeTypes);let s=await this.http.patch(`/api/storage/buckets/${encodeURIComponent(e)}`,r);return {data:L(s),error:null}}catch(r){return d(r,"updateBucket failed")}}async deleteBucket(e){try{return await this.http.delete(`/api/storage/buckets/${encodeURIComponent(e)}`),{data:null,error:null}}catch(t){return d(t,"deleteBucket failed")}}async emptyBucket(e){try{return {data:await this.http.post(`/api/storage/buckets/${encodeURIComponent(e)}/empty`,{}),error:null}}catch(t){return d(t,"emptyBucket failed")}}async getConfig(){try{let e=await this.http.get("/api/storage/config");return {data:ue(e),error:null}}catch(e){return d(e,"getConfig failed")}}};function he(n){return {key:n.key,digest:n.digest,updatedAt:n.updated_at}}function k(n,e){return n instanceof c?{data:null,error:n}:{data:null,error:new c(n instanceof Error?n.message:e,0,"FUNCTIONS_ERROR")}}function ge(n){return typeof n=="object"&&n!==null&&!ArrayBuffer.isView(n)&&!(n instanceof Blob)&&!(n instanceof FormData)&&!(n instanceof URLSearchParams)&&!(n instanceof ReadableStream)}var U=class{constructor(e){this.http=e;}http;async list(){try{return {data:await this.http.get("/api/functions"),error:null}}catch(e){return k(e,"list failed")}}async get(e){try{return {data:await this.http.get(`/api/functions/${encodeURIComponent(e)}`),error:null}}catch(t){return k(t,"get failed")}}async create(e){try{return {data:await this.http.post("/api/functions",e),error:null}}catch(t){return k(t,"create failed")}}async update(e,t){try{return {data:await this.http.put(`/api/functions/${encodeURIComponent(e)}`,t),error:null}}catch(r){return k(r,"update failed")}}async remove(e){try{return await this.http.delete(`/api/functions/${encodeURIComponent(e)}`),{data:{deleted:!0},error:null}}catch(t){return k(t,"remove failed")}}async invoke(e,t={}){try{let r=t.method??"POST",s={...t.headers},i;return t.body!==void 0&&t.body!==null&&(ge(t.body)?(i=JSON.stringify(t.body),s["Content-Type"]||(s["Content-Type"]="application/json")):i=t.body),{data:await this.http.rawFetch(`/api/invoke/${encodeURIComponent(e)}`,{method:r,headers:s,body:i}),error:null}}catch(r){return k(r,"invoke failed")}}async listSecrets(){try{return {data:(await this.http.get("/api/functions/secrets")).secrets.map(he),error:null}}catch(e){return k(e,"listSecrets failed")}}async setSecrets(e){try{return await this.http.put("/api/functions/secrets",{secrets:e}),{data:{saved:!0},error:null}}catch(t){return k(t,"setSecrets failed")}}async deleteSecret(e){try{return await this.http.delete(`/api/functions/secrets/${encodeURIComponent(e)}`),{data:{deleted:!0},error:null}}catch(t){return k(t,"deleteSecret failed")}}};var v=class{http;tokenManager;auth;database;realtime;storage;functions;constructor(e={}){let t=new R(e.debug);this.tokenManager=new S({persistSession:e.persistSession,storageKey:e.storageKey,storage:e.storage,multiTab:e.multiTab}),this.http=new _(e,this.tokenManager,t),this.auth=new A(this.http,this.tokenManager),this.database=new O(this.http,this.tokenManager,e.anonKey),this.realtime=new B(this.http.baseUrl,this.tokenManager,e.anonKey,e.realtime),this.storage=new I(this.http),this.functions=new U(this.http);}getHttpClient(){return this.http}};function Qe(n){return new v(n)}var Xe=v;
|
|
5
|
+
exports.Auth=A;exports.Database=O;exports.Functions=U;exports.HttpClient=_;exports.Logger=R;exports.MitwayBaasClient=v;exports.MitwayBaasError=c;exports.Realtime=B;exports.RealtimeChannel=N;exports.Storage=I;exports.StorageBucketClient=$;exports.TokenManager=S;exports.createClient=Qe;exports.createLocalStorageAdapter=V;exports.default=Xe;
|