@snackbase/sdk 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2835 -0
- package/dist/{index-Dr6K4PMl.d.mts → index.d.cts} +367 -38
- package/dist/index.d.mts +2447 -2
- package/dist/index.mjs +357 -56
- package/package.json +33 -38
- package/CHANGELOG.md +0 -61
- package/README.md +0 -287
- package/dist/react/index.d.mts +0 -63
- package/dist/react/index.mjs +0 -271
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2835 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
//#region src/types/config.ts
|
|
4
|
+
const DEFAULT_CONFIG = {
|
|
5
|
+
timeout: 3e4,
|
|
6
|
+
enableAutoRefresh: true,
|
|
7
|
+
refreshBeforeExpiry: 300,
|
|
8
|
+
maxRetries: 3,
|
|
9
|
+
retryDelay: 1e3,
|
|
10
|
+
logLevel: "error",
|
|
11
|
+
enableLogging: false,
|
|
12
|
+
defaultAccount: void 0,
|
|
13
|
+
maxRealTimeRetries: 10,
|
|
14
|
+
realTimeReconnectionDelay: 1e3
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/utils/platform.ts
|
|
19
|
+
/**
|
|
20
|
+
* Detects the platform and returns the recommended storage backend.
|
|
21
|
+
* - Web: localStorage
|
|
22
|
+
* - React Native: asyncStorage
|
|
23
|
+
*/
|
|
24
|
+
function getAutoDetectedStorage() {
|
|
25
|
+
if (typeof navigator !== "undefined" && navigator.product === "ReactNative") return "asyncStorage";
|
|
26
|
+
if (typeof window !== "undefined" && typeof window.localStorage !== "undefined") return "localStorage";
|
|
27
|
+
return "memory";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/core/errors.ts
|
|
32
|
+
/**
|
|
33
|
+
* Base error class for all SnackBase SDK errors.
|
|
34
|
+
*/
|
|
35
|
+
var SnackBaseError = class SnackBaseError extends Error {
|
|
36
|
+
code;
|
|
37
|
+
status;
|
|
38
|
+
details;
|
|
39
|
+
field;
|
|
40
|
+
retryable;
|
|
41
|
+
constructor(message, code, status, details, retryable = false, field, redirectUrl, authProvider, providerName) {
|
|
42
|
+
super(message);
|
|
43
|
+
this.redirectUrl = redirectUrl;
|
|
44
|
+
this.authProvider = authProvider;
|
|
45
|
+
this.providerName = providerName;
|
|
46
|
+
this.name = this.constructor.name;
|
|
47
|
+
this.code = code;
|
|
48
|
+
this.status = status;
|
|
49
|
+
this.details = details;
|
|
50
|
+
this.retryable = retryable;
|
|
51
|
+
this.field = field;
|
|
52
|
+
Object.setPrototypeOf(this, SnackBaseError.prototype);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Thrown when authentication fails (401).
|
|
57
|
+
*/
|
|
58
|
+
var AuthenticationError = class AuthenticationError extends SnackBaseError {
|
|
59
|
+
constructor(message = "Authentication failed", details) {
|
|
60
|
+
super(message, "AUTHENTICATION_ERROR", 401, details, false);
|
|
61
|
+
Object.setPrototypeOf(this, AuthenticationError.prototype);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Thrown when the user is not authorized to perform an action (403).
|
|
66
|
+
*/
|
|
67
|
+
var AuthorizationError = class AuthorizationError extends SnackBaseError {
|
|
68
|
+
constructor(message = "Not authorized", details) {
|
|
69
|
+
super(message, "AUTHORIZATION_ERROR", 403, details, false);
|
|
70
|
+
Object.setPrototypeOf(this, AuthorizationError.prototype);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Thrown when an API key is restricted to superadmin users (403).
|
|
75
|
+
*/
|
|
76
|
+
var ApiKeyRestrictedError = class ApiKeyRestrictedError extends SnackBaseError {
|
|
77
|
+
constructor(message, details) {
|
|
78
|
+
super(message || "API keys are restricted to superadmin users. Please use JWT authentication.", "API_KEY_RESTRICTED", 403, details || {
|
|
79
|
+
suggestion: "Remove apiKey config and use login() instead",
|
|
80
|
+
documentation: "https://docs.snackbase.com/authentication/api-keys"
|
|
81
|
+
}, false);
|
|
82
|
+
Object.setPrototypeOf(this, ApiKeyRestrictedError.prototype);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Thrown when email verification is required (401).
|
|
87
|
+
*/
|
|
88
|
+
var EmailVerificationRequiredError = class EmailVerificationRequiredError extends SnackBaseError {
|
|
89
|
+
constructor(message, details) {
|
|
90
|
+
super(message || "Please check your email inbox to verify your account before logging in.", "EMAIL_VERIFICATION_REQUIRED", 401, details || {
|
|
91
|
+
suggestion: "Click the verification link sent to your email address",
|
|
92
|
+
canResend: true
|
|
93
|
+
}, false);
|
|
94
|
+
Object.setPrototypeOf(this, EmailVerificationRequiredError.prototype);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Thrown when a resource is not found (404).
|
|
99
|
+
*/
|
|
100
|
+
var NotFoundError = class NotFoundError extends SnackBaseError {
|
|
101
|
+
constructor(message = "Resource not found", details) {
|
|
102
|
+
super(message, "NOT_FOUND_ERROR", 404, details, false);
|
|
103
|
+
Object.setPrototypeOf(this, NotFoundError.prototype);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Thrown when a conflict occurs (409).
|
|
108
|
+
*/
|
|
109
|
+
var ConflictError = class ConflictError extends SnackBaseError {
|
|
110
|
+
constructor(message = "Resource conflict", details) {
|
|
111
|
+
super(message, "CONFLICT_ERROR", 409, details, false);
|
|
112
|
+
Object.setPrototypeOf(this, ConflictError.prototype);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Thrown when validation fails (422).
|
|
117
|
+
*/
|
|
118
|
+
var ValidationError = class ValidationError extends SnackBaseError {
|
|
119
|
+
fields;
|
|
120
|
+
constructor(message = "Validation failed", details) {
|
|
121
|
+
super(message, "VALIDATION_ERROR", 422, details, false, details?.field);
|
|
122
|
+
Object.setPrototypeOf(this, ValidationError.prototype);
|
|
123
|
+
if (details?.errors) this.fields = details.errors;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Thrown when rate limit is exceeded (429).
|
|
128
|
+
*/
|
|
129
|
+
var RateLimitError = class RateLimitError extends SnackBaseError {
|
|
130
|
+
retryAfter;
|
|
131
|
+
constructor(message = "Rate limit exceeded", details, retryAfter) {
|
|
132
|
+
super(message, "RATE_LIMIT_ERROR", 429, details, true);
|
|
133
|
+
Object.setPrototypeOf(this, RateLimitError.prototype);
|
|
134
|
+
this.retryAfter = retryAfter;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Thrown when a network failure occurs.
|
|
139
|
+
*/
|
|
140
|
+
var NetworkError = class NetworkError extends SnackBaseError {
|
|
141
|
+
constructor(message = "Network error", details) {
|
|
142
|
+
super(message, "NETWORK_ERROR", void 0, details, true);
|
|
143
|
+
Object.setPrototypeOf(this, NetworkError.prototype);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* Thrown when a request times out.
|
|
148
|
+
*/
|
|
149
|
+
var TimeoutError = class TimeoutError extends SnackBaseError {
|
|
150
|
+
constructor(message = "Request timed out", details) {
|
|
151
|
+
super(message, "TIMEOUT_ERROR", void 0, details, true);
|
|
152
|
+
Object.setPrototypeOf(this, TimeoutError.prototype);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* Thrown when a server error occurs (500+).
|
|
157
|
+
*/
|
|
158
|
+
var ServerError = class ServerError extends SnackBaseError {
|
|
159
|
+
constructor(message = "Internal server error", status = 500, details) {
|
|
160
|
+
super(message, "SERVER_ERROR", status, details, true);
|
|
161
|
+
Object.setPrototypeOf(this, ServerError.prototype);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
//#endregion
|
|
166
|
+
//#region src/core/http-client.ts
|
|
167
|
+
/**
|
|
168
|
+
* Robust HTTP client wrapping the fetch API.
|
|
169
|
+
*/
|
|
170
|
+
var HttpClient = class {
|
|
171
|
+
config;
|
|
172
|
+
requestInterceptors = [];
|
|
173
|
+
responseInterceptors = [];
|
|
174
|
+
errorInterceptors = [];
|
|
175
|
+
constructor(config) {
|
|
176
|
+
this.config = {
|
|
177
|
+
timeout: 3e4,
|
|
178
|
+
maxRetries: 3,
|
|
179
|
+
retryDelay: 1e3,
|
|
180
|
+
...config
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
addRequestInterceptor(interceptor) {
|
|
184
|
+
this.requestInterceptors.push(interceptor);
|
|
185
|
+
}
|
|
186
|
+
addResponseInterceptor(interceptor) {
|
|
187
|
+
this.responseInterceptors.push(interceptor);
|
|
188
|
+
}
|
|
189
|
+
addErrorInterceptor(interceptor) {
|
|
190
|
+
this.errorInterceptors.push(interceptor);
|
|
191
|
+
}
|
|
192
|
+
async request(req) {
|
|
193
|
+
let currentReq = {
|
|
194
|
+
url: req.url || "",
|
|
195
|
+
method: req.method || "GET",
|
|
196
|
+
headers: req.headers || {},
|
|
197
|
+
body: req.body,
|
|
198
|
+
params: req.params,
|
|
199
|
+
timeout: req.timeout ?? this.config.timeout,
|
|
200
|
+
signal: req.signal
|
|
201
|
+
};
|
|
202
|
+
const startTime = Date.now();
|
|
203
|
+
const requestId = Math.random().toString(36).substring(7);
|
|
204
|
+
try {
|
|
205
|
+
for (const interceptor of this.requestInterceptors) currentReq = await interceptor(currentReq);
|
|
206
|
+
const fullUrl = this.resolveUrl(currentReq.url, currentReq);
|
|
207
|
+
if (this.config.logger) this.config.logger.debug(`[${requestId}] Request: ${currentReq.method} ${fullUrl}`, {
|
|
208
|
+
headers: currentReq.headers,
|
|
209
|
+
body: currentReq.body
|
|
210
|
+
});
|
|
211
|
+
const controller = new AbortController();
|
|
212
|
+
const { signal } = controller;
|
|
213
|
+
if (currentReq.signal) currentReq.signal.addEventListener("abort", () => controller.abort());
|
|
214
|
+
const timeoutId = setTimeout(() => controller.abort(), currentReq.timeout);
|
|
215
|
+
let retryCount = 0;
|
|
216
|
+
const executeFetch = async () => {
|
|
217
|
+
try {
|
|
218
|
+
const fetchOptions = {
|
|
219
|
+
method: currentReq.method,
|
|
220
|
+
headers: currentReq.headers,
|
|
221
|
+
body: currentReq.body ? JSON.stringify(currentReq.body) : void 0,
|
|
222
|
+
signal
|
|
223
|
+
};
|
|
224
|
+
const response = await fetch(fullUrl, fetchOptions);
|
|
225
|
+
clearTimeout(timeoutId);
|
|
226
|
+
let data;
|
|
227
|
+
const contentType = response.headers.get("content-type");
|
|
228
|
+
if (contentType && contentType.includes("application/json")) data = await response.json();
|
|
229
|
+
else data = await response.text();
|
|
230
|
+
let httpResponse = {
|
|
231
|
+
data,
|
|
232
|
+
status: response.status,
|
|
233
|
+
headers: response.headers,
|
|
234
|
+
request: currentReq
|
|
235
|
+
};
|
|
236
|
+
const duration = Date.now() - startTime;
|
|
237
|
+
if (this.config.logger) {
|
|
238
|
+
this.config.logger.debug(`[${requestId}] Response: ${response.status} (${duration}ms)`, {
|
|
239
|
+
status: response.status,
|
|
240
|
+
headers: response.headers,
|
|
241
|
+
data
|
|
242
|
+
});
|
|
243
|
+
if (duration > 1e3) this.config.logger.warn(`[${requestId}] Slow request detected: ${currentReq.method} ${fullUrl} took ${duration}ms`);
|
|
244
|
+
}
|
|
245
|
+
for (const interceptor of this.responseInterceptors) httpResponse = await interceptor(httpResponse);
|
|
246
|
+
return httpResponse;
|
|
247
|
+
} catch (error) {
|
|
248
|
+
clearTimeout(timeoutId);
|
|
249
|
+
let processedError = error;
|
|
250
|
+
if (error.name === "AbortError") processedError = new TimeoutError(`Request timed out after ${currentReq.timeout}ms`);
|
|
251
|
+
else if (!(error instanceof Error)) processedError = new NetworkError(error.message || "Network request failed", error);
|
|
252
|
+
for (const interceptor of this.errorInterceptors) try {
|
|
253
|
+
processedError = await interceptor(processedError);
|
|
254
|
+
} catch (interceptorError) {
|
|
255
|
+
processedError = interceptorError;
|
|
256
|
+
}
|
|
257
|
+
if (this.shouldRetry(processedError, retryCount)) {
|
|
258
|
+
retryCount++;
|
|
259
|
+
const delay = this.calculateRetryDelay(retryCount);
|
|
260
|
+
if (this.config.logger) this.config.logger.info(`[${requestId}] Retrying request (attempt ${retryCount}/${this.config.maxRetries})...`);
|
|
261
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
262
|
+
return executeFetch();
|
|
263
|
+
}
|
|
264
|
+
throw processedError;
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
return await executeFetch();
|
|
268
|
+
} catch (error) {
|
|
269
|
+
const duration = Date.now() - startTime;
|
|
270
|
+
if (this.config.logger) this.config.logger.error(`[${requestId}] Request failed (${duration}ms): ${error.message}`, error);
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
async get(url, config) {
|
|
275
|
+
return this.request({
|
|
276
|
+
...config,
|
|
277
|
+
url,
|
|
278
|
+
method: "GET"
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
async post(url, body, config) {
|
|
282
|
+
return this.request({
|
|
283
|
+
...config,
|
|
284
|
+
url,
|
|
285
|
+
method: "POST",
|
|
286
|
+
body
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
async put(url, body, config) {
|
|
290
|
+
return this.request({
|
|
291
|
+
...config,
|
|
292
|
+
url,
|
|
293
|
+
method: "PUT",
|
|
294
|
+
body
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
async patch(url, body, config) {
|
|
298
|
+
return this.request({
|
|
299
|
+
...config,
|
|
300
|
+
url,
|
|
301
|
+
method: "PATCH",
|
|
302
|
+
body
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
async delete(url, config) {
|
|
306
|
+
return this.request({
|
|
307
|
+
...config,
|
|
308
|
+
url,
|
|
309
|
+
method: "DELETE"
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
resolveUrl(url, currentReq) {
|
|
313
|
+
if (url.startsWith("http://") || url.startsWith("https://")) return url;
|
|
314
|
+
let fullUrl = `${this.config.baseUrl.endsWith("/") ? this.config.baseUrl : `${this.config.baseUrl}/`}${url.startsWith("/") ? url.slice(1) : url}`;
|
|
315
|
+
if (currentReq?.params) {
|
|
316
|
+
const urlObj = new URL(fullUrl);
|
|
317
|
+
Object.entries(currentReq.params).forEach(([key, value]) => {
|
|
318
|
+
if (value !== void 0) urlObj.searchParams.append(key, String(value));
|
|
319
|
+
});
|
|
320
|
+
fullUrl = urlObj.toString();
|
|
321
|
+
}
|
|
322
|
+
return fullUrl;
|
|
323
|
+
}
|
|
324
|
+
shouldRetry(error, retryCount) {
|
|
325
|
+
if (retryCount >= this.config.maxRetries) return false;
|
|
326
|
+
return error.retryable === true;
|
|
327
|
+
}
|
|
328
|
+
calculateRetryDelay(retryCount) {
|
|
329
|
+
return this.config.retryDelay * Math.pow(2, retryCount - 1);
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
//#endregion
|
|
334
|
+
//#region src/core/logger.ts
|
|
335
|
+
let LogLevel = /* @__PURE__ */ function(LogLevel) {
|
|
336
|
+
LogLevel[LogLevel["NONE"] = 0] = "NONE";
|
|
337
|
+
LogLevel[LogLevel["ERROR"] = 1] = "ERROR";
|
|
338
|
+
LogLevel[LogLevel["WARN"] = 2] = "WARN";
|
|
339
|
+
LogLevel[LogLevel["INFO"] = 3] = "INFO";
|
|
340
|
+
LogLevel[LogLevel["DEBUG"] = 4] = "DEBUG";
|
|
341
|
+
return LogLevel;
|
|
342
|
+
}({});
|
|
343
|
+
var Logger = class {
|
|
344
|
+
level;
|
|
345
|
+
handlers = [];
|
|
346
|
+
logs = [];
|
|
347
|
+
maxLogs = 1e3;
|
|
348
|
+
constructor(level = LogLevel.NONE) {
|
|
349
|
+
this.level = level;
|
|
350
|
+
this.handlers.push((entry) => {
|
|
351
|
+
const { level, message, data } = entry;
|
|
352
|
+
const args = data ? [message, data] : [message];
|
|
353
|
+
switch (level) {
|
|
354
|
+
case LogLevel.ERROR:
|
|
355
|
+
console.error("[SnackBase]", ...args);
|
|
356
|
+
break;
|
|
357
|
+
case LogLevel.WARN:
|
|
358
|
+
console.warn("[SnackBase]", ...args);
|
|
359
|
+
break;
|
|
360
|
+
case LogLevel.INFO:
|
|
361
|
+
console.info("[SnackBase]", ...args);
|
|
362
|
+
break;
|
|
363
|
+
case LogLevel.DEBUG:
|
|
364
|
+
console.debug("[SnackBase]", ...args);
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
this.handlers.push((entry) => {
|
|
369
|
+
this.logs.push(entry);
|
|
370
|
+
if (this.logs.length > this.maxLogs) this.logs.shift();
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
setLevel(level) {
|
|
374
|
+
this.level = level;
|
|
375
|
+
}
|
|
376
|
+
getLogs() {
|
|
377
|
+
return [...this.logs];
|
|
378
|
+
}
|
|
379
|
+
clearLogs() {
|
|
380
|
+
this.logs = [];
|
|
381
|
+
}
|
|
382
|
+
log(level, message, data) {
|
|
383
|
+
if (this.level < level) return;
|
|
384
|
+
const entry = {
|
|
385
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
386
|
+
level,
|
|
387
|
+
message,
|
|
388
|
+
data
|
|
389
|
+
};
|
|
390
|
+
this.handlers.forEach((handler) => handler(entry));
|
|
391
|
+
}
|
|
392
|
+
error(message, data) {
|
|
393
|
+
this.log(LogLevel.ERROR, message, data);
|
|
394
|
+
}
|
|
395
|
+
warn(message, data) {
|
|
396
|
+
this.log(LogLevel.WARN, message, data);
|
|
397
|
+
}
|
|
398
|
+
info(message, data) {
|
|
399
|
+
this.log(LogLevel.INFO, message, data);
|
|
400
|
+
}
|
|
401
|
+
debug(message, data) {
|
|
402
|
+
this.log(LogLevel.DEBUG, message, data);
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
//#endregion
|
|
407
|
+
//#region src/core/interceptors.ts
|
|
408
|
+
/**
|
|
409
|
+
* Interceptor to set Content-Type: application/json for requests with a body.
|
|
410
|
+
*/
|
|
411
|
+
const contentTypeInterceptor = (request) => {
|
|
412
|
+
if (request.body && !request.headers["Content-Type"]) request.headers["Content-Type"] = "application/json";
|
|
413
|
+
return request;
|
|
414
|
+
};
|
|
415
|
+
/**
|
|
416
|
+
* Interceptor to inject Authorization and API Key headers.
|
|
417
|
+
*/
|
|
418
|
+
const createAuthInterceptor = (getToken, apiKey) => {
|
|
419
|
+
return (request) => {
|
|
420
|
+
const isUserSpecific = request.url.includes("/auth/oauth/") || request.url.includes("/auth/saml/");
|
|
421
|
+
if (apiKey && !isUserSpecific) {
|
|
422
|
+
if (!(request.url.includes("/auth/login") || request.url.includes("/auth/register"))) request.headers["X-API-Key"] = apiKey;
|
|
423
|
+
}
|
|
424
|
+
const token = getToken();
|
|
425
|
+
if (token) request.headers["Authorization"] = `Bearer ${token}`;
|
|
426
|
+
return request;
|
|
427
|
+
};
|
|
428
|
+
};
|
|
429
|
+
/**
|
|
430
|
+
* Interceptor to normalize error responses into SnackBaseError instances.
|
|
431
|
+
*/
|
|
432
|
+
const errorNormalizationInterceptor = (response) => {
|
|
433
|
+
if (response.status >= 400) throw createErrorFromResponse(response);
|
|
434
|
+
return response;
|
|
435
|
+
};
|
|
436
|
+
/**
|
|
437
|
+
* Error interceptor to handle raw fetch errors.
|
|
438
|
+
*/
|
|
439
|
+
const errorInterceptor = (error) => {
|
|
440
|
+
if (error instanceof SnackBaseError) throw error;
|
|
441
|
+
if (error instanceof Error) {
|
|
442
|
+
if (error.name === "AbortError") return error;
|
|
443
|
+
throw new NetworkError(error.message, error);
|
|
444
|
+
}
|
|
445
|
+
throw new NetworkError(String(error), error);
|
|
446
|
+
};
|
|
447
|
+
/**
|
|
448
|
+
* Creates a specific SnackBaseError based on the response status and body.
|
|
449
|
+
*/
|
|
450
|
+
function createErrorFromResponse(response) {
|
|
451
|
+
const { status, data } = response;
|
|
452
|
+
const message = data?.message || data?.error || "An unexpected error occurred";
|
|
453
|
+
switch (status) {
|
|
454
|
+
case 401: return new AuthenticationError(message, data);
|
|
455
|
+
case 403: return new AuthorizationError(message, data);
|
|
456
|
+
case 404: return new NotFoundError(message, data);
|
|
457
|
+
case 409: return new ConflictError(message, data);
|
|
458
|
+
case 422: return new ValidationError(message, data);
|
|
459
|
+
case 429:
|
|
460
|
+
const retryAfter = response.headers.get("Retry-After") || data?.retryAfter;
|
|
461
|
+
return new RateLimitError(message, data, retryAfter ? parseInt(retryAfter, 10) : void 0);
|
|
462
|
+
default:
|
|
463
|
+
if (status >= 500) return new ServerError(message, status, data);
|
|
464
|
+
return new SnackBaseError(message, "UNKNOWN_ERROR", status, data, false);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Enhanced error interceptor to handle 403 API key errors and preserve redirects.
|
|
469
|
+
*/
|
|
470
|
+
const createAuthErrorInterceptor = (onAuthError) => {
|
|
471
|
+
return (error) => {
|
|
472
|
+
if (error.status === 403 && error.details) {
|
|
473
|
+
const detail = error.details.detail || error.details.message || "";
|
|
474
|
+
if (detail.includes("superadmin") || detail.includes("restricted")) {
|
|
475
|
+
const restrictedError = new ApiKeyRestrictedError(detail, error.details);
|
|
476
|
+
if (onAuthError) onAuthError(restrictedError);
|
|
477
|
+
throw restrictedError;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (error.status === 403 && error.details?.redirect_url) {
|
|
481
|
+
error.redirectUrl = error.details.redirect_url;
|
|
482
|
+
error.authProvider = error.details.auth_provider;
|
|
483
|
+
error.providerName = error.details.provider_name;
|
|
484
|
+
}
|
|
485
|
+
if (error.status === 401 && error.details) {
|
|
486
|
+
const detail = error.details.detail || error.details.message || "";
|
|
487
|
+
if (detail.includes("verify") || detail.includes("email")) {
|
|
488
|
+
const verificationError = new EmailVerificationRequiredError(detail, error.details);
|
|
489
|
+
if (onAuthError) onAuthError(verificationError);
|
|
490
|
+
throw verificationError;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
if (error.status === 401 || error.status === 403) {
|
|
494
|
+
if (onAuthError) onAuthError(error);
|
|
495
|
+
}
|
|
496
|
+
throw error;
|
|
497
|
+
};
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
//#endregion
|
|
501
|
+
//#region src/types/auth.ts
|
|
502
|
+
/**
|
|
503
|
+
* Token type enum matching backend TokenType
|
|
504
|
+
*/
|
|
505
|
+
let TokenType = /* @__PURE__ */ function(TokenType) {
|
|
506
|
+
TokenType["JWT"] = "jwt";
|
|
507
|
+
TokenType["API_KEY"] = "api_key";
|
|
508
|
+
TokenType["PERSONAL_TOKEN"] = "personal_token";
|
|
509
|
+
TokenType["OAUTH"] = "oauth";
|
|
510
|
+
return TokenType;
|
|
511
|
+
}({});
|
|
512
|
+
|
|
513
|
+
//#endregion
|
|
514
|
+
//#region src/core/events.ts
|
|
515
|
+
var AuthEventEmitter = class {
|
|
516
|
+
listeners = {};
|
|
517
|
+
on(event, listener) {
|
|
518
|
+
if (!this.listeners[event]) this.listeners[event] = /* @__PURE__ */ new Set();
|
|
519
|
+
this.listeners[event].add(listener);
|
|
520
|
+
return () => this.off(event, listener);
|
|
521
|
+
}
|
|
522
|
+
off(event, listener) {
|
|
523
|
+
this.listeners[event]?.delete(listener);
|
|
524
|
+
}
|
|
525
|
+
emit(event, ...args) {
|
|
526
|
+
this.listeners[event]?.forEach((listener) => {
|
|
527
|
+
listener(...args);
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
//#endregion
|
|
533
|
+
//#region src/core/constants.ts
|
|
534
|
+
/**
|
|
535
|
+
* System account ID for superadmin detection
|
|
536
|
+
* Nil UUID format used by backend
|
|
537
|
+
* @see https://github.com/snackbase/snackbase/blob/main/src/snackbase/infrastructure/api/middleware/authorization.py
|
|
538
|
+
*/
|
|
539
|
+
const SYSTEM_ACCOUNT_ID = "00000000-0000-0000-0000-000000000000";
|
|
540
|
+
/**
|
|
541
|
+
* Token type prefixes matching backend TokenCodec
|
|
542
|
+
*/
|
|
543
|
+
const TOKEN_PREFIXES = {
|
|
544
|
+
JWT: "sb_jwt",
|
|
545
|
+
API_KEY: "sb_ak",
|
|
546
|
+
PERSONAL_TOKEN: "sb_pt",
|
|
547
|
+
OAUTH: "sb_ot"
|
|
548
|
+
};
|
|
549
|
+
/**
|
|
550
|
+
* Valid token prefixes for validation
|
|
551
|
+
*/
|
|
552
|
+
const VALID_TOKEN_PREFIXES = new Set(Object.values(TOKEN_PREFIXES));
|
|
553
|
+
/**
|
|
554
|
+
* API key endpoint path
|
|
555
|
+
*/
|
|
556
|
+
const API_KEY_BASE_PATH = "/api/v1/admin/api-keys";
|
|
557
|
+
|
|
558
|
+
//#endregion
|
|
559
|
+
//#region src/utils/token-utils.ts
|
|
560
|
+
/**
|
|
561
|
+
* Detect token type from token string
|
|
562
|
+
* @param token - The token to analyze
|
|
563
|
+
* @returns Detected token type or undefined
|
|
564
|
+
*/
|
|
565
|
+
function detectTokenType(token) {
|
|
566
|
+
if (!token) return void 0;
|
|
567
|
+
switch (token.split(".")[0]) {
|
|
568
|
+
case TOKEN_PREFIXES.API_KEY: return TokenType.API_KEY;
|
|
569
|
+
case TOKEN_PREFIXES.PERSONAL_TOKEN: return TokenType.PERSONAL_TOKEN;
|
|
570
|
+
case TOKEN_PREFIXES.OAUTH: return TokenType.OAUTH;
|
|
571
|
+
case TOKEN_PREFIXES.JWT: return TokenType.JWT;
|
|
572
|
+
default: return token.startsWith("ey") ? TokenType.JWT : void 0;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Check if user is a superadmin
|
|
577
|
+
* @param user - User object to check
|
|
578
|
+
* @returns true if user is superadmin
|
|
579
|
+
*/
|
|
580
|
+
function isSuperadmin(user) {
|
|
581
|
+
if (!user) return false;
|
|
582
|
+
return user.account_id === SYSTEM_ACCOUNT_ID;
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Format masked API key for display
|
|
586
|
+
* @param key - The full or masked key
|
|
587
|
+
* @returns Formatted masked key
|
|
588
|
+
*/
|
|
589
|
+
function formatMaskedKey(key) {
|
|
590
|
+
if (!key) return "";
|
|
591
|
+
if (key.includes("...")) return key;
|
|
592
|
+
const parts = key.split(".");
|
|
593
|
+
if (parts.length === 3) {
|
|
594
|
+
const [, payload, signature] = parts;
|
|
595
|
+
return `${parts[0]}.${payload.slice(0, 4)}...${signature.slice(-4)}`;
|
|
596
|
+
}
|
|
597
|
+
return key;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
//#endregion
|
|
601
|
+
//#region src/core/auth.ts
|
|
602
|
+
const DEFAULT_AUTH_STATE = {
|
|
603
|
+
user: null,
|
|
604
|
+
account: null,
|
|
605
|
+
token: null,
|
|
606
|
+
refreshToken: null,
|
|
607
|
+
isAuthenticated: false,
|
|
608
|
+
expiresAt: null,
|
|
609
|
+
tokenType: TokenType.JWT
|
|
610
|
+
};
|
|
611
|
+
var AuthManager = class {
|
|
612
|
+
state = { ...DEFAULT_AUTH_STATE };
|
|
613
|
+
storage;
|
|
614
|
+
storageKey;
|
|
615
|
+
events;
|
|
616
|
+
constructor(options) {
|
|
617
|
+
this.storage = options.storage;
|
|
618
|
+
this.storageKey = options.storageKey || "sb_auth_state";
|
|
619
|
+
this.events = new AuthEventEmitter();
|
|
620
|
+
}
|
|
621
|
+
async initialize() {
|
|
622
|
+
await this.hydrate();
|
|
623
|
+
this.validateSession();
|
|
624
|
+
}
|
|
625
|
+
getState() {
|
|
626
|
+
return { ...this.state };
|
|
627
|
+
}
|
|
628
|
+
get user() {
|
|
629
|
+
return this.state.user;
|
|
630
|
+
}
|
|
631
|
+
get account() {
|
|
632
|
+
return this.state.account;
|
|
633
|
+
}
|
|
634
|
+
get token() {
|
|
635
|
+
return this.state.token;
|
|
636
|
+
}
|
|
637
|
+
get refreshToken() {
|
|
638
|
+
return this.state.refreshToken;
|
|
639
|
+
}
|
|
640
|
+
get isAuthenticated() {
|
|
641
|
+
return this.state.isAuthenticated;
|
|
642
|
+
}
|
|
643
|
+
get tokenType() {
|
|
644
|
+
return this.state.tokenType;
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Update auth state (enhanced to extract token_type)
|
|
648
|
+
*/
|
|
649
|
+
async updateState(data) {
|
|
650
|
+
const user = data.user || (data.user_id ? {
|
|
651
|
+
id: data.user_id,
|
|
652
|
+
email: data.email || "",
|
|
653
|
+
role: data.role || "user",
|
|
654
|
+
account_id: data.account_id || "",
|
|
655
|
+
groups: [],
|
|
656
|
+
is_active: true,
|
|
657
|
+
created_at: "",
|
|
658
|
+
last_login: null,
|
|
659
|
+
token_type: TokenType.JWT
|
|
660
|
+
} : null);
|
|
661
|
+
const token = data.token || null;
|
|
662
|
+
const refreshToken = data.refresh_token || data.refreshToken || null;
|
|
663
|
+
let tokenType = TokenType.JWT;
|
|
664
|
+
if (token) tokenType = detectTokenType(token) || TokenType.JWT;
|
|
665
|
+
else if (user?.token_type) tokenType = user.token_type;
|
|
666
|
+
this.state = {
|
|
667
|
+
...this.state,
|
|
668
|
+
user,
|
|
669
|
+
account: data.account || (data.account_id ? {
|
|
670
|
+
id: data.account_id,
|
|
671
|
+
slug: "",
|
|
672
|
+
name: "",
|
|
673
|
+
created_at: ""
|
|
674
|
+
} : this.state.account),
|
|
675
|
+
token: token || this.state.token,
|
|
676
|
+
refreshToken: refreshToken || this.state.refreshToken,
|
|
677
|
+
isAuthenticated: !!((token || this.state.token) && user),
|
|
678
|
+
expiresAt: this.calculateExpiry(data) || this.state.expiresAt,
|
|
679
|
+
tokenType
|
|
680
|
+
};
|
|
681
|
+
await this.persist();
|
|
682
|
+
if (token || user) this.events.emit("auth:login", this.state);
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Check if current user is superadmin
|
|
686
|
+
*/
|
|
687
|
+
isSuperadmin() {
|
|
688
|
+
return isSuperadmin(this.state.user);
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Check if current session uses API key authentication
|
|
692
|
+
*/
|
|
693
|
+
isApiKeySession() {
|
|
694
|
+
return this.state.tokenType === TokenType.API_KEY;
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Check if current session uses personal token authentication
|
|
698
|
+
*/
|
|
699
|
+
isPersonalTokenSession() {
|
|
700
|
+
return this.state.tokenType === TokenType.PERSONAL_TOKEN;
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Check if current session uses OAuth authentication
|
|
704
|
+
*/
|
|
705
|
+
isOAuthSession() {
|
|
706
|
+
return this.state.tokenType === TokenType.OAUTH;
|
|
707
|
+
}
|
|
708
|
+
calculateExpiry(data) {
|
|
709
|
+
if (data.expiresAt) return data.expiresAt;
|
|
710
|
+
if (data.expires_in) return new Date(Date.now() + data.expires_in * 1e3).toISOString();
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
async setState(newState) {
|
|
714
|
+
this.state = {
|
|
715
|
+
...this.state,
|
|
716
|
+
...newState
|
|
717
|
+
};
|
|
718
|
+
this.state.isAuthenticated = !!(this.state.token && this.state.user);
|
|
719
|
+
await this.persist();
|
|
720
|
+
if (newState.token || newState.user) this.events.emit("auth:login", this.state);
|
|
721
|
+
}
|
|
722
|
+
async clear() {
|
|
723
|
+
this.state = { ...DEFAULT_AUTH_STATE };
|
|
724
|
+
await this.storage.removeItem(this.storageKey);
|
|
725
|
+
this.events.emit("auth:logout");
|
|
726
|
+
}
|
|
727
|
+
on(event, listener) {
|
|
728
|
+
return this.events.on(event, listener);
|
|
729
|
+
}
|
|
730
|
+
async hydrate() {
|
|
731
|
+
try {
|
|
732
|
+
const stored = await this.storage.getItem(this.storageKey);
|
|
733
|
+
if (stored) {
|
|
734
|
+
const parsed = JSON.parse(stored);
|
|
735
|
+
this.state = {
|
|
736
|
+
...DEFAULT_AUTH_STATE,
|
|
737
|
+
...parsed,
|
|
738
|
+
isAuthenticated: !!(parsed.token && parsed.user)
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
} catch (error) {
|
|
742
|
+
console.error("Failed to hydrate auth state:", error);
|
|
743
|
+
await this.clear();
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
async persist() {
|
|
747
|
+
try {
|
|
748
|
+
await this.storage.setItem(this.storageKey, JSON.stringify(this.state));
|
|
749
|
+
} catch (error) {
|
|
750
|
+
console.error("Failed to persist auth state:", error);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
validateSession() {
|
|
754
|
+
if (!this.state.expiresAt) return;
|
|
755
|
+
if (new Date(this.state.expiresAt).getTime() <= Date.now()) this.clear();
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
//#endregion
|
|
760
|
+
//#region src/core/auth-service.ts
|
|
761
|
+
/**
|
|
762
|
+
* Service for handling authentication operations.
|
|
763
|
+
*/
|
|
764
|
+
var AuthService = class {
|
|
765
|
+
oauthStates = /* @__PURE__ */ new Map();
|
|
766
|
+
STATE_EXPIRY_MS = 600 * 1e3;
|
|
767
|
+
constructor(http, auth, apiKey, defaultAccount) {
|
|
768
|
+
this.http = http;
|
|
769
|
+
this.auth = auth;
|
|
770
|
+
this.apiKey = apiKey;
|
|
771
|
+
this.defaultAccount = defaultAccount;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Helper to ensure API key is not used for user-specific operations.
|
|
775
|
+
*/
|
|
776
|
+
checkApiKeyRestriction() {
|
|
777
|
+
if (this.apiKey && !this.auth.token) {}
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Authenticate a user with email and password.
|
|
781
|
+
*/
|
|
782
|
+
async login(credentials) {
|
|
783
|
+
const data = { ...credentials };
|
|
784
|
+
if (!data.account && this.defaultAccount) data.account = this.defaultAccount;
|
|
785
|
+
const authData = (await this.http.post("/api/v1/auth/login", data)).data;
|
|
786
|
+
console.log("Login Response:", JSON.stringify(authData, null, 2));
|
|
787
|
+
await this.auth.updateState(authData);
|
|
788
|
+
return authData;
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Register a new user and account.
|
|
792
|
+
*/
|
|
793
|
+
async register(data) {
|
|
794
|
+
const payload = { ...data };
|
|
795
|
+
if (!payload.account_name && this.defaultAccount) payload.account_name = this.defaultAccount;
|
|
796
|
+
return (await this.http.post("/api/v1/auth/register", payload)).data;
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Refresh the access token using the refresh token.
|
|
800
|
+
*/
|
|
801
|
+
async refreshToken() {
|
|
802
|
+
const refreshToken = this.auth.refreshToken;
|
|
803
|
+
if (!refreshToken) throw new Error("No refresh token available");
|
|
804
|
+
const authData = (await this.http.post("/api/v1/auth/refresh", { refresh_token: refreshToken })).data;
|
|
805
|
+
await this.auth.updateState(authData);
|
|
806
|
+
return authData;
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Log out the current user.
|
|
810
|
+
*/
|
|
811
|
+
async logout() {
|
|
812
|
+
try {
|
|
813
|
+
await this.http.post("/api/v1/auth/logout", {});
|
|
814
|
+
} catch (e) {} finally {
|
|
815
|
+
await this.auth.clear();
|
|
816
|
+
}
|
|
817
|
+
return { success: true };
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Get the current authenticated user profile.
|
|
821
|
+
*/
|
|
822
|
+
async getCurrentUser() {
|
|
823
|
+
const authData = (await this.http.get("/api/v1/auth/me")).data;
|
|
824
|
+
console.log("GetMe Response:", JSON.stringify(authData, null, 2));
|
|
825
|
+
await this.auth.updateState(authData);
|
|
826
|
+
return authData;
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Initiate password reset flow.
|
|
830
|
+
*/
|
|
831
|
+
async forgotPassword(data) {
|
|
832
|
+
const payload = { ...data };
|
|
833
|
+
if (!payload.account && this.defaultAccount) payload.account = this.defaultAccount;
|
|
834
|
+
return (await this.http.post("/api/v1/auth/forgot-password", payload)).data;
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Reset password using a token.
|
|
838
|
+
*/
|
|
839
|
+
async resetPassword(data) {
|
|
840
|
+
return (await this.http.post("/api/v1/auth/reset-password", data)).data;
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Verify email using a token.
|
|
844
|
+
*/
|
|
845
|
+
async verifyEmail(token) {
|
|
846
|
+
return (await this.http.post("/api/v1/auth/verify-email", { token })).data;
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Resend the verification email to the current user.
|
|
850
|
+
*/
|
|
851
|
+
async resendVerificationEmail() {
|
|
852
|
+
return (await this.http.post("/api/v1/auth/resend-verification", {})).data;
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Send a verification email to a specific email address.
|
|
856
|
+
* This can be used by admins to send verification emails to users.
|
|
857
|
+
*/
|
|
858
|
+
async sendVerification(email) {
|
|
859
|
+
return (await this.http.post("/api/v1/auth/send-verification", { email })).data;
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Verify a password reset token is valid.
|
|
863
|
+
* Returns the email associated with the token if valid.
|
|
864
|
+
*/
|
|
865
|
+
async verifyResetToken(token) {
|
|
866
|
+
return (await this.http.get(`/api/v1/auth/verify-reset-token/${token}`)).data;
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Generate OAuth authorization URL for the specified provider.
|
|
870
|
+
*/
|
|
871
|
+
async getOAuthUrl(provider, redirectUri, state) {
|
|
872
|
+
this.checkApiKeyRestriction();
|
|
873
|
+
const response = await this.http.post(`/api/v1/auth/oauth/${provider}/authorize`, {
|
|
874
|
+
redirectUri,
|
|
875
|
+
state
|
|
876
|
+
});
|
|
877
|
+
const { url, state: stateToken } = response.data;
|
|
878
|
+
this.oauthStates.set(stateToken, Date.now() + this.STATE_EXPIRY_MS);
|
|
879
|
+
this.cleanupExpiredStates();
|
|
880
|
+
return response.data;
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Handle OAuth callback and authenticate user.
|
|
884
|
+
*/
|
|
885
|
+
async handleOAuthCallback(params) {
|
|
886
|
+
this.checkApiKeyRestriction();
|
|
887
|
+
const { provider, code, redirectUri, state } = params;
|
|
888
|
+
const expiry = this.oauthStates.get(state);
|
|
889
|
+
if (!expiry || expiry < Date.now()) {
|
|
890
|
+
this.oauthStates.delete(state);
|
|
891
|
+
throw new AuthenticationError("Invalid or expired state token", { code: "INVALID_STATE" });
|
|
892
|
+
}
|
|
893
|
+
this.oauthStates.delete(state);
|
|
894
|
+
const authData = (await this.http.post(`/api/v1/auth/oauth/${provider}/callback`, {
|
|
895
|
+
code,
|
|
896
|
+
redirectUri,
|
|
897
|
+
state
|
|
898
|
+
})).data;
|
|
899
|
+
await this.auth.updateState(authData);
|
|
900
|
+
return authData;
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Generate SAML SSO authorization URL for the specified provider and account.
|
|
904
|
+
*/
|
|
905
|
+
async getSAMLUrl(provider, account, relayState) {
|
|
906
|
+
this.checkApiKeyRestriction();
|
|
907
|
+
return (await this.http.get("/api/v1/auth/saml/sso", { params: {
|
|
908
|
+
provider,
|
|
909
|
+
account,
|
|
910
|
+
relayState
|
|
911
|
+
} })).data;
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* Handle SAML callback (ACS) and authenticate user.
|
|
915
|
+
*/
|
|
916
|
+
async handleSAMLCallback(params) {
|
|
917
|
+
const authData = (await this.http.post("/api/v1/auth/saml/acs", params)).data;
|
|
918
|
+
await this.auth.updateState(authData);
|
|
919
|
+
return authData;
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* Get SAML metadata for the specified provider and account.
|
|
923
|
+
*/
|
|
924
|
+
async getSAMLMetadata(provider, account) {
|
|
925
|
+
return (await this.http.get("/api/v1/auth/saml/metadata", { params: {
|
|
926
|
+
provider,
|
|
927
|
+
account
|
|
928
|
+
} })).data;
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Cleanup expired state tokens.
|
|
932
|
+
*/
|
|
933
|
+
cleanupExpiredStates() {
|
|
934
|
+
const now = Date.now();
|
|
935
|
+
for (const [state, expiry] of this.oauthStates.entries()) if (expiry < now) this.oauthStates.delete(state);
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
|
|
939
|
+
//#endregion
|
|
940
|
+
//#region src/core/account-service.ts
|
|
941
|
+
/**
|
|
942
|
+
* Service for managing accounts and their users.
|
|
943
|
+
* Requires superadmin authentication.
|
|
944
|
+
*/
|
|
945
|
+
var AccountService = class {
|
|
946
|
+
constructor(http) {
|
|
947
|
+
this.http = http;
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* List all accounts with pagination, filtering, and sorting.
|
|
951
|
+
*/
|
|
952
|
+
async list(params) {
|
|
953
|
+
return (await this.http.get("/api/v1/accounts", { params })).data;
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Get details for a specific account.
|
|
957
|
+
*/
|
|
958
|
+
async get(accountId) {
|
|
959
|
+
return (await this.http.get(`/api/v1/accounts/${accountId}`)).data;
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Create a new account.
|
|
963
|
+
*/
|
|
964
|
+
async create(data) {
|
|
965
|
+
return (await this.http.post("/api/v1/accounts", data)).data;
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Update an existing account.
|
|
969
|
+
*/
|
|
970
|
+
async update(accountId, data) {
|
|
971
|
+
return (await this.http.patch(`/api/v1/accounts/${accountId}`, data)).data;
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Delete an account and all its associated data.
|
|
975
|
+
*/
|
|
976
|
+
async delete(accountId) {
|
|
977
|
+
await this.http.delete(`/api/v1/accounts/${accountId}`);
|
|
978
|
+
return { success: true };
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Get all users belonging to a specific account.
|
|
982
|
+
*/
|
|
983
|
+
async getUsers(accountId, params) {
|
|
984
|
+
return (await this.http.get(`/api/v1/accounts/${accountId}/users`, { params })).data;
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
//#endregion
|
|
989
|
+
//#region src/core/user-service.ts
|
|
990
|
+
/**
|
|
991
|
+
* Service for managing users.
|
|
992
|
+
* Requires superadmin authentication for most operations.
|
|
993
|
+
*/
|
|
994
|
+
var UserService = class {
|
|
995
|
+
constructor(http) {
|
|
996
|
+
this.http = http;
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* List all users with pagination, filtering, and sorting.
|
|
1000
|
+
*/
|
|
1001
|
+
async list(params) {
|
|
1002
|
+
return (await this.http.get("/api/v1/users", { params })).data;
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Get details for a specific user.
|
|
1006
|
+
*/
|
|
1007
|
+
async get(userId) {
|
|
1008
|
+
return (await this.http.get(`/api/v1/users/${userId}`)).data;
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Create a new user in a specific account.
|
|
1012
|
+
*/
|
|
1013
|
+
async create(data) {
|
|
1014
|
+
return (await this.http.post("/api/v1/users", data)).data;
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Update an existing user.
|
|
1018
|
+
*/
|
|
1019
|
+
async update(userId, data) {
|
|
1020
|
+
return (await this.http.patch(`/api/v1/users/${userId}`, data)).data;
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Soft delete (deactivate) a user.
|
|
1024
|
+
*/
|
|
1025
|
+
async delete(userId) {
|
|
1026
|
+
await this.http.delete(`/api/v1/users/${userId}`);
|
|
1027
|
+
return { success: true };
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Manually set a new password for a user.
|
|
1031
|
+
*/
|
|
1032
|
+
async setPassword(userId, password) {
|
|
1033
|
+
await this.http.post(`/api/v1/users/${userId}/password`, { password });
|
|
1034
|
+
return { success: true };
|
|
1035
|
+
}
|
|
1036
|
+
/**
|
|
1037
|
+
* Manually verify a user's email address.
|
|
1038
|
+
*/
|
|
1039
|
+
async verifyEmail(userId) {
|
|
1040
|
+
await this.http.post(`/api/v1/users/${userId}/verify`, {});
|
|
1041
|
+
return { success: true };
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Resend the verification email to a user.
|
|
1045
|
+
*/
|
|
1046
|
+
async resendVerification(userId) {
|
|
1047
|
+
await this.http.post(`/api/v1/users/${userId}/resend-verification`, {});
|
|
1048
|
+
return { success: true };
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
|
|
1052
|
+
//#endregion
|
|
1053
|
+
//#region src/core/collection-service.ts
|
|
1054
|
+
/**
|
|
1055
|
+
* Service for managing collections and their schemas.
|
|
1056
|
+
* Requires superadmin authentication.
|
|
1057
|
+
*/
|
|
1058
|
+
var CollectionService = class {
|
|
1059
|
+
constructor(http) {
|
|
1060
|
+
this.http = http;
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* List all collections.
|
|
1064
|
+
*/
|
|
1065
|
+
async list() {
|
|
1066
|
+
return (await this.http.get("/api/v1/collections")).data;
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* List collection names only.
|
|
1070
|
+
*/
|
|
1071
|
+
async listNames() {
|
|
1072
|
+
return (await this.http.get("/api/v1/collections/names")).data;
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Get schema details for a specific collection.
|
|
1076
|
+
*/
|
|
1077
|
+
async get(collectionId) {
|
|
1078
|
+
return (await this.http.get(`/api/v1/collections/${collectionId}`)).data;
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Create a new collection and its physical table.
|
|
1082
|
+
*/
|
|
1083
|
+
async create(data) {
|
|
1084
|
+
return (await this.http.post("/api/v1/collections", data)).data;
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Update an existing collection schema.
|
|
1088
|
+
* Note: Field types cannot be changed for data safety.
|
|
1089
|
+
*/
|
|
1090
|
+
async update(collectionId, data) {
|
|
1091
|
+
return (await this.http.patch(`/api/v1/collections/${collectionId}`, data)).data;
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Delete a collection and drop its physical table.
|
|
1095
|
+
*/
|
|
1096
|
+
async delete(collectionId) {
|
|
1097
|
+
await this.http.delete(`/api/v1/collections/${collectionId}`);
|
|
1098
|
+
return { success: true };
|
|
1099
|
+
}
|
|
1100
|
+
/**
|
|
1101
|
+
* Export collections to JSON format.
|
|
1102
|
+
* Returns collection schemas and rules for backup or migration.
|
|
1103
|
+
*
|
|
1104
|
+
* @param params Optional filter by collection IDs
|
|
1105
|
+
* @returns Complete export data structure with collections, schemas, and rules
|
|
1106
|
+
* @throws {AuthorizationError} If user is not a superadmin
|
|
1107
|
+
*
|
|
1108
|
+
* @example
|
|
1109
|
+
* // Export all collections
|
|
1110
|
+
* const exportData = await client.collections.export();
|
|
1111
|
+
*
|
|
1112
|
+
* @example
|
|
1113
|
+
* // Export specific collections
|
|
1114
|
+
* const exportData = await client.collections.export({
|
|
1115
|
+
* collection_ids: ['col-123', 'col-456']
|
|
1116
|
+
* });
|
|
1117
|
+
*/
|
|
1118
|
+
async export(params) {
|
|
1119
|
+
const queryParams = {};
|
|
1120
|
+
if (params?.collection_ids && params.collection_ids.length > 0) queryParams.collection_ids = params.collection_ids.join(",");
|
|
1121
|
+
return (await this.http.get("/api/v1/collections/export", { params: queryParams })).data;
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Import collections from JSON export.
|
|
1125
|
+
*
|
|
1126
|
+
* @param request Import request with data and conflict strategy
|
|
1127
|
+
* @returns Import result with per-collection status and migration IDs
|
|
1128
|
+
* @throws {ValidationError} If import data is invalid
|
|
1129
|
+
* @throws {ConflictError} If collection exists and strategy is 'error'
|
|
1130
|
+
* @throws {AuthorizationError} If user is not a superadmin
|
|
1131
|
+
*
|
|
1132
|
+
* @example
|
|
1133
|
+
* // Import with error strategy (fail on conflicts)
|
|
1134
|
+
* const result = await client.collections.import({
|
|
1135
|
+
* data: exportData,
|
|
1136
|
+
* strategy: 'error'
|
|
1137
|
+
* });
|
|
1138
|
+
*
|
|
1139
|
+
* @example
|
|
1140
|
+
* // Import with skip strategy (skip existing collections)
|
|
1141
|
+
* const result = await client.collections.import({
|
|
1142
|
+
* data: exportData,
|
|
1143
|
+
* strategy: 'skip'
|
|
1144
|
+
* });
|
|
1145
|
+
*
|
|
1146
|
+
* @example
|
|
1147
|
+
* // Import with update strategy (update existing collections)
|
|
1148
|
+
* const result = await client.collections.import({
|
|
1149
|
+
* data: exportData,
|
|
1150
|
+
* strategy: 'update'
|
|
1151
|
+
* });
|
|
1152
|
+
*/
|
|
1153
|
+
async import(request) {
|
|
1154
|
+
return (await this.http.post("/api/v1/collections/import", request)).data;
|
|
1155
|
+
}
|
|
1156
|
+
};
|
|
1157
|
+
|
|
1158
|
+
//#endregion
|
|
1159
|
+
//#region src/core/query-builder.ts
|
|
1160
|
+
/**
|
|
1161
|
+
* Fluent interface for building complex queries.
|
|
1162
|
+
*/
|
|
1163
|
+
var QueryBuilder = class {
|
|
1164
|
+
_fields = [];
|
|
1165
|
+
_expand = [];
|
|
1166
|
+
_filterParts = [];
|
|
1167
|
+
_sortParts = [];
|
|
1168
|
+
_page = 1;
|
|
1169
|
+
_perPage = 30;
|
|
1170
|
+
_skip = 0;
|
|
1171
|
+
_limit = 30;
|
|
1172
|
+
_useLegacyPagination = false;
|
|
1173
|
+
constructor(service, collection) {
|
|
1174
|
+
this.service = service;
|
|
1175
|
+
this.collection = collection;
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Specify fields to return.
|
|
1179
|
+
* @param fields Array of field names or comma-separated string
|
|
1180
|
+
*/
|
|
1181
|
+
select(fields) {
|
|
1182
|
+
if (Array.isArray(fields)) this._fields = [...this._fields, ...fields];
|
|
1183
|
+
else this._fields.push(fields);
|
|
1184
|
+
return this;
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Expand related records.
|
|
1188
|
+
* @param relations Array of relation names or comma-separated string
|
|
1189
|
+
*/
|
|
1190
|
+
expand(relations) {
|
|
1191
|
+
if (Array.isArray(relations)) this._expand = [...this._expand, ...relations];
|
|
1192
|
+
else this._expand.push(relations);
|
|
1193
|
+
return this;
|
|
1194
|
+
}
|
|
1195
|
+
filter(fieldOrString, operator, value) {
|
|
1196
|
+
if (operator === void 0) this._filterParts.push(`(${fieldOrString})`);
|
|
1197
|
+
else {
|
|
1198
|
+
let expression = "";
|
|
1199
|
+
const formattedValue = this.formatValue(value);
|
|
1200
|
+
switch (operator) {
|
|
1201
|
+
case "=":
|
|
1202
|
+
expression = `${fieldOrString} = ${formattedValue}`;
|
|
1203
|
+
break;
|
|
1204
|
+
case "!=":
|
|
1205
|
+
expression = `${fieldOrString} != ${formattedValue}`;
|
|
1206
|
+
break;
|
|
1207
|
+
case ">":
|
|
1208
|
+
expression = `${fieldOrString} > ${formattedValue}`;
|
|
1209
|
+
break;
|
|
1210
|
+
case ">=":
|
|
1211
|
+
expression = `${fieldOrString} >= ${formattedValue}`;
|
|
1212
|
+
break;
|
|
1213
|
+
case "<":
|
|
1214
|
+
expression = `${fieldOrString} < ${formattedValue}`;
|
|
1215
|
+
break;
|
|
1216
|
+
case "<=":
|
|
1217
|
+
expression = `${fieldOrString} <= ${formattedValue}`;
|
|
1218
|
+
break;
|
|
1219
|
+
case "~":
|
|
1220
|
+
expression = `${fieldOrString} ~ ${formattedValue}`;
|
|
1221
|
+
break;
|
|
1222
|
+
case "!~":
|
|
1223
|
+
expression = `${fieldOrString} !~ ${formattedValue}`;
|
|
1224
|
+
break;
|
|
1225
|
+
case "?=":
|
|
1226
|
+
expression = `${fieldOrString} ?= ${formattedValue}`;
|
|
1227
|
+
break;
|
|
1228
|
+
case "?!=":
|
|
1229
|
+
expression = `${fieldOrString} ?!= ${formattedValue}`;
|
|
1230
|
+
break;
|
|
1231
|
+
default: expression = `${fieldOrString} = ${formattedValue}`;
|
|
1232
|
+
}
|
|
1233
|
+
this._filterParts.push(expression);
|
|
1234
|
+
}
|
|
1235
|
+
return this;
|
|
1236
|
+
}
|
|
1237
|
+
/**
|
|
1238
|
+
* Add sorting.
|
|
1239
|
+
* @param field Field name
|
|
1240
|
+
* @param direction 'asc' or 'desc' (default: 'asc')
|
|
1241
|
+
*/
|
|
1242
|
+
sort(field, direction = "asc") {
|
|
1243
|
+
let sortStr = field;
|
|
1244
|
+
if (direction === "desc") sortStr = `-${field}`;
|
|
1245
|
+
else sortStr = `+${field}`;
|
|
1246
|
+
this._sortParts.push(sortStr);
|
|
1247
|
+
return this;
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Set limit (and optionally skip).
|
|
1251
|
+
* Note: Using limit/skip switches to manual offset pagination.
|
|
1252
|
+
* @param limit Max records
|
|
1253
|
+
* @param skip Records to skip
|
|
1254
|
+
*/
|
|
1255
|
+
limit(limit) {
|
|
1256
|
+
this._limit = limit;
|
|
1257
|
+
this._useLegacyPagination = true;
|
|
1258
|
+
return this;
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Set skip.
|
|
1262
|
+
* Note: Using limit/skip switches to manual offset pagination.
|
|
1263
|
+
* @param skip Records to skip
|
|
1264
|
+
*/
|
|
1265
|
+
skip(skip) {
|
|
1266
|
+
this._skip = skip;
|
|
1267
|
+
this._useLegacyPagination = true;
|
|
1268
|
+
return this;
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Set page number and page size.
|
|
1272
|
+
* @param page Page number (1-based)
|
|
1273
|
+
* @param perPage Records per page
|
|
1274
|
+
*/
|
|
1275
|
+
page(page, perPage = 30) {
|
|
1276
|
+
this._page = page;
|
|
1277
|
+
this._perPage = perPage;
|
|
1278
|
+
this._useLegacyPagination = false;
|
|
1279
|
+
return this;
|
|
1280
|
+
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Execute query and get list of records.
|
|
1283
|
+
*/
|
|
1284
|
+
async get() {
|
|
1285
|
+
const params = {};
|
|
1286
|
+
if (this._fields.length > 0) params.fields = this._fields.join(",");
|
|
1287
|
+
if (this._expand.length > 0) params.expand = this._expand.join(",");
|
|
1288
|
+
if (this._filterParts.length > 0) params.filter = this._filterParts.join(" && ");
|
|
1289
|
+
if (this._sortParts.length > 0) params.sort = this._sortParts.join(",");
|
|
1290
|
+
if (this._useLegacyPagination) {
|
|
1291
|
+
params.skip = this._skip;
|
|
1292
|
+
params.limit = this._limit;
|
|
1293
|
+
} else {
|
|
1294
|
+
if (this._page < 1) this._page = 1;
|
|
1295
|
+
params.skip = (this._page - 1) * this._perPage;
|
|
1296
|
+
params.limit = this._perPage;
|
|
1297
|
+
}
|
|
1298
|
+
return this.service.list(this.collection, params);
|
|
1299
|
+
}
|
|
1300
|
+
/**
|
|
1301
|
+
* Execute query and get the first matching record.
|
|
1302
|
+
*/
|
|
1303
|
+
async first() {
|
|
1304
|
+
this.limit(1);
|
|
1305
|
+
this.skip(0);
|
|
1306
|
+
const result = await this.get();
|
|
1307
|
+
return result.items.length > 0 ? result.items[0] : null;
|
|
1308
|
+
}
|
|
1309
|
+
formatValue(value) {
|
|
1310
|
+
if (value === null || value === void 0) return "null";
|
|
1311
|
+
if (typeof value === "string") return `'${value.replace(/'/g, "\\'")}'`;
|
|
1312
|
+
return String(value);
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
|
|
1316
|
+
//#endregion
|
|
1317
|
+
//#region src/core/record-service.ts
|
|
1318
|
+
/**
|
|
1319
|
+
* Service for performing CRUD operations on dynamic collections.
|
|
1320
|
+
*/
|
|
1321
|
+
var RecordService = class {
|
|
1322
|
+
constructor(http) {
|
|
1323
|
+
this.http = http;
|
|
1324
|
+
}
|
|
1325
|
+
/**
|
|
1326
|
+
* List records from a collection with pagination, filtering, and sorting.
|
|
1327
|
+
* @template T The record type
|
|
1328
|
+
* @param collection Collection name
|
|
1329
|
+
* @param params Query parameters
|
|
1330
|
+
*/
|
|
1331
|
+
async list(collection, params) {
|
|
1332
|
+
const formattedParams = {};
|
|
1333
|
+
if (params) {
|
|
1334
|
+
if (params.skip !== void 0) formattedParams.skip = params.skip;
|
|
1335
|
+
if (params.limit !== void 0) formattedParams.limit = params.limit;
|
|
1336
|
+
if (params.sort !== void 0) formattedParams.sort = params.sort;
|
|
1337
|
+
if (params.fields) formattedParams.fields = Array.isArray(params.fields) ? params.fields.join(",") : params.fields;
|
|
1338
|
+
if (params.expand) formattedParams.expand = Array.isArray(params.expand) ? params.expand.join(",") : params.expand;
|
|
1339
|
+
if (params.filter) {
|
|
1340
|
+
const filterStr = typeof params.filter === "string" ? params.filter : JSON.stringify(params.filter);
|
|
1341
|
+
const match = filterStr.match(/^\(?\s*(\w+)\s*=\s*(["']?)([^"'\)]+)\2\s*\)?$/);
|
|
1342
|
+
if (match) {
|
|
1343
|
+
const [, key, , value] = match;
|
|
1344
|
+
formattedParams[key] = value;
|
|
1345
|
+
} else formattedParams.filter = filterStr;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
return (await this.http.get(`/api/v1/records/${collection}`, { params: formattedParams })).data;
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Create a query builder for the collection.
|
|
1352
|
+
* @template T The record type
|
|
1353
|
+
* @param collection Collection name
|
|
1354
|
+
*/
|
|
1355
|
+
query(collection) {
|
|
1356
|
+
return new QueryBuilder(this, collection);
|
|
1357
|
+
}
|
|
1358
|
+
/**
|
|
1359
|
+
* Get a single record by ID.
|
|
1360
|
+
* @template T The record type
|
|
1361
|
+
* @param collection Collection name
|
|
1362
|
+
* @param recordId Record ID
|
|
1363
|
+
* @param params Optional query parameters (e.g., fields)
|
|
1364
|
+
*/
|
|
1365
|
+
async get(collection, recordId, params) {
|
|
1366
|
+
const formattedParams = {};
|
|
1367
|
+
if (params) {
|
|
1368
|
+
if (params.fields) formattedParams.fields = Array.isArray(params.fields) ? params.fields.join(",") : params.fields;
|
|
1369
|
+
if (params.expand) formattedParams.expand = Array.isArray(params.expand) ? params.expand.join(",") : params.expand;
|
|
1370
|
+
}
|
|
1371
|
+
return (await this.http.get(`/api/v1/records/${collection}/${recordId}`, { params: formattedParams })).data;
|
|
1372
|
+
}
|
|
1373
|
+
/**
|
|
1374
|
+
* Create a new record in a collection.
|
|
1375
|
+
* @template T The record type
|
|
1376
|
+
* @param collection Collection name
|
|
1377
|
+
* @param data Record data
|
|
1378
|
+
*/
|
|
1379
|
+
async create(collection, data) {
|
|
1380
|
+
return (await this.http.post(`/api/v1/records/${collection}`, data)).data;
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* Full update of an existing record (PUT).
|
|
1384
|
+
* @template T The record type
|
|
1385
|
+
* @param collection Collection name
|
|
1386
|
+
* @param recordId Record ID
|
|
1387
|
+
* @param data Record data
|
|
1388
|
+
*/
|
|
1389
|
+
async update(collection, recordId, data) {
|
|
1390
|
+
return (await this.http.put(`/api/v1/records/${collection}/${recordId}`, data)).data;
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* Partial update of an existing record (PATCH).
|
|
1394
|
+
* @template T The record type
|
|
1395
|
+
* @param collection Collection name
|
|
1396
|
+
* @param recordId Record ID
|
|
1397
|
+
* @param data Partial record data
|
|
1398
|
+
*/
|
|
1399
|
+
async patch(collection, recordId, data) {
|
|
1400
|
+
return (await this.http.patch(`/api/v1/records/${collection}/${recordId}`, data)).data;
|
|
1401
|
+
}
|
|
1402
|
+
/**
|
|
1403
|
+
* Delete a record from a collection.
|
|
1404
|
+
* @param collection Collection name
|
|
1405
|
+
* @param recordId Record ID
|
|
1406
|
+
*/
|
|
1407
|
+
async delete(collection, recordId) {
|
|
1408
|
+
await this.http.delete(`/api/v1/records/${collection}/${recordId}`);
|
|
1409
|
+
return { success: true };
|
|
1410
|
+
}
|
|
1411
|
+
};
|
|
1412
|
+
|
|
1413
|
+
//#endregion
|
|
1414
|
+
//#region src/core/group-service.ts
|
|
1415
|
+
/**
|
|
1416
|
+
* Service for managing groups.
|
|
1417
|
+
*/
|
|
1418
|
+
var GroupsService = class {
|
|
1419
|
+
constructor(http) {
|
|
1420
|
+
this.http = http;
|
|
1421
|
+
}
|
|
1422
|
+
/**
|
|
1423
|
+
* List all groups in the current account.
|
|
1424
|
+
*/
|
|
1425
|
+
async list(params) {
|
|
1426
|
+
return (await this.http.get("/api/v1/groups", { params })).data;
|
|
1427
|
+
}
|
|
1428
|
+
/**
|
|
1429
|
+
* Get details for a specific group.
|
|
1430
|
+
*/
|
|
1431
|
+
async get(groupId) {
|
|
1432
|
+
return (await this.http.get(`/api/v1/groups/${groupId}`)).data;
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
1435
|
+
* Create a new group in the current account.
|
|
1436
|
+
*/
|
|
1437
|
+
async create(data) {
|
|
1438
|
+
return (await this.http.post("/api/v1/groups", data)).data;
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Update a group's name or description.
|
|
1442
|
+
*/
|
|
1443
|
+
async update(groupId, data) {
|
|
1444
|
+
return (await this.http.patch(`/api/v1/groups/${groupId}`, data)).data;
|
|
1445
|
+
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Delete a group.
|
|
1448
|
+
*/
|
|
1449
|
+
async delete(groupId) {
|
|
1450
|
+
await this.http.delete(`/api/v1/groups/${groupId}`);
|
|
1451
|
+
return { success: true };
|
|
1452
|
+
}
|
|
1453
|
+
/**
|
|
1454
|
+
* Add a user to a group.
|
|
1455
|
+
*/
|
|
1456
|
+
async addMember(groupId, userId) {
|
|
1457
|
+
await this.http.post(`/api/v1/groups/${groupId}/members`, { user_id: userId });
|
|
1458
|
+
return { success: true };
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* Remove a user from a group.
|
|
1462
|
+
*/
|
|
1463
|
+
async removeMember(groupId, userId) {
|
|
1464
|
+
await this.http.delete(`/api/v1/groups/${groupId}/members/${userId}`);
|
|
1465
|
+
return { success: true };
|
|
1466
|
+
}
|
|
1467
|
+
};
|
|
1468
|
+
|
|
1469
|
+
//#endregion
|
|
1470
|
+
//#region src/core/invitation-service.ts
|
|
1471
|
+
/**
|
|
1472
|
+
* Service for managing user invitations.
|
|
1473
|
+
* Allows admins to invite users and tracks invitation status.
|
|
1474
|
+
*/
|
|
1475
|
+
var InvitationService = class {
|
|
1476
|
+
constructor(http) {
|
|
1477
|
+
this.http = http;
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* List all invitations in the current account.
|
|
1481
|
+
*/
|
|
1482
|
+
async list(params) {
|
|
1483
|
+
return (await this.http.get("/api/v1/invitations", { params })).data;
|
|
1484
|
+
}
|
|
1485
|
+
/**
|
|
1486
|
+
* Create a new invitation for a user.
|
|
1487
|
+
*/
|
|
1488
|
+
async create(data) {
|
|
1489
|
+
return (await this.http.post("/api/v1/invitations", data)).data;
|
|
1490
|
+
}
|
|
1491
|
+
/**
|
|
1492
|
+
* Resend an invitation email.
|
|
1493
|
+
*/
|
|
1494
|
+
async resend(invitationId) {
|
|
1495
|
+
await this.http.post(`/api/v1/invitations/${invitationId}/resend`, {});
|
|
1496
|
+
return { success: true };
|
|
1497
|
+
}
|
|
1498
|
+
/**
|
|
1499
|
+
* Get public details of an invitation using a token.
|
|
1500
|
+
* No authentication required.
|
|
1501
|
+
*/
|
|
1502
|
+
async getPublic(token) {
|
|
1503
|
+
return (await this.http.get(`/api/v1/invitations/${token}`)).data;
|
|
1504
|
+
}
|
|
1505
|
+
/**
|
|
1506
|
+
* Accept an invitation using a token and password.
|
|
1507
|
+
* Creates the user account and returns authentication tokens.
|
|
1508
|
+
*/
|
|
1509
|
+
async accept(token, password) {
|
|
1510
|
+
return (await this.http.post(`/api/v1/invitations/${token}/accept`, { password })).data;
|
|
1511
|
+
}
|
|
1512
|
+
/**
|
|
1513
|
+
* Cancel a pending invitation.
|
|
1514
|
+
*/
|
|
1515
|
+
async cancel(invitationId) {
|
|
1516
|
+
await this.http.delete(`/api/v1/invitations/${invitationId}`);
|
|
1517
|
+
return { success: true };
|
|
1518
|
+
}
|
|
1519
|
+
};
|
|
1520
|
+
|
|
1521
|
+
//#endregion
|
|
1522
|
+
//#region src/core/api-key-service.ts
|
|
1523
|
+
/**
|
|
1524
|
+
* Service for managing API keys.
|
|
1525
|
+
* API keys are used for service-to-service communication.
|
|
1526
|
+
*/
|
|
1527
|
+
var ApiKeyService = class {
|
|
1528
|
+
constructor(http) {
|
|
1529
|
+
this.http = http;
|
|
1530
|
+
}
|
|
1531
|
+
/**
|
|
1532
|
+
* List all API keys
|
|
1533
|
+
* GET /api/v1/admin/api-keys
|
|
1534
|
+
*/
|
|
1535
|
+
async list(params) {
|
|
1536
|
+
return (await this.http.get(API_KEY_BASE_PATH, { params })).data;
|
|
1537
|
+
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Get specific API key
|
|
1540
|
+
* GET /api/v1/admin/api-keys/{id}
|
|
1541
|
+
*/
|
|
1542
|
+
async get(keyId) {
|
|
1543
|
+
return (await this.http.get(`${API_KEY_BASE_PATH}/${encodeURIComponent(keyId)}`)).data;
|
|
1544
|
+
}
|
|
1545
|
+
/**
|
|
1546
|
+
* Create a new API key
|
|
1547
|
+
* POST /api/v1/admin/api-keys
|
|
1548
|
+
*/
|
|
1549
|
+
async create(data) {
|
|
1550
|
+
const apiKey = (await this.http.post(API_KEY_BASE_PATH, data)).data;
|
|
1551
|
+
if (apiKey.key && !apiKey.masked_key) apiKey.masked_key = formatMaskedKey(apiKey.key);
|
|
1552
|
+
return apiKey;
|
|
1553
|
+
}
|
|
1554
|
+
/**
|
|
1555
|
+
* Revoke an API key
|
|
1556
|
+
* DELETE /api/v1/admin/api-keys/{id}
|
|
1557
|
+
*/
|
|
1558
|
+
async revoke(keyId) {
|
|
1559
|
+
return (await this.http.delete(`${API_KEY_BASE_PATH}/${encodeURIComponent(keyId)}`)).data;
|
|
1560
|
+
}
|
|
1561
|
+
};
|
|
1562
|
+
|
|
1563
|
+
//#endregion
|
|
1564
|
+
//#region src/core/audit-log-service.ts
|
|
1565
|
+
var AuditLogService = class {
|
|
1566
|
+
constructor(httpClient) {
|
|
1567
|
+
this.httpClient = httpClient;
|
|
1568
|
+
}
|
|
1569
|
+
/**
|
|
1570
|
+
* Lists audit logs with optional filtering, pagination, and sorting.
|
|
1571
|
+
*/
|
|
1572
|
+
async list(params) {
|
|
1573
|
+
return (await this.httpClient.get("/api/v1/audit-logs", { params })).data;
|
|
1574
|
+
}
|
|
1575
|
+
/**
|
|
1576
|
+
* Retrieves a single audit log entry by ID.
|
|
1577
|
+
*/
|
|
1578
|
+
async get(logId) {
|
|
1579
|
+
return (await this.httpClient.get(`/api/v1/audit-logs/${logId}`)).data;
|
|
1580
|
+
}
|
|
1581
|
+
/**
|
|
1582
|
+
* Exports audit logs in the specified format (JSON, CSV, or PDF).
|
|
1583
|
+
*
|
|
1584
|
+
* @param params Optional filters (account_id, table_name, operation, date range, etc.)
|
|
1585
|
+
* @param format Export format: 'json', 'csv', or 'pdf' (default: 'json')
|
|
1586
|
+
* @returns Exported data as string (base64-encoded for PDF format)
|
|
1587
|
+
* @throws {AuthorizationError} If user is not a superadmin
|
|
1588
|
+
*
|
|
1589
|
+
* @example
|
|
1590
|
+
* // Export as JSON
|
|
1591
|
+
* const jsonData = await client.auditLogs.export({ table_name: 'users' }, 'json');
|
|
1592
|
+
*
|
|
1593
|
+
* @example
|
|
1594
|
+
* // Export as CSV
|
|
1595
|
+
* const csvData = await client.auditLogs.export({ table_name: 'users' }, 'csv');
|
|
1596
|
+
*
|
|
1597
|
+
* @example
|
|
1598
|
+
* // Export as PDF
|
|
1599
|
+
* const pdfBase64 = await client.auditLogs.export({ table_name: 'users' }, 'pdf');
|
|
1600
|
+
* // pdfBase64 is a base64-encoded PDF string
|
|
1601
|
+
*/
|
|
1602
|
+
async export(params, format = "json") {
|
|
1603
|
+
return (await this.httpClient.get("/api/v1/audit-logs/export", { params: {
|
|
1604
|
+
...params,
|
|
1605
|
+
format
|
|
1606
|
+
} })).data;
|
|
1607
|
+
}
|
|
1608
|
+
};
|
|
1609
|
+
|
|
1610
|
+
//#endregion
|
|
1611
|
+
//#region src/core/role-service.ts
|
|
1612
|
+
/**
|
|
1613
|
+
* Service for managing roles.
|
|
1614
|
+
* Requires superadmin authentication.
|
|
1615
|
+
*/
|
|
1616
|
+
var RoleService = class {
|
|
1617
|
+
constructor(http) {
|
|
1618
|
+
this.http = http;
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* List all roles with pagination.
|
|
1622
|
+
*/
|
|
1623
|
+
async list() {
|
|
1624
|
+
return (await this.http.get("/api/v1/roles")).data;
|
|
1625
|
+
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Get details for a specific role.
|
|
1628
|
+
*/
|
|
1629
|
+
async get(roleId) {
|
|
1630
|
+
return (await this.http.get(`/api/v1/roles/${roleId}`)).data;
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Create a new role.
|
|
1634
|
+
*/
|
|
1635
|
+
async create(data) {
|
|
1636
|
+
return (await this.http.post("/api/v1/roles", data)).data;
|
|
1637
|
+
}
|
|
1638
|
+
/**
|
|
1639
|
+
* Update an existing role.
|
|
1640
|
+
*/
|
|
1641
|
+
async update(roleId, data) {
|
|
1642
|
+
return (await this.http.patch(`/api/v1/roles/${roleId}`, data)).data;
|
|
1643
|
+
}
|
|
1644
|
+
/**
|
|
1645
|
+
* Delete a role.
|
|
1646
|
+
* Fails if the role is currently in use.
|
|
1647
|
+
*/
|
|
1648
|
+
async delete(roleId) {
|
|
1649
|
+
await this.http.delete(`/api/v1/roles/${roleId}`);
|
|
1650
|
+
return { success: true };
|
|
1651
|
+
}
|
|
1652
|
+
};
|
|
1653
|
+
|
|
1654
|
+
//#endregion
|
|
1655
|
+
//#region src/core/collection-rule-service.ts
|
|
1656
|
+
/**
|
|
1657
|
+
* Service for managing collection-level access rules and field permissions.
|
|
1658
|
+
* Requires superadmin authentication.
|
|
1659
|
+
*/
|
|
1660
|
+
var CollectionRuleService = class {
|
|
1661
|
+
constructor(http) {
|
|
1662
|
+
this.http = http;
|
|
1663
|
+
}
|
|
1664
|
+
/**
|
|
1665
|
+
* Get access rules and field permissions for a specific collection.
|
|
1666
|
+
*/
|
|
1667
|
+
async get(collectionName) {
|
|
1668
|
+
return (await this.http.get(`/api/v1/collections/${collectionName}/rules`)).data;
|
|
1669
|
+
}
|
|
1670
|
+
/**
|
|
1671
|
+
* Update access rules and field permissions for a specific collection.
|
|
1672
|
+
*/
|
|
1673
|
+
async update(collectionName, data) {
|
|
1674
|
+
return (await this.http.put(`/api/v1/collections/${collectionName}/rules`, data)).data;
|
|
1675
|
+
}
|
|
1676
|
+
/**
|
|
1677
|
+
* Validate a rule expression against a collection schema.
|
|
1678
|
+
*/
|
|
1679
|
+
async validateRule(rule, operation, collectionFields) {
|
|
1680
|
+
return (await this.http.post("/api/v1/rules/validate", {
|
|
1681
|
+
rule,
|
|
1682
|
+
operation,
|
|
1683
|
+
collectionFields
|
|
1684
|
+
})).data;
|
|
1685
|
+
}
|
|
1686
|
+
/**
|
|
1687
|
+
* Test a rule evaluation with a sample context.
|
|
1688
|
+
*/
|
|
1689
|
+
async testRule(rule, context) {
|
|
1690
|
+
return (await this.http.post("/api/v1/rules/test", {
|
|
1691
|
+
rule,
|
|
1692
|
+
context
|
|
1693
|
+
})).data;
|
|
1694
|
+
}
|
|
1695
|
+
};
|
|
1696
|
+
|
|
1697
|
+
//#endregion
|
|
1698
|
+
//#region src/core/macro-service.ts
|
|
1699
|
+
/**
|
|
1700
|
+
* Service for managing SQL macros.
|
|
1701
|
+
* Macros can be used in permission rules.
|
|
1702
|
+
* Requires superadmin authentication for most operations.
|
|
1703
|
+
*/
|
|
1704
|
+
var MacroService = class {
|
|
1705
|
+
constructor(http) {
|
|
1706
|
+
this.http = http;
|
|
1707
|
+
}
|
|
1708
|
+
/**
|
|
1709
|
+
* List all macros, including built-in ones.
|
|
1710
|
+
*/
|
|
1711
|
+
async list() {
|
|
1712
|
+
return (await this.http.get("/api/v1/macros")).data;
|
|
1713
|
+
}
|
|
1714
|
+
/**
|
|
1715
|
+
* Get details for a specific macro.
|
|
1716
|
+
*/
|
|
1717
|
+
async get(macroId) {
|
|
1718
|
+
return (await this.http.get(`/api/v1/macros/${macroId}`)).data;
|
|
1719
|
+
}
|
|
1720
|
+
/**
|
|
1721
|
+
* Create a new custom macro.
|
|
1722
|
+
*/
|
|
1723
|
+
async create(data) {
|
|
1724
|
+
return (await this.http.post("/api/v1/macros", data)).data;
|
|
1725
|
+
}
|
|
1726
|
+
/**
|
|
1727
|
+
* Update an existing custom macro.
|
|
1728
|
+
* Built-in macros cannot be updated.
|
|
1729
|
+
*/
|
|
1730
|
+
async update(macroId, data) {
|
|
1731
|
+
return (await this.http.patch(`/api/v1/macros/${macroId}`, data)).data;
|
|
1732
|
+
}
|
|
1733
|
+
/**
|
|
1734
|
+
* Delete a macro.
|
|
1735
|
+
* Fails if the macro is built-in or currently in use.
|
|
1736
|
+
*/
|
|
1737
|
+
async delete(macroId) {
|
|
1738
|
+
await this.http.delete(`/api/v1/macros/${macroId}`);
|
|
1739
|
+
return { success: true };
|
|
1740
|
+
}
|
|
1741
|
+
/**
|
|
1742
|
+
* Test a macro with parameters.
|
|
1743
|
+
*/
|
|
1744
|
+
async test(macroId, params) {
|
|
1745
|
+
return (await this.http.post(`/api/v1/macros/${macroId}/test`, { params })).data;
|
|
1746
|
+
}
|
|
1747
|
+
};
|
|
1748
|
+
|
|
1749
|
+
//#endregion
|
|
1750
|
+
//#region src/core/dashboard-service.ts
|
|
1751
|
+
/**
|
|
1752
|
+
* Service for interacting with the Dashboard API.
|
|
1753
|
+
* Provides statistics and metrics for system monitoring.
|
|
1754
|
+
*/
|
|
1755
|
+
var DashboardService = class {
|
|
1756
|
+
constructor(httpClient) {
|
|
1757
|
+
this.httpClient = httpClient;
|
|
1758
|
+
}
|
|
1759
|
+
/**
|
|
1760
|
+
* Retrieves dashboard statistics including counts for accounts, users,
|
|
1761
|
+
* collections, and records, as well as recent activity and health metrics.
|
|
1762
|
+
*
|
|
1763
|
+
* @returns {Promise<DashboardStats>} A promise that resolves to dashboard statistics.
|
|
1764
|
+
* @throws {AuthenticationError} If not authenticated.
|
|
1765
|
+
* @throws {AuthorizationError} If the user is not a superadmin.
|
|
1766
|
+
*/
|
|
1767
|
+
async getStats() {
|
|
1768
|
+
return (await this.httpClient.get("/api/v1/dashboard/stats")).data;
|
|
1769
|
+
}
|
|
1770
|
+
};
|
|
1771
|
+
|
|
1772
|
+
//#endregion
|
|
1773
|
+
//#region src/core/admin-service.ts
|
|
1774
|
+
/**
|
|
1775
|
+
* Service for superadmin operations and system configuration management.
|
|
1776
|
+
*/
|
|
1777
|
+
var AdminService = class {
|
|
1778
|
+
constructor(http) {
|
|
1779
|
+
this.http = http;
|
|
1780
|
+
}
|
|
1781
|
+
/**
|
|
1782
|
+
* Returns configuration statistics by category.
|
|
1783
|
+
*/
|
|
1784
|
+
async getConfigurationStats() {
|
|
1785
|
+
return (await this.http.get("/api/v1/admin/configuration/stats")).data;
|
|
1786
|
+
}
|
|
1787
|
+
/**
|
|
1788
|
+
* Returns recently modified configurations.
|
|
1789
|
+
* @param limit Number of configurations to return
|
|
1790
|
+
*/
|
|
1791
|
+
async getRecentConfigurations(limit = 10) {
|
|
1792
|
+
return (await this.http.get("/api/v1/admin/configuration/recent", { params: { limit } })).data;
|
|
1793
|
+
}
|
|
1794
|
+
/**
|
|
1795
|
+
* Returns all system-level configurations.
|
|
1796
|
+
* @param category Optional category filter
|
|
1797
|
+
*/
|
|
1798
|
+
async listSystemConfigurations(category) {
|
|
1799
|
+
return (await this.http.get("/api/v1/admin/configuration/system", { params: { category } })).data;
|
|
1800
|
+
}
|
|
1801
|
+
/**
|
|
1802
|
+
* Returns configurations for specific account.
|
|
1803
|
+
* @param accountId Account ID
|
|
1804
|
+
* @param category Optional category filter
|
|
1805
|
+
*/
|
|
1806
|
+
async listAccountConfigurations(accountId, category) {
|
|
1807
|
+
return (await this.http.get("/api/v1/admin/configuration/account", { params: {
|
|
1808
|
+
account_id: accountId,
|
|
1809
|
+
category
|
|
1810
|
+
} })).data;
|
|
1811
|
+
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Returns decrypted configuration values with secrets masked.
|
|
1814
|
+
* @param configId Configuration ID
|
|
1815
|
+
*/
|
|
1816
|
+
async getConfigurationValues(configId) {
|
|
1817
|
+
return (await this.http.get(`/api/v1/admin/configuration/${configId}/values`)).data;
|
|
1818
|
+
}
|
|
1819
|
+
/**
|
|
1820
|
+
* Updates configuration values.
|
|
1821
|
+
* @param configId Configuration ID
|
|
1822
|
+
* @param values New configuration values
|
|
1823
|
+
*/
|
|
1824
|
+
async updateConfigurationValues(configId, values) {
|
|
1825
|
+
return (await this.http.patch(`/api/v1/admin/configuration/${configId}/values`, values)).data;
|
|
1826
|
+
}
|
|
1827
|
+
/**
|
|
1828
|
+
* Enables or disables configuration.
|
|
1829
|
+
* @param configId Configuration ID
|
|
1830
|
+
* @param enabled Enabled status
|
|
1831
|
+
*/
|
|
1832
|
+
async updateConfigurationStatus(configId, enabled) {
|
|
1833
|
+
return (await this.http.patch(`/api/v1/admin/configuration/${configId}`, { enabled })).data;
|
|
1834
|
+
}
|
|
1835
|
+
/**
|
|
1836
|
+
* Creates new configuration record.
|
|
1837
|
+
* @param data Configuration data
|
|
1838
|
+
*/
|
|
1839
|
+
async createConfiguration(data) {
|
|
1840
|
+
return (await this.http.post("/api/v1/admin/configuration", data)).data;
|
|
1841
|
+
}
|
|
1842
|
+
/**
|
|
1843
|
+
* Deletes configuration.
|
|
1844
|
+
* @param configId Configuration ID
|
|
1845
|
+
*/
|
|
1846
|
+
async deleteConfiguration(configId) {
|
|
1847
|
+
return (await this.http.delete(`/api/v1/admin/configuration/${configId}`)).data;
|
|
1848
|
+
}
|
|
1849
|
+
/**
|
|
1850
|
+
* Lists all available provider definitions.
|
|
1851
|
+
* @param category Optional category filter
|
|
1852
|
+
*/
|
|
1853
|
+
async listProviders(category) {
|
|
1854
|
+
return (await this.http.get("/api/v1/admin/configuration/providers", { params: { category } })).data;
|
|
1855
|
+
}
|
|
1856
|
+
/**
|
|
1857
|
+
* Returns JSON schema for provider configuration.
|
|
1858
|
+
* @param category Provider category
|
|
1859
|
+
* @param providerName Provider name
|
|
1860
|
+
*/
|
|
1861
|
+
async getProviderSchema(category, providerName) {
|
|
1862
|
+
return (await this.http.get(`/api/v1/admin/configuration/schema/${category}/${providerName}`)).data;
|
|
1863
|
+
}
|
|
1864
|
+
/**
|
|
1865
|
+
* Tests provider connection.
|
|
1866
|
+
* @param category Provider category
|
|
1867
|
+
* @param providerName Provider name
|
|
1868
|
+
* @param config Configuration values to test
|
|
1869
|
+
*/
|
|
1870
|
+
async testConnection(category, providerName, config) {
|
|
1871
|
+
return (await this.http.post("/api/v1/admin/configuration/test-connection", {
|
|
1872
|
+
category,
|
|
1873
|
+
provider_name: providerName,
|
|
1874
|
+
config
|
|
1875
|
+
}, { timeout: 15e3 })).data;
|
|
1876
|
+
}
|
|
1877
|
+
};
|
|
1878
|
+
|
|
1879
|
+
//#endregion
|
|
1880
|
+
//#region src/core/email-template-service.ts
|
|
1881
|
+
/**
|
|
1882
|
+
* Service for managing email templates and logs.
|
|
1883
|
+
*/
|
|
1884
|
+
var EmailTemplateService = class {
|
|
1885
|
+
constructor(http) {
|
|
1886
|
+
this.http = http;
|
|
1887
|
+
}
|
|
1888
|
+
/**
|
|
1889
|
+
* Returns a list of email templates.
|
|
1890
|
+
* @param filters Optional filters for listing templates
|
|
1891
|
+
*/
|
|
1892
|
+
async list(filters) {
|
|
1893
|
+
return (await this.http.get("/api/v1/admin/email/templates", { params: filters })).data;
|
|
1894
|
+
}
|
|
1895
|
+
/**
|
|
1896
|
+
* Returns email template details.
|
|
1897
|
+
* @param templateId Template ID
|
|
1898
|
+
*/
|
|
1899
|
+
async get(templateId) {
|
|
1900
|
+
return (await this.http.get(`/api/v1/admin/email/templates/${templateId}`)).data;
|
|
1901
|
+
}
|
|
1902
|
+
/**
|
|
1903
|
+
* Updates an email template.
|
|
1904
|
+
* @param templateId Template ID
|
|
1905
|
+
* @param data Update data
|
|
1906
|
+
*/
|
|
1907
|
+
async update(templateId, data) {
|
|
1908
|
+
return (await this.http.put(`/api/v1/admin/email/templates/${templateId}`, data)).data;
|
|
1909
|
+
}
|
|
1910
|
+
/**
|
|
1911
|
+
* Renders an email template with provided variables.
|
|
1912
|
+
* @param request Render request data
|
|
1913
|
+
*/
|
|
1914
|
+
async render(request) {
|
|
1915
|
+
return (await this.http.post("/api/v1/admin/email/templates/render", request)).data;
|
|
1916
|
+
}
|
|
1917
|
+
/**
|
|
1918
|
+
* Sends a test email using the specified template.
|
|
1919
|
+
* @param templateId Template ID
|
|
1920
|
+
* @param recipientEmail Recipient email address
|
|
1921
|
+
* @param variables Template variables
|
|
1922
|
+
* @param provider Optional provider override
|
|
1923
|
+
*/
|
|
1924
|
+
async sendTest(templateId, recipientEmail, variables = {}, provider) {
|
|
1925
|
+
return (await this.http.post(`/api/v1/admin/email/templates/${templateId}/test`, {
|
|
1926
|
+
recipient: recipientEmail,
|
|
1927
|
+
variables,
|
|
1928
|
+
provider
|
|
1929
|
+
})).data;
|
|
1930
|
+
}
|
|
1931
|
+
/**
|
|
1932
|
+
* Returns a paginated list of email logs.
|
|
1933
|
+
* @param filters Optional filters for listing logs
|
|
1934
|
+
*/
|
|
1935
|
+
async listLogs(filters) {
|
|
1936
|
+
return (await this.http.get("/api/v1/admin/email/logs", { params: filters })).data;
|
|
1937
|
+
}
|
|
1938
|
+
/**
|
|
1939
|
+
* Returns single email log details.
|
|
1940
|
+
* @param logId Log ID
|
|
1941
|
+
*/
|
|
1942
|
+
async getLog(logId) {
|
|
1943
|
+
return (await this.http.get(`/api/v1/admin/email/logs/${logId}`)).data;
|
|
1944
|
+
}
|
|
1945
|
+
};
|
|
1946
|
+
|
|
1947
|
+
//#endregion
|
|
1948
|
+
//#region src/core/file-service.ts
|
|
1949
|
+
/**
|
|
1950
|
+
* Service for working with files (upload, download, delete).
|
|
1951
|
+
*/
|
|
1952
|
+
var FileService = class {
|
|
1953
|
+
constructor(http, getBaseUrl, getToken) {
|
|
1954
|
+
this.http = http;
|
|
1955
|
+
this.getBaseUrl = getBaseUrl;
|
|
1956
|
+
this.getToken = getToken;
|
|
1957
|
+
}
|
|
1958
|
+
/**
|
|
1959
|
+
* Upload a file to the server.
|
|
1960
|
+
* @param file The file or blob to upload
|
|
1961
|
+
* @param options Optional upload options (filename, contentType)
|
|
1962
|
+
*/
|
|
1963
|
+
async upload(file, options) {
|
|
1964
|
+
const formData = new FormData();
|
|
1965
|
+
const filename = options?.filename || file.name || "file";
|
|
1966
|
+
formData.append("file", file, filename);
|
|
1967
|
+
if (options?.contentType) {}
|
|
1968
|
+
return (await this.http.post("/api/v1/files/upload", formData, { headers: { "Content-Type": void 0 } })).data;
|
|
1969
|
+
}
|
|
1970
|
+
/**
|
|
1971
|
+
* Get the download URL for a file.
|
|
1972
|
+
* @param path The server path to the file
|
|
1973
|
+
*/
|
|
1974
|
+
getDownloadUrl(path) {
|
|
1975
|
+
const baseUrl = this.getBaseUrl().replace(/\/$/, "");
|
|
1976
|
+
const cleanPath = path.startsWith("/") ? path : `/${path}`;
|
|
1977
|
+
const url = new URL(`${baseUrl}/api/v1/files/download${cleanPath}`);
|
|
1978
|
+
const token = this.getToken();
|
|
1979
|
+
if (token) url.searchParams.set("token", token);
|
|
1980
|
+
return url.toString();
|
|
1981
|
+
}
|
|
1982
|
+
/**
|
|
1983
|
+
* Delete a file from the server.
|
|
1984
|
+
* @param path The server path to the file
|
|
1985
|
+
*/
|
|
1986
|
+
async delete(path) {
|
|
1987
|
+
const cleanPath = path.startsWith("/") ? path : `/${path}`;
|
|
1988
|
+
await this.http.delete(`/api/v1/files${cleanPath}`);
|
|
1989
|
+
return { success: true };
|
|
1990
|
+
}
|
|
1991
|
+
};
|
|
1992
|
+
|
|
1993
|
+
//#endregion
|
|
1994
|
+
//#region src/core/realtime-service.ts
|
|
1995
|
+
var RealTimeService = class {
|
|
1996
|
+
state = "disconnected";
|
|
1997
|
+
socket = null;
|
|
1998
|
+
sse = null;
|
|
1999
|
+
listeners = {};
|
|
2000
|
+
retryCount = 0;
|
|
2001
|
+
reconnectTimer = null;
|
|
2002
|
+
heartbeatTimer = null;
|
|
2003
|
+
subscriptions = /* @__PURE__ */ new Set();
|
|
2004
|
+
subscriptionRequests = /* @__PURE__ */ new Map();
|
|
2005
|
+
pendingSubscriptions = /* @__PURE__ */ new Map();
|
|
2006
|
+
authUnsubscribe;
|
|
2007
|
+
isReconnectingForAuth = false;
|
|
2008
|
+
constructor(options) {
|
|
2009
|
+
this.options = options;
|
|
2010
|
+
if (this.options.authManager) this.authUnsubscribe = this.options.authManager.on("auth:login", () => {
|
|
2011
|
+
this.handleTokenRefresh();
|
|
2012
|
+
});
|
|
2013
|
+
}
|
|
2014
|
+
async connect() {
|
|
2015
|
+
if (this.state === "connected" || this.state === "connecting") return;
|
|
2016
|
+
this.setState("connecting");
|
|
2017
|
+
this.retryCount = 0;
|
|
2018
|
+
if (this.options.logger) this.options.logger.info("RealTimeService: Connecting...");
|
|
2019
|
+
return this.doConnect();
|
|
2020
|
+
}
|
|
2021
|
+
disconnect() {
|
|
2022
|
+
if (this.options.logger) this.options.logger.info("RealTimeService: Disconnecting...");
|
|
2023
|
+
this.clearTimers();
|
|
2024
|
+
this.closeConnections();
|
|
2025
|
+
this.setState("disconnected");
|
|
2026
|
+
this.subscriptions.clear();
|
|
2027
|
+
if (this.authUnsubscribe) {
|
|
2028
|
+
this.authUnsubscribe();
|
|
2029
|
+
this.authUnsubscribe = void 0;
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
getState() {
|
|
2033
|
+
return this.state;
|
|
2034
|
+
}
|
|
2035
|
+
on(event, handler) {
|
|
2036
|
+
const eventName = event;
|
|
2037
|
+
if (!this.listeners[eventName]) this.listeners[eventName] = /* @__PURE__ */ new Set();
|
|
2038
|
+
this.listeners[eventName].add(handler);
|
|
2039
|
+
return () => this.off(event, handler);
|
|
2040
|
+
}
|
|
2041
|
+
off(event, handler) {
|
|
2042
|
+
this.listeners[event]?.delete(handler);
|
|
2043
|
+
}
|
|
2044
|
+
async subscribe(collection, operations = [
|
|
2045
|
+
"create",
|
|
2046
|
+
"update",
|
|
2047
|
+
"delete"
|
|
2048
|
+
]) {
|
|
2049
|
+
if (this.subscriptions.size >= 100) throw new Error("Maximum 100 subscriptions per connection");
|
|
2050
|
+
this.subscriptions.add(collection);
|
|
2051
|
+
this.subscriptionRequests.set(collection, operations);
|
|
2052
|
+
if (this.options.logger) this.options.logger.debug(`RealTimeService: Subscribing to ${collection}`, { operations });
|
|
2053
|
+
if (this.state === "connected" && this.socket) return new Promise((resolve, reject) => {
|
|
2054
|
+
this.pendingSubscriptions.set(`subscribe:${collection}`, {
|
|
2055
|
+
resolve,
|
|
2056
|
+
reject
|
|
2057
|
+
});
|
|
2058
|
+
this.send({
|
|
2059
|
+
action: "subscribe",
|
|
2060
|
+
collection,
|
|
2061
|
+
operations
|
|
2062
|
+
});
|
|
2063
|
+
setTimeout(() => {
|
|
2064
|
+
if (this.pendingSubscriptions.has(`subscribe:${collection}`)) {
|
|
2065
|
+
this.pendingSubscriptions.delete(`subscribe:${collection}`);
|
|
2066
|
+
const error = /* @__PURE__ */ new Error(`Subscription to ${collection} timed out`);
|
|
2067
|
+
if (this.options.logger) this.options.logger.warn(`RealTimeService: ${error.message}`);
|
|
2068
|
+
reject(error);
|
|
2069
|
+
}
|
|
2070
|
+
}, 5e3);
|
|
2071
|
+
});
|
|
2072
|
+
return Promise.resolve();
|
|
2073
|
+
}
|
|
2074
|
+
async unsubscribe(collection) {
|
|
2075
|
+
this.subscriptions.delete(collection);
|
|
2076
|
+
this.subscriptionRequests.delete(collection);
|
|
2077
|
+
if (this.options.logger) this.options.logger.debug(`RealTimeService: Unsubscribing from ${collection}`);
|
|
2078
|
+
if (this.state === "connected" && this.socket) return new Promise((resolve, reject) => {
|
|
2079
|
+
this.pendingSubscriptions.set(`unsubscribe:${collection}`, {
|
|
2080
|
+
resolve,
|
|
2081
|
+
reject
|
|
2082
|
+
});
|
|
2083
|
+
this.send({
|
|
2084
|
+
action: "unsubscribe",
|
|
2085
|
+
collection
|
|
2086
|
+
});
|
|
2087
|
+
setTimeout(() => {
|
|
2088
|
+
if (this.pendingSubscriptions.has(`unsubscribe:${collection}`)) {
|
|
2089
|
+
this.pendingSubscriptions.delete(`unsubscribe:${collection}`);
|
|
2090
|
+
const error = /* @__PURE__ */ new Error(`Unsubscription from ${collection} timed out`);
|
|
2091
|
+
if (this.options.logger) this.options.logger.warn(`RealTimeService: ${error.message}`);
|
|
2092
|
+
reject(error);
|
|
2093
|
+
}
|
|
2094
|
+
}, 5e3);
|
|
2095
|
+
});
|
|
2096
|
+
return Promise.resolve();
|
|
2097
|
+
}
|
|
2098
|
+
getSubscriptions() {
|
|
2099
|
+
return Array.from(this.subscriptions);
|
|
2100
|
+
}
|
|
2101
|
+
async doConnect() {
|
|
2102
|
+
if (this.state === "error" && !this.isReconnectingForAuth) return;
|
|
2103
|
+
const token = this.options.getToken();
|
|
2104
|
+
if (!token) {
|
|
2105
|
+
this.setState("error");
|
|
2106
|
+
const error = /* @__PURE__ */ new Error("Authentication token is required for real-time connection");
|
|
2107
|
+
this.emit("error", error);
|
|
2108
|
+
this.emit("auth_error", error);
|
|
2109
|
+
if (this.options.logger) this.options.logger.error(`RealTimeService: ${error.message}`);
|
|
2110
|
+
return;
|
|
2111
|
+
}
|
|
2112
|
+
if (this.options.authManager) {
|
|
2113
|
+
const authState = this.options.authManager.getState();
|
|
2114
|
+
if (authState.expiresAt) {
|
|
2115
|
+
if (new Date(authState.expiresAt).getTime() <= Date.now()) {
|
|
2116
|
+
this.setState("error");
|
|
2117
|
+
const error = /* @__PURE__ */ new Error("Authentication token has expired");
|
|
2118
|
+
this.emit("error", error);
|
|
2119
|
+
this.emit("auth_error", error);
|
|
2120
|
+
if (this.options.logger) this.options.logger.error(`RealTimeService: ${error.message}`);
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
let webSocketAttempted = false;
|
|
2126
|
+
try {
|
|
2127
|
+
if (typeof WebSocket !== "undefined") {
|
|
2128
|
+
webSocketAttempted = true;
|
|
2129
|
+
await this.connectWebSocket(token);
|
|
2130
|
+
} else await this.connectSSE(token);
|
|
2131
|
+
} catch (err) {
|
|
2132
|
+
if (webSocketAttempted && !this.sse) try {
|
|
2133
|
+
if (this.options.logger) this.options.logger.info(`RealTimeService: WebSocket connection failed, falling back to SSE`);
|
|
2134
|
+
await this.connectSSE(token);
|
|
2135
|
+
} catch (sseErr) {
|
|
2136
|
+
this.handleError(sseErr);
|
|
2137
|
+
}
|
|
2138
|
+
else this.handleError(err);
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
connectWebSocket(token) {
|
|
2142
|
+
return new Promise((resolve, reject) => {
|
|
2143
|
+
const url = new URL(this.options.baseUrl);
|
|
2144
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
2145
|
+
url.pathname = "/api/v1/realtime/ws";
|
|
2146
|
+
url.searchParams.set("token", token);
|
|
2147
|
+
this.socket = new WebSocket(url.toString());
|
|
2148
|
+
let connectionResolved = false;
|
|
2149
|
+
this.socket.onopen = () => {
|
|
2150
|
+
this.setState("connected");
|
|
2151
|
+
this.retryCount = 0;
|
|
2152
|
+
this.startHeartbeat();
|
|
2153
|
+
this.resubscribe();
|
|
2154
|
+
connectionResolved = true;
|
|
2155
|
+
if (this.options.logger) this.options.logger.info(`RealTimeService: WebSocket connected`);
|
|
2156
|
+
resolve();
|
|
2157
|
+
};
|
|
2158
|
+
this.socket.onmessage = (event) => {
|
|
2159
|
+
try {
|
|
2160
|
+
const message = JSON.parse(event.data);
|
|
2161
|
+
this.handleMessage(message);
|
|
2162
|
+
} catch (e) {}
|
|
2163
|
+
};
|
|
2164
|
+
this.socket.onclose = () => {
|
|
2165
|
+
this.socket = null;
|
|
2166
|
+
if (!connectionResolved) reject(/* @__PURE__ */ new Error("WebSocket closed during connection"));
|
|
2167
|
+
else if (this.state !== "disconnected") {
|
|
2168
|
+
if (this.options.logger) this.options.logger.info(`RealTimeService: WebSocket closed, reconnecting...`);
|
|
2169
|
+
this.handleReconnect();
|
|
2170
|
+
}
|
|
2171
|
+
};
|
|
2172
|
+
this.socket.onerror = (e) => {
|
|
2173
|
+
if (!connectionResolved) reject(e);
|
|
2174
|
+
else this.emit("error", /* @__PURE__ */ new Error("WebSocket error"));
|
|
2175
|
+
};
|
|
2176
|
+
});
|
|
2177
|
+
}
|
|
2178
|
+
connectSSE(token) {
|
|
2179
|
+
return new Promise((resolve, reject) => {
|
|
2180
|
+
const url = new URL(this.options.baseUrl);
|
|
2181
|
+
url.pathname = "/api/v1/realtime/subscribe";
|
|
2182
|
+
url.searchParams.set("token", token);
|
|
2183
|
+
this.subscriptionRequests.forEach((operations, collection) => {
|
|
2184
|
+
const value = operations.length > 0 ? `${collection}:${operations.join(",")}` : collection;
|
|
2185
|
+
url.searchParams.append("collections", value);
|
|
2186
|
+
});
|
|
2187
|
+
this.sse = new EventSource(url.toString());
|
|
2188
|
+
this.sse.onopen = () => {
|
|
2189
|
+
this.setState("connected");
|
|
2190
|
+
this.retryCount = 0;
|
|
2191
|
+
if (this.options.logger) this.options.logger.info(`RealTimeService: SSE connected`);
|
|
2192
|
+
resolve();
|
|
2193
|
+
};
|
|
2194
|
+
this.sse.onmessage = (event) => {
|
|
2195
|
+
try {
|
|
2196
|
+
const message = JSON.parse(event.data);
|
|
2197
|
+
this.handleMessage(message);
|
|
2198
|
+
} catch (e) {}
|
|
2199
|
+
};
|
|
2200
|
+
this.sse.onerror = (e) => {
|
|
2201
|
+
if (this.state === "connecting") reject(e);
|
|
2202
|
+
else {
|
|
2203
|
+
if (this.options.logger) this.options.logger.info(`RealTimeService: SSE connection error, reconnecting...`);
|
|
2204
|
+
this.handleReconnect();
|
|
2205
|
+
}
|
|
2206
|
+
};
|
|
2207
|
+
});
|
|
2208
|
+
}
|
|
2209
|
+
handleMessage(message) {
|
|
2210
|
+
if (this.options.logger && message.type !== "heartbeat" && message.type !== "pong") this.options.logger.debug(`RealTimeService: Received message`, message);
|
|
2211
|
+
if (message.type === "heartbeat" || message.type === "pong") return;
|
|
2212
|
+
if (message.type === "subscribed" && message.collection) {
|
|
2213
|
+
const key = `subscribe:${message.collection}`;
|
|
2214
|
+
const pending = this.pendingSubscriptions.get(key);
|
|
2215
|
+
if (pending) {
|
|
2216
|
+
pending.resolve();
|
|
2217
|
+
this.pendingSubscriptions.delete(key);
|
|
2218
|
+
}
|
|
2219
|
+
return;
|
|
2220
|
+
}
|
|
2221
|
+
if (message.type === "unsubscribed" && message.collection) {
|
|
2222
|
+
const key = `unsubscribe:${message.collection}`;
|
|
2223
|
+
const pending = this.pendingSubscriptions.get(key);
|
|
2224
|
+
if (pending) {
|
|
2225
|
+
pending.resolve();
|
|
2226
|
+
this.pendingSubscriptions.delete(key);
|
|
2227
|
+
}
|
|
2228
|
+
return;
|
|
2229
|
+
}
|
|
2230
|
+
this.emit("message", message);
|
|
2231
|
+
if (message.type) {
|
|
2232
|
+
this.emit(message.type, message.data);
|
|
2233
|
+
const parts = message.type.split(".");
|
|
2234
|
+
if (parts.length === 2) {
|
|
2235
|
+
const [collection, operation] = parts;
|
|
2236
|
+
this.emit(`${collection}.*`, message.data);
|
|
2237
|
+
}
|
|
2238
|
+
this.emit("*", message.data);
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
handleReconnect() {
|
|
2242
|
+
if (this.state === "disconnected") return;
|
|
2243
|
+
if (!this.isReconnectingForAuth) {
|
|
2244
|
+
if (!this.options.getToken()) {
|
|
2245
|
+
this.setState("error");
|
|
2246
|
+
const error = /* @__PURE__ */ new Error("Cannot reconnect: no authentication token available");
|
|
2247
|
+
this.emit("error", error);
|
|
2248
|
+
this.emit("auth_error", error);
|
|
2249
|
+
if (this.options.logger) this.options.logger.error(`RealTimeService: ${error.message}`);
|
|
2250
|
+
return;
|
|
2251
|
+
}
|
|
2252
|
+
if (this.options.authManager) {
|
|
2253
|
+
const authState = this.options.authManager.getState();
|
|
2254
|
+
if (authState.expiresAt) {
|
|
2255
|
+
if (new Date(authState.expiresAt).getTime() <= Date.now()) {
|
|
2256
|
+
this.setState("error");
|
|
2257
|
+
const error = /* @__PURE__ */ new Error("Cannot reconnect: authentication token has expired");
|
|
2258
|
+
this.emit("error", error);
|
|
2259
|
+
this.emit("auth_error", error);
|
|
2260
|
+
if (this.options.logger) this.options.logger.error(`RealTimeService: ${error.message}`);
|
|
2261
|
+
return;
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
this.setState("connecting");
|
|
2267
|
+
const maxRetries = this.options.maxRetries ?? 10;
|
|
2268
|
+
if (this.retryCount >= maxRetries) {
|
|
2269
|
+
this.setState("error");
|
|
2270
|
+
const error = /* @__PURE__ */ new Error("Maximum reconnection attempts reached");
|
|
2271
|
+
this.emit("error", error);
|
|
2272
|
+
if (this.options.logger) this.options.logger.error(`RealTimeService: ${error.message}`);
|
|
2273
|
+
return;
|
|
2274
|
+
}
|
|
2275
|
+
const delay = Math.min(3e4, (this.options.reconnectionDelay ?? 1e3) * Math.pow(2, this.retryCount));
|
|
2276
|
+
this.retryCount++;
|
|
2277
|
+
if (this.options.logger) this.options.logger.info(`RealTimeService: Reconnecting in ${delay}ms (attempt ${this.retryCount}/${maxRetries})`);
|
|
2278
|
+
this.reconnectTimer = setTimeout(() => {
|
|
2279
|
+
this.doConnect();
|
|
2280
|
+
}, delay);
|
|
2281
|
+
}
|
|
2282
|
+
handleError(error) {
|
|
2283
|
+
this.emit("error", error);
|
|
2284
|
+
if (this.options.logger) this.options.logger.error(`RealTimeService: Error: ${error.message}`, error);
|
|
2285
|
+
this.handleReconnect();
|
|
2286
|
+
}
|
|
2287
|
+
setState(state) {
|
|
2288
|
+
if (this.state !== state && this.options.logger) this.options.logger.debug(`RealTimeService: State changed from ${this.state} to ${state}`);
|
|
2289
|
+
this.state = state;
|
|
2290
|
+
this.emit(state);
|
|
2291
|
+
}
|
|
2292
|
+
emit(event, ...args) {
|
|
2293
|
+
this.listeners[event]?.forEach((handler) => handler(...args));
|
|
2294
|
+
}
|
|
2295
|
+
send(message) {
|
|
2296
|
+
if (this.options.logger) this.options.logger.debug(`RealTimeService: Sending message`, message);
|
|
2297
|
+
if (this.socket && this.socket.readyState === 1) this.socket.send(JSON.stringify(message));
|
|
2298
|
+
}
|
|
2299
|
+
startHeartbeat() {
|
|
2300
|
+
this.heartbeatTimer = setInterval(() => {
|
|
2301
|
+
this.send({ action: "ping" });
|
|
2302
|
+
}, 3e4);
|
|
2303
|
+
}
|
|
2304
|
+
resubscribe() {
|
|
2305
|
+
this.subscriptionRequests.forEach((operations, collection) => {
|
|
2306
|
+
this.send({
|
|
2307
|
+
action: "subscribe",
|
|
2308
|
+
collection,
|
|
2309
|
+
operations
|
|
2310
|
+
});
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
clearTimers() {
|
|
2314
|
+
if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
|
|
2315
|
+
if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
|
|
2316
|
+
this.reconnectTimer = null;
|
|
2317
|
+
this.heartbeatTimer = null;
|
|
2318
|
+
}
|
|
2319
|
+
closeConnections() {
|
|
2320
|
+
if (this.socket) {
|
|
2321
|
+
this.socket.close();
|
|
2322
|
+
this.socket = null;
|
|
2323
|
+
}
|
|
2324
|
+
if (this.sse) {
|
|
2325
|
+
this.sse.close();
|
|
2326
|
+
this.sse = null;
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
/**
|
|
2330
|
+
* Handle token refresh by reconnecting with new token
|
|
2331
|
+
*/
|
|
2332
|
+
handleTokenRefresh() {
|
|
2333
|
+
if (this.state === "connected") {
|
|
2334
|
+
if (this.options.logger) this.options.logger.info(`RealTimeService: Token refreshed, reconnecting...`);
|
|
2335
|
+
this.isReconnectingForAuth = true;
|
|
2336
|
+
this.clearTimers();
|
|
2337
|
+
this.closeConnections();
|
|
2338
|
+
this.setState("connecting");
|
|
2339
|
+
this.retryCount = 0;
|
|
2340
|
+
this.doConnect().finally(() => {
|
|
2341
|
+
this.isReconnectingForAuth = false;
|
|
2342
|
+
});
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
};
|
|
2346
|
+
|
|
2347
|
+
//#endregion
|
|
2348
|
+
//#region src/core/migration-service.ts
|
|
2349
|
+
/**
|
|
2350
|
+
* Service for viewing migration status and history.
|
|
2351
|
+
* Requires superadmin authentication.
|
|
2352
|
+
*/
|
|
2353
|
+
var MigrationService = class {
|
|
2354
|
+
constructor(http) {
|
|
2355
|
+
this.http = http;
|
|
2356
|
+
}
|
|
2357
|
+
/**
|
|
2358
|
+
* List all Alembic migration revisions.
|
|
2359
|
+
* Returns all migrations with their application status.
|
|
2360
|
+
*/
|
|
2361
|
+
async list() {
|
|
2362
|
+
return (await this.http.get("/api/v1/migrations")).data;
|
|
2363
|
+
}
|
|
2364
|
+
/**
|
|
2365
|
+
* Get the current database revision.
|
|
2366
|
+
* Returns the currently applied migration.
|
|
2367
|
+
*/
|
|
2368
|
+
async getCurrent() {
|
|
2369
|
+
try {
|
|
2370
|
+
return (await this.http.get("/api/v1/migrations/current")).data;
|
|
2371
|
+
} catch (error) {
|
|
2372
|
+
if (error?.status === 404) return null;
|
|
2373
|
+
throw error;
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
/**
|
|
2377
|
+
* Get full migration history.
|
|
2378
|
+
* Returns all applied migrations in chronological order.
|
|
2379
|
+
*/
|
|
2380
|
+
async getHistory() {
|
|
2381
|
+
return (await this.http.get("/api/v1/migrations/history")).data;
|
|
2382
|
+
}
|
|
2383
|
+
};
|
|
2384
|
+
|
|
2385
|
+
//#endregion
|
|
2386
|
+
//#region src/core/storage.ts
|
|
2387
|
+
var MemoryStorage = class {
|
|
2388
|
+
storage = /* @__PURE__ */ new Map();
|
|
2389
|
+
getItem(key) {
|
|
2390
|
+
return this.storage.get(key) || null;
|
|
2391
|
+
}
|
|
2392
|
+
setItem(key, value) {
|
|
2393
|
+
this.storage.set(key, value);
|
|
2394
|
+
}
|
|
2395
|
+
removeItem(key) {
|
|
2396
|
+
this.storage.delete(key);
|
|
2397
|
+
}
|
|
2398
|
+
};
|
|
2399
|
+
var LocalStorageBackend = class {
|
|
2400
|
+
getItem(key) {
|
|
2401
|
+
if (typeof localStorage === "undefined") return null;
|
|
2402
|
+
return localStorage.getItem(key);
|
|
2403
|
+
}
|
|
2404
|
+
setItem(key, value) {
|
|
2405
|
+
if (typeof localStorage === "undefined") return;
|
|
2406
|
+
localStorage.setItem(key, value);
|
|
2407
|
+
}
|
|
2408
|
+
removeItem(key) {
|
|
2409
|
+
if (typeof localStorage === "undefined") return;
|
|
2410
|
+
localStorage.removeItem(key);
|
|
2411
|
+
}
|
|
2412
|
+
};
|
|
2413
|
+
var SessionStorageBackend = class {
|
|
2414
|
+
getItem(key) {
|
|
2415
|
+
if (typeof sessionStorage === "undefined") return null;
|
|
2416
|
+
return sessionStorage.getItem(key);
|
|
2417
|
+
}
|
|
2418
|
+
setItem(key, value) {
|
|
2419
|
+
if (typeof sessionStorage === "undefined") return;
|
|
2420
|
+
sessionStorage.setItem(key, value);
|
|
2421
|
+
}
|
|
2422
|
+
removeItem(key) {
|
|
2423
|
+
if (typeof sessionStorage === "undefined") return;
|
|
2424
|
+
sessionStorage.removeItem(key);
|
|
2425
|
+
}
|
|
2426
|
+
};
|
|
2427
|
+
function createStorageBackend(type) {
|
|
2428
|
+
switch (type) {
|
|
2429
|
+
case "localStorage": return new LocalStorageBackend();
|
|
2430
|
+
case "sessionStorage": return new SessionStorageBackend();
|
|
2431
|
+
case "memory": return new MemoryStorage();
|
|
2432
|
+
case "asyncStorage": return new MemoryStorage();
|
|
2433
|
+
default: return new LocalStorageBackend();
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
//#endregion
|
|
2438
|
+
//#region src/core/client.ts
|
|
2439
|
+
/**
|
|
2440
|
+
* Main SDK client for interacting with SnackBase API.
|
|
2441
|
+
*/
|
|
2442
|
+
var SnackBaseClient = class {
|
|
2443
|
+
config;
|
|
2444
|
+
http;
|
|
2445
|
+
logger;
|
|
2446
|
+
authManager;
|
|
2447
|
+
authService;
|
|
2448
|
+
accountService;
|
|
2449
|
+
userService;
|
|
2450
|
+
collectionService;
|
|
2451
|
+
recordService;
|
|
2452
|
+
groupsService;
|
|
2453
|
+
invitationService;
|
|
2454
|
+
apiKeyService;
|
|
2455
|
+
auditLogService;
|
|
2456
|
+
roleService;
|
|
2457
|
+
collectionRuleService;
|
|
2458
|
+
macroService;
|
|
2459
|
+
dashboardService;
|
|
2460
|
+
adminService;
|
|
2461
|
+
emailTemplateService;
|
|
2462
|
+
fileService;
|
|
2463
|
+
realtimeService;
|
|
2464
|
+
migrationService;
|
|
2465
|
+
/**
|
|
2466
|
+
* Initialize a new SnackBaseClient instance.
|
|
2467
|
+
* @param config Configuration options
|
|
2468
|
+
*/
|
|
2469
|
+
constructor(config) {
|
|
2470
|
+
this.validateConfig(config);
|
|
2471
|
+
this.config = {
|
|
2472
|
+
...DEFAULT_CONFIG,
|
|
2473
|
+
storageBackend: config.storageBackend || getAutoDetectedStorage(),
|
|
2474
|
+
...config
|
|
2475
|
+
};
|
|
2476
|
+
let logLevel = LogLevel.NONE;
|
|
2477
|
+
if (this.config.enableLogging) switch (this.config.logLevel) {
|
|
2478
|
+
case "debug":
|
|
2479
|
+
logLevel = LogLevel.DEBUG;
|
|
2480
|
+
break;
|
|
2481
|
+
case "info":
|
|
2482
|
+
logLevel = LogLevel.INFO;
|
|
2483
|
+
break;
|
|
2484
|
+
case "warn":
|
|
2485
|
+
logLevel = LogLevel.WARN;
|
|
2486
|
+
break;
|
|
2487
|
+
case "error":
|
|
2488
|
+
logLevel = LogLevel.ERROR;
|
|
2489
|
+
break;
|
|
2490
|
+
default: logLevel = LogLevel.ERROR;
|
|
2491
|
+
}
|
|
2492
|
+
this.logger = new Logger(logLevel);
|
|
2493
|
+
if (typeof window !== "undefined") window.snackbase_debug = {
|
|
2494
|
+
logger: this.logger,
|
|
2495
|
+
client: this
|
|
2496
|
+
};
|
|
2497
|
+
this.http = new HttpClient({
|
|
2498
|
+
baseUrl: this.config.baseUrl,
|
|
2499
|
+
timeout: this.config.timeout,
|
|
2500
|
+
maxRetries: this.config.maxRetries,
|
|
2501
|
+
retryDelay: this.config.retryDelay,
|
|
2502
|
+
logger: this.logger
|
|
2503
|
+
});
|
|
2504
|
+
this.authManager = new AuthManager({ storage: createStorageBackend(this.config.storageBackend) });
|
|
2505
|
+
this.authService = new AuthService(this.http, this.authManager, this.config.apiKey, this.config.defaultAccount);
|
|
2506
|
+
this.accountService = new AccountService(this.http);
|
|
2507
|
+
this.userService = new UserService(this.http);
|
|
2508
|
+
this.collectionService = new CollectionService(this.http);
|
|
2509
|
+
this.recordService = new RecordService(this.http);
|
|
2510
|
+
this.groupsService = new GroupsService(this.http);
|
|
2511
|
+
this.invitationService = new InvitationService(this.http);
|
|
2512
|
+
this.apiKeyService = new ApiKeyService(this.http);
|
|
2513
|
+
this.auditLogService = new AuditLogService(this.http);
|
|
2514
|
+
this.roleService = new RoleService(this.http);
|
|
2515
|
+
this.collectionRuleService = new CollectionRuleService(this.http);
|
|
2516
|
+
this.macroService = new MacroService(this.http);
|
|
2517
|
+
this.dashboardService = new DashboardService(this.http);
|
|
2518
|
+
this.adminService = new AdminService(this.http);
|
|
2519
|
+
this.emailTemplateService = new EmailTemplateService(this.http);
|
|
2520
|
+
this.fileService = new FileService(this.http, () => this.config.baseUrl, () => this.authManager.token);
|
|
2521
|
+
this.realtimeService = new RealTimeService({
|
|
2522
|
+
baseUrl: this.config.baseUrl,
|
|
2523
|
+
getToken: () => this.authManager.token,
|
|
2524
|
+
authManager: this.authManager,
|
|
2525
|
+
maxRetries: this.config.maxRealTimeRetries,
|
|
2526
|
+
reconnectionDelay: this.config.realTimeReconnectionDelay,
|
|
2527
|
+
logger: this.logger
|
|
2528
|
+
});
|
|
2529
|
+
this.migrationService = new MigrationService(this.http);
|
|
2530
|
+
this.setupInterceptors();
|
|
2531
|
+
this.authManager.initialize();
|
|
2532
|
+
}
|
|
2533
|
+
/**
|
|
2534
|
+
* Returns the current client configuration.
|
|
2535
|
+
*/
|
|
2536
|
+
getConfig() {
|
|
2537
|
+
return { ...this.config };
|
|
2538
|
+
}
|
|
2539
|
+
/**
|
|
2540
|
+
* Internal helper to access the HTTP client.
|
|
2541
|
+
* @internal
|
|
2542
|
+
*/
|
|
2543
|
+
get httpClient() {
|
|
2544
|
+
return this.http;
|
|
2545
|
+
}
|
|
2546
|
+
/**
|
|
2547
|
+
* Sets up the default interceptors for the HTTP client.
|
|
2548
|
+
*/
|
|
2549
|
+
setupInterceptors() {
|
|
2550
|
+
this.http.addRequestInterceptor(contentTypeInterceptor);
|
|
2551
|
+
this.http.addRequestInterceptor(createAuthInterceptor(() => this.authManager.token || void 0, this.config.apiKey));
|
|
2552
|
+
this.http.addResponseInterceptor(errorNormalizationInterceptor);
|
|
2553
|
+
this.http.addErrorInterceptor(createAuthErrorInterceptor(this.config.onAuthError));
|
|
2554
|
+
this.http.addErrorInterceptor(errorInterceptor);
|
|
2555
|
+
}
|
|
2556
|
+
/**
|
|
2557
|
+
* Returns the current authenticated user.
|
|
2558
|
+
*/
|
|
2559
|
+
get user() {
|
|
2560
|
+
return this.authManager.user;
|
|
2561
|
+
}
|
|
2562
|
+
/**
|
|
2563
|
+
* Returns the current account.
|
|
2564
|
+
*/
|
|
2565
|
+
get account() {
|
|
2566
|
+
return this.authManager.account;
|
|
2567
|
+
}
|
|
2568
|
+
/**
|
|
2569
|
+
* Returns whether the client is currently authenticated.
|
|
2570
|
+
*/
|
|
2571
|
+
get isAuthenticated() {
|
|
2572
|
+
return this.authManager.isAuthenticated;
|
|
2573
|
+
}
|
|
2574
|
+
/**
|
|
2575
|
+
* Check if current user is superadmin.
|
|
2576
|
+
*/
|
|
2577
|
+
get isSuperadmin() {
|
|
2578
|
+
return this.authManager.isSuperadmin();
|
|
2579
|
+
}
|
|
2580
|
+
/**
|
|
2581
|
+
* Check if current session uses API key authentication.
|
|
2582
|
+
*/
|
|
2583
|
+
get isApiKeySession() {
|
|
2584
|
+
return this.authManager.isApiKeySession();
|
|
2585
|
+
}
|
|
2586
|
+
/**
|
|
2587
|
+
* Check if current session uses personal token authentication.
|
|
2588
|
+
*/
|
|
2589
|
+
get isPersonalTokenSession() {
|
|
2590
|
+
return this.authManager.isPersonalTokenSession();
|
|
2591
|
+
}
|
|
2592
|
+
/**
|
|
2593
|
+
* Check if current session uses OAuth authentication.
|
|
2594
|
+
*/
|
|
2595
|
+
get isOAuthSession() {
|
|
2596
|
+
return this.authManager.isOAuthSession();
|
|
2597
|
+
}
|
|
2598
|
+
/**
|
|
2599
|
+
* Returns the current token type.
|
|
2600
|
+
*/
|
|
2601
|
+
get tokenType() {
|
|
2602
|
+
return this.authManager.tokenType;
|
|
2603
|
+
}
|
|
2604
|
+
/**
|
|
2605
|
+
* Access to authentication methods.
|
|
2606
|
+
*/
|
|
2607
|
+
get auth() {
|
|
2608
|
+
return this.authService;
|
|
2609
|
+
}
|
|
2610
|
+
/**
|
|
2611
|
+
* Access to account management methods.
|
|
2612
|
+
*/
|
|
2613
|
+
get accounts() {
|
|
2614
|
+
return this.accountService;
|
|
2615
|
+
}
|
|
2616
|
+
/**
|
|
2617
|
+
* Access to user management methods.
|
|
2618
|
+
*/
|
|
2619
|
+
get users() {
|
|
2620
|
+
return this.userService;
|
|
2621
|
+
}
|
|
2622
|
+
/**
|
|
2623
|
+
* Access to collection management methods.
|
|
2624
|
+
*/
|
|
2625
|
+
get collections() {
|
|
2626
|
+
return this.collectionService;
|
|
2627
|
+
}
|
|
2628
|
+
/**
|
|
2629
|
+
* Access to record management methods (CRUD on dynamic collections).
|
|
2630
|
+
*/
|
|
2631
|
+
get records() {
|
|
2632
|
+
return this.recordService;
|
|
2633
|
+
}
|
|
2634
|
+
/**
|
|
2635
|
+
* Access to group management methods.
|
|
2636
|
+
*/
|
|
2637
|
+
get groups() {
|
|
2638
|
+
return this.groupsService;
|
|
2639
|
+
}
|
|
2640
|
+
/**
|
|
2641
|
+
* Access to invitation management methods.
|
|
2642
|
+
*/
|
|
2643
|
+
get invitations() {
|
|
2644
|
+
return this.invitationService;
|
|
2645
|
+
}
|
|
2646
|
+
/**
|
|
2647
|
+
* Access to API key management methods.
|
|
2648
|
+
*/
|
|
2649
|
+
get apiKeys() {
|
|
2650
|
+
return this.apiKeyService;
|
|
2651
|
+
}
|
|
2652
|
+
/**
|
|
2653
|
+
* Access to audit log management methods.
|
|
2654
|
+
*/
|
|
2655
|
+
get auditLogs() {
|
|
2656
|
+
return this.auditLogService;
|
|
2657
|
+
}
|
|
2658
|
+
/**
|
|
2659
|
+
* Access to role management methods.
|
|
2660
|
+
*/
|
|
2661
|
+
get roles() {
|
|
2662
|
+
return this.roleService;
|
|
2663
|
+
}
|
|
2664
|
+
/**
|
|
2665
|
+
* Access to collection rule management methods.
|
|
2666
|
+
*/
|
|
2667
|
+
get collectionRules() {
|
|
2668
|
+
return this.collectionRuleService;
|
|
2669
|
+
}
|
|
2670
|
+
/**
|
|
2671
|
+
* Access to macro management methods.
|
|
2672
|
+
*/
|
|
2673
|
+
get macros() {
|
|
2674
|
+
return this.macroService;
|
|
2675
|
+
}
|
|
2676
|
+
/**
|
|
2677
|
+
* Access to dashboard statistics and monitoring.
|
|
2678
|
+
*/
|
|
2679
|
+
get dashboard() {
|
|
2680
|
+
return this.dashboardService;
|
|
2681
|
+
}
|
|
2682
|
+
/**
|
|
2683
|
+
* Access to system administration and configuration methods.
|
|
2684
|
+
*/
|
|
2685
|
+
get admin() {
|
|
2686
|
+
return this.adminService;
|
|
2687
|
+
}
|
|
2688
|
+
/**
|
|
2689
|
+
* Access to email template management methods.
|
|
2690
|
+
*/
|
|
2691
|
+
get emailTemplates() {
|
|
2692
|
+
return this.emailTemplateService;
|
|
2693
|
+
}
|
|
2694
|
+
/**
|
|
2695
|
+
* Access to file management methods.
|
|
2696
|
+
*/
|
|
2697
|
+
get files() {
|
|
2698
|
+
return this.fileService;
|
|
2699
|
+
}
|
|
2700
|
+
/**
|
|
2701
|
+
* Access to real-time features and subscriptions.
|
|
2702
|
+
*/
|
|
2703
|
+
get realtime() {
|
|
2704
|
+
return this.realtimeService;
|
|
2705
|
+
}
|
|
2706
|
+
/**
|
|
2707
|
+
* Access to migration status and history.
|
|
2708
|
+
*/
|
|
2709
|
+
get migrations() {
|
|
2710
|
+
return this.migrationService;
|
|
2711
|
+
}
|
|
2712
|
+
/**
|
|
2713
|
+
* Subscribe to authentication events.
|
|
2714
|
+
* @param event Event name
|
|
2715
|
+
* @param listener Callback function
|
|
2716
|
+
*/
|
|
2717
|
+
on(event, listener) {
|
|
2718
|
+
return this.authManager.on(event, listener);
|
|
2719
|
+
}
|
|
2720
|
+
/**
|
|
2721
|
+
* Authenticate a user with email and password.
|
|
2722
|
+
*/
|
|
2723
|
+
async login(credentials) {
|
|
2724
|
+
return this.authService.login(credentials);
|
|
2725
|
+
}
|
|
2726
|
+
/**
|
|
2727
|
+
* Log out the current user.
|
|
2728
|
+
*/
|
|
2729
|
+
async logout() {
|
|
2730
|
+
return this.authService.logout();
|
|
2731
|
+
}
|
|
2732
|
+
/**
|
|
2733
|
+
* Register a new user and account.
|
|
2734
|
+
*/
|
|
2735
|
+
async register(data) {
|
|
2736
|
+
return this.authService.register(data);
|
|
2737
|
+
}
|
|
2738
|
+
/**
|
|
2739
|
+
* Refresh the access token using the refresh token.
|
|
2740
|
+
*/
|
|
2741
|
+
async refreshToken() {
|
|
2742
|
+
return this.authService.refreshToken();
|
|
2743
|
+
}
|
|
2744
|
+
/**
|
|
2745
|
+
* Get the current authenticated user profile.
|
|
2746
|
+
*/
|
|
2747
|
+
async getCurrentUser() {
|
|
2748
|
+
return this.authService.getCurrentUser();
|
|
2749
|
+
}
|
|
2750
|
+
/**
|
|
2751
|
+
* Initiate password reset flow.
|
|
2752
|
+
*/
|
|
2753
|
+
async forgotPassword(data) {
|
|
2754
|
+
return this.authService.forgotPassword(data);
|
|
2755
|
+
}
|
|
2756
|
+
/**
|
|
2757
|
+
* Reset password using a token.
|
|
2758
|
+
*/
|
|
2759
|
+
async resetPassword(data) {
|
|
2760
|
+
return this.authService.resetPassword(data);
|
|
2761
|
+
}
|
|
2762
|
+
/**
|
|
2763
|
+
* Verify email using a token.
|
|
2764
|
+
*/
|
|
2765
|
+
async verifyEmail(token) {
|
|
2766
|
+
return this.authService.verifyEmail(token);
|
|
2767
|
+
}
|
|
2768
|
+
/**
|
|
2769
|
+
* Resend the verification email to the current user.
|
|
2770
|
+
*/
|
|
2771
|
+
async resendVerificationEmail() {
|
|
2772
|
+
return this.authService.resendVerificationEmail();
|
|
2773
|
+
}
|
|
2774
|
+
/**
|
|
2775
|
+
* Generate SAML SSO authorization URL.
|
|
2776
|
+
*/
|
|
2777
|
+
async getSAMLUrl(provider, account, relayState) {
|
|
2778
|
+
return this.authService.getSAMLUrl(provider, account, relayState);
|
|
2779
|
+
}
|
|
2780
|
+
/**
|
|
2781
|
+
* Handle SAML callback.
|
|
2782
|
+
*/
|
|
2783
|
+
async handleSAMLCallback(params) {
|
|
2784
|
+
return this.authService.handleSAMLCallback(params);
|
|
2785
|
+
}
|
|
2786
|
+
/**
|
|
2787
|
+
* Get SAML metadata.
|
|
2788
|
+
*/
|
|
2789
|
+
async getSAMLMetadata(provider, account) {
|
|
2790
|
+
return this.authService.getSAMLMetadata(provider, account);
|
|
2791
|
+
}
|
|
2792
|
+
/**
|
|
2793
|
+
* Internal access to AuthManager.
|
|
2794
|
+
* @internal
|
|
2795
|
+
*/
|
|
2796
|
+
get internalAuthManager() {
|
|
2797
|
+
return this.authManager;
|
|
2798
|
+
}
|
|
2799
|
+
/**
|
|
2800
|
+
* Validates the configuration object.
|
|
2801
|
+
* Throws descriptive errors for invalid options.
|
|
2802
|
+
*/
|
|
2803
|
+
validateConfig(config) {
|
|
2804
|
+
if (!config.baseUrl) throw new Error("SnackBaseClient: baseUrl is required");
|
|
2805
|
+
try {
|
|
2806
|
+
new URL(config.baseUrl);
|
|
2807
|
+
} catch (e) {
|
|
2808
|
+
throw new Error("SnackBaseClient: baseUrl must be a valid URL");
|
|
2809
|
+
}
|
|
2810
|
+
if (config.timeout !== void 0 && (typeof config.timeout !== "number" || config.timeout < 0)) throw new Error("SnackBaseClient: timeout must be a non-negative number");
|
|
2811
|
+
if (config.maxRetries !== void 0 && (typeof config.maxRetries !== "number" || config.maxRetries < 0)) throw new Error("SnackBaseClient: maxRetries must be a non-negative number");
|
|
2812
|
+
if (config.retryDelay !== void 0 && (typeof config.retryDelay !== "number" || config.retryDelay < 0)) throw new Error("SnackBaseClient: retryDelay must be a non-negative number");
|
|
2813
|
+
if (config.refreshBeforeExpiry !== void 0 && (typeof config.refreshBeforeExpiry !== "number" || config.refreshBeforeExpiry < 0)) throw new Error("SnackBaseClient: refreshBeforeExpiry must be a non-negative number");
|
|
2814
|
+
if (config.defaultAccount !== void 0 && typeof config.defaultAccount !== "string") throw new Error("SnackBaseClient: defaultAccount must be a string");
|
|
2815
|
+
}
|
|
2816
|
+
};
|
|
2817
|
+
|
|
2818
|
+
//#endregion
|
|
2819
|
+
exports.ApiKeyRestrictedError = ApiKeyRestrictedError;
|
|
2820
|
+
exports.AuthenticationError = AuthenticationError;
|
|
2821
|
+
exports.AuthorizationError = AuthorizationError;
|
|
2822
|
+
exports.ConflictError = ConflictError;
|
|
2823
|
+
exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
|
|
2824
|
+
exports.EmailVerificationRequiredError = EmailVerificationRequiredError;
|
|
2825
|
+
exports.NetworkError = NetworkError;
|
|
2826
|
+
exports.NotFoundError = NotFoundError;
|
|
2827
|
+
exports.QueryBuilder = QueryBuilder;
|
|
2828
|
+
exports.RateLimitError = RateLimitError;
|
|
2829
|
+
exports.ServerError = ServerError;
|
|
2830
|
+
exports.SnackBaseClient = SnackBaseClient;
|
|
2831
|
+
exports.SnackBaseError = SnackBaseError;
|
|
2832
|
+
exports.TimeoutError = TimeoutError;
|
|
2833
|
+
exports.TokenType = TokenType;
|
|
2834
|
+
exports.ValidationError = ValidationError;
|
|
2835
|
+
exports.getAutoDetectedStorage = getAutoDetectedStorage;
|