@spacelr/sdk 0.1.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.d.mts +609 -0
- package/dist/index.d.ts +609 -0
- package/dist/index.js +1471 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1438 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +40 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1471 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// libs/sdk/src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
BrowserTokenStorage: () => BrowserTokenStorage,
|
|
24
|
+
CodeChallengeMethod: () => CodeChallengeMethod,
|
|
25
|
+
FileVisibility: () => FileVisibility,
|
|
26
|
+
GrantType: () => GrantType,
|
|
27
|
+
MemoryTokenStorage: () => MemoryTokenStorage,
|
|
28
|
+
SharePermission: () => SharePermission,
|
|
29
|
+
SpacelrAuthError: () => SpacelrAuthError,
|
|
30
|
+
SpacelrEmailVerificationRequiredError: () => SpacelrEmailVerificationRequiredError,
|
|
31
|
+
SpacelrError: () => SpacelrError,
|
|
32
|
+
SpacelrNetworkError: () => SpacelrNetworkError,
|
|
33
|
+
SpacelrTimeoutError: () => SpacelrTimeoutError,
|
|
34
|
+
SpacelrTwoFactorRequiredError: () => SpacelrTwoFactorRequiredError,
|
|
35
|
+
createClient: () => createClient,
|
|
36
|
+
generatePKCEChallenge: () => generatePKCEChallenge
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(index_exports);
|
|
39
|
+
|
|
40
|
+
// libs/sdk/src/core/errors.ts
|
|
41
|
+
var SpacelrError = class extends Error {
|
|
42
|
+
constructor(message, code, statusCode, details) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.name = "SpacelrError";
|
|
45
|
+
this.code = code;
|
|
46
|
+
this.statusCode = statusCode;
|
|
47
|
+
this.details = details;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
var SpacelrAuthError = class extends SpacelrError {
|
|
51
|
+
constructor(message, statusCode = 401, details) {
|
|
52
|
+
super(message, "AUTH_ERROR", statusCode, details);
|
|
53
|
+
this.name = "SpacelrAuthError";
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
var SpacelrNetworkError = class extends SpacelrError {
|
|
57
|
+
constructor(message, details) {
|
|
58
|
+
super(message, "NETWORK_ERROR", void 0, details);
|
|
59
|
+
this.name = "SpacelrNetworkError";
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
var SpacelrTimeoutError = class extends SpacelrError {
|
|
63
|
+
constructor(timeoutMs) {
|
|
64
|
+
super(
|
|
65
|
+
`Request timed out after ${timeoutMs}ms`,
|
|
66
|
+
"TIMEOUT_ERROR",
|
|
67
|
+
void 0,
|
|
68
|
+
{ timeoutMs }
|
|
69
|
+
);
|
|
70
|
+
this.name = "SpacelrTimeoutError";
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var SpacelrTwoFactorRequiredError = class extends SpacelrError {
|
|
74
|
+
constructor(twoFactorToken, details) {
|
|
75
|
+
super(
|
|
76
|
+
"Two-factor authentication required",
|
|
77
|
+
"TWO_FACTOR_REQUIRED",
|
|
78
|
+
200,
|
|
79
|
+
details
|
|
80
|
+
);
|
|
81
|
+
this.name = "SpacelrTwoFactorRequiredError";
|
|
82
|
+
this.twoFactorToken = twoFactorToken;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
var SpacelrEmailVerificationRequiredError = class extends SpacelrError {
|
|
86
|
+
constructor(emailSent, details) {
|
|
87
|
+
super(
|
|
88
|
+
emailSent ? "Email verification required. A new verification email has been sent." : "Email verification required. Please check your inbox for the verification email.",
|
|
89
|
+
"EMAIL_VERIFICATION_REQUIRED",
|
|
90
|
+
200,
|
|
91
|
+
details
|
|
92
|
+
);
|
|
93
|
+
this.name = "SpacelrEmailVerificationRequiredError";
|
|
94
|
+
this.emailSent = emailSent;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// libs/sdk/src/core/http-client.ts
|
|
99
|
+
var HttpClient = class {
|
|
100
|
+
constructor(config, tokenManager) {
|
|
101
|
+
this.config = config;
|
|
102
|
+
this.tokenManager = tokenManager;
|
|
103
|
+
}
|
|
104
|
+
async request(options) {
|
|
105
|
+
const url = this.buildUrl(options.path, options.query);
|
|
106
|
+
const headers = await this.buildHeaders(options);
|
|
107
|
+
const timeout = this.config.timeout ?? 3e4;
|
|
108
|
+
const includeCredentials = options.withCredentials ?? options.path.startsWith("/auth/");
|
|
109
|
+
const controller = new AbortController();
|
|
110
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
111
|
+
try {
|
|
112
|
+
const response = await fetch(url, {
|
|
113
|
+
method: options.method,
|
|
114
|
+
headers,
|
|
115
|
+
body: options.body ? JSON.stringify(options.body) : void 0,
|
|
116
|
+
signal: controller.signal,
|
|
117
|
+
...includeCredentials && { credentials: "include" }
|
|
118
|
+
});
|
|
119
|
+
const responseBody = await this.parseResponse(response);
|
|
120
|
+
if (!response.ok) {
|
|
121
|
+
this.throwHttpError(response.status, responseBody);
|
|
122
|
+
}
|
|
123
|
+
return this.extractData(responseBody);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
if (error instanceof SpacelrError) throw error;
|
|
126
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
127
|
+
throw new SpacelrTimeoutError(timeout);
|
|
128
|
+
}
|
|
129
|
+
throw new SpacelrNetworkError(
|
|
130
|
+
error instanceof Error ? error.message : "Network request failed"
|
|
131
|
+
);
|
|
132
|
+
} finally {
|
|
133
|
+
clearTimeout(timeoutId);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async uploadForm(path, formData, onProgress) {
|
|
137
|
+
const url = this.buildUrl(path);
|
|
138
|
+
const headers = await this.buildFormHeaders();
|
|
139
|
+
const timeoutMs = this.config.timeout ?? 3e4;
|
|
140
|
+
if (onProgress) {
|
|
141
|
+
return this.uploadFormWithProgress(url, headers, formData, onProgress, timeoutMs);
|
|
142
|
+
}
|
|
143
|
+
const controller = new AbortController();
|
|
144
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
145
|
+
try {
|
|
146
|
+
const response = await fetch(url, {
|
|
147
|
+
method: "POST",
|
|
148
|
+
headers,
|
|
149
|
+
body: formData,
|
|
150
|
+
signal: controller.signal
|
|
151
|
+
});
|
|
152
|
+
const responseBody = await this.parseResponse(response);
|
|
153
|
+
if (!response.ok) {
|
|
154
|
+
this.throwHttpError(response.status, responseBody);
|
|
155
|
+
}
|
|
156
|
+
return this.extractData(responseBody);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
if (error instanceof SpacelrError) throw error;
|
|
159
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
160
|
+
throw new SpacelrTimeoutError(timeoutMs);
|
|
161
|
+
}
|
|
162
|
+
throw new SpacelrNetworkError(
|
|
163
|
+
error instanceof Error ? error.message : "Network request failed"
|
|
164
|
+
);
|
|
165
|
+
} finally {
|
|
166
|
+
clearTimeout(timeoutId);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
uploadFormWithProgress(url, headers, formData, onProgress, timeoutMs) {
|
|
170
|
+
return new Promise((resolve, reject) => {
|
|
171
|
+
const xhr = new XMLHttpRequest();
|
|
172
|
+
xhr.open("POST", url);
|
|
173
|
+
xhr.timeout = timeoutMs;
|
|
174
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
175
|
+
xhr.setRequestHeader(key, value);
|
|
176
|
+
}
|
|
177
|
+
xhr.upload.addEventListener("progress", (e) => {
|
|
178
|
+
if (e.lengthComputable) {
|
|
179
|
+
onProgress({ loaded: e.loaded, total: e.total });
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
xhr.addEventListener("load", () => {
|
|
183
|
+
try {
|
|
184
|
+
const contentType = xhr.getResponseHeader("content-type") ?? "";
|
|
185
|
+
if (!contentType.includes("application/json")) {
|
|
186
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
187
|
+
resolve({ success: true, data: xhr.responseText });
|
|
188
|
+
} else {
|
|
189
|
+
reject(new SpacelrNetworkError(`HTTP ${xhr.status}: ${xhr.responseText.slice(0, 200)}`));
|
|
190
|
+
}
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const body = JSON.parse(xhr.responseText);
|
|
194
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
195
|
+
resolve(this.extractData(body));
|
|
196
|
+
} else {
|
|
197
|
+
this.throwHttpError(xhr.status, body);
|
|
198
|
+
}
|
|
199
|
+
} catch (error) {
|
|
200
|
+
if (error instanceof SpacelrError) {
|
|
201
|
+
reject(error);
|
|
202
|
+
} else {
|
|
203
|
+
reject(new SpacelrNetworkError("Failed to parse response"));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
xhr.addEventListener("error", () => {
|
|
208
|
+
reject(new SpacelrNetworkError("Network request failed"));
|
|
209
|
+
});
|
|
210
|
+
xhr.addEventListener("timeout", () => {
|
|
211
|
+
xhr.abort();
|
|
212
|
+
reject(new SpacelrTimeoutError(timeoutMs));
|
|
213
|
+
});
|
|
214
|
+
xhr.send(formData);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
buildUrl(path, query) {
|
|
218
|
+
const cleanPath = path.startsWith("/") ? path : `/${path}`;
|
|
219
|
+
const base = cleanPath.startsWith("/.well-known") ? new URL(this.config.apiUrl).origin : this.config.apiUrl.replace(/\/+$/, "");
|
|
220
|
+
const url = new URL(`${base}${cleanPath}`);
|
|
221
|
+
if (query) {
|
|
222
|
+
for (const [key, value] of Object.entries(query)) {
|
|
223
|
+
if (value !== void 0) {
|
|
224
|
+
url.searchParams.set(key, String(value));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return url.toString();
|
|
229
|
+
}
|
|
230
|
+
async buildHeaders(options) {
|
|
231
|
+
const headers = {
|
|
232
|
+
"Content-Type": "application/json",
|
|
233
|
+
"x-client-id": this.config.clientId,
|
|
234
|
+
"x-project-id": this.config.projectId,
|
|
235
|
+
...options.headers
|
|
236
|
+
};
|
|
237
|
+
if (options.authenticated) {
|
|
238
|
+
const token = await this.tokenManager.getAccessToken();
|
|
239
|
+
if (token) {
|
|
240
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return headers;
|
|
244
|
+
}
|
|
245
|
+
async buildFormHeaders() {
|
|
246
|
+
const headers = {
|
|
247
|
+
"x-client-id": this.config.clientId,
|
|
248
|
+
"x-project-id": this.config.projectId
|
|
249
|
+
};
|
|
250
|
+
const token = await this.tokenManager.getAccessToken();
|
|
251
|
+
if (token) {
|
|
252
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
253
|
+
}
|
|
254
|
+
return headers;
|
|
255
|
+
}
|
|
256
|
+
async parseResponse(response) {
|
|
257
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
258
|
+
if (contentType.includes("application/json")) {
|
|
259
|
+
return response.json();
|
|
260
|
+
}
|
|
261
|
+
const text = await response.text();
|
|
262
|
+
return { success: response.ok, data: text };
|
|
263
|
+
}
|
|
264
|
+
throwHttpError(statusCode, body) {
|
|
265
|
+
const apiBody = body;
|
|
266
|
+
const message = apiBody.error?.message ?? `HTTP ${statusCode}`;
|
|
267
|
+
const code = apiBody.error?.code ?? `HTTP_${statusCode}`;
|
|
268
|
+
const details = apiBody.error?.details;
|
|
269
|
+
if (statusCode === 401 || statusCode === 403) {
|
|
270
|
+
throw new SpacelrAuthError(message, statusCode, details);
|
|
271
|
+
}
|
|
272
|
+
throw new SpacelrError(message, code, statusCode, details);
|
|
273
|
+
}
|
|
274
|
+
extractData(body) {
|
|
275
|
+
const apiBody = body;
|
|
276
|
+
if ("success" in apiBody && apiBody.data !== void 0) {
|
|
277
|
+
const data = apiBody.data;
|
|
278
|
+
if (data["emailVerificationRequired"] === true) {
|
|
279
|
+
throw new SpacelrEmailVerificationRequiredError(data["emailSent"] === true);
|
|
280
|
+
}
|
|
281
|
+
if (data["twoFactorRequired"] === true && typeof data["twoFactorToken"] === "string") {
|
|
282
|
+
throw new SpacelrTwoFactorRequiredError(data["twoFactorToken"]);
|
|
283
|
+
}
|
|
284
|
+
return apiBody.data;
|
|
285
|
+
}
|
|
286
|
+
const rawBody = body;
|
|
287
|
+
if (rawBody["emailVerificationRequired"] === true) {
|
|
288
|
+
throw new SpacelrEmailVerificationRequiredError(rawBody["emailSent"] === true);
|
|
289
|
+
}
|
|
290
|
+
if (rawBody["twoFactorRequired"] === true && typeof rawBody["twoFactorToken"] === "string") {
|
|
291
|
+
throw new SpacelrTwoFactorRequiredError(rawBody["twoFactorToken"]);
|
|
292
|
+
}
|
|
293
|
+
return body;
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// libs/sdk/src/core/token-storage.ts
|
|
298
|
+
var MemoryTokenStorage = class {
|
|
299
|
+
constructor() {
|
|
300
|
+
this.tokens = null;
|
|
301
|
+
}
|
|
302
|
+
async getTokens() {
|
|
303
|
+
return this.tokens;
|
|
304
|
+
}
|
|
305
|
+
async setTokens(tokens) {
|
|
306
|
+
this.tokens = tokens;
|
|
307
|
+
}
|
|
308
|
+
async clearTokens() {
|
|
309
|
+
this.tokens = null;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
var BrowserTokenStorage = class {
|
|
313
|
+
constructor(storageKey = "spacelr_tokens") {
|
|
314
|
+
this.storageKey = storageKey;
|
|
315
|
+
}
|
|
316
|
+
async getTokens() {
|
|
317
|
+
try {
|
|
318
|
+
const raw = localStorage.getItem(this.storageKey);
|
|
319
|
+
if (!raw) return null;
|
|
320
|
+
return JSON.parse(raw);
|
|
321
|
+
} catch {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
async setTokens(tokens) {
|
|
326
|
+
try {
|
|
327
|
+
localStorage.setItem(this.storageKey, JSON.stringify(tokens));
|
|
328
|
+
} catch {
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
async clearTokens() {
|
|
332
|
+
try {
|
|
333
|
+
localStorage.removeItem(this.storageKey);
|
|
334
|
+
} catch {
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
// libs/sdk/src/core/token-manager.ts
|
|
340
|
+
var TokenManager = class {
|
|
341
|
+
constructor(storage, refreshBufferSeconds = 60) {
|
|
342
|
+
this.refreshCallback = null;
|
|
343
|
+
this.refreshPromise = null;
|
|
344
|
+
this.storage = storage ?? new MemoryTokenStorage();
|
|
345
|
+
this.refreshBufferSeconds = refreshBufferSeconds;
|
|
346
|
+
}
|
|
347
|
+
setRefreshCallback(callback) {
|
|
348
|
+
this.refreshCallback = callback;
|
|
349
|
+
}
|
|
350
|
+
async getAccessToken() {
|
|
351
|
+
const tokens = await this.storage.getTokens();
|
|
352
|
+
if (!tokens) return null;
|
|
353
|
+
if (this.isTokenExpired(tokens)) {
|
|
354
|
+
const refreshed = await this.tryRefresh(tokens);
|
|
355
|
+
return refreshed?.accessToken ?? null;
|
|
356
|
+
}
|
|
357
|
+
if (this.shouldRefresh(tokens)) {
|
|
358
|
+
this.tryRefresh(tokens).catch(() => {
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
return tokens.accessToken;
|
|
362
|
+
}
|
|
363
|
+
async setTokens(tokens) {
|
|
364
|
+
await this.storage.setTokens(tokens);
|
|
365
|
+
}
|
|
366
|
+
async clearTokens() {
|
|
367
|
+
await this.storage.clearTokens();
|
|
368
|
+
this.refreshPromise = null;
|
|
369
|
+
}
|
|
370
|
+
async getStoredTokens() {
|
|
371
|
+
return this.storage.getTokens();
|
|
372
|
+
}
|
|
373
|
+
isTokenExpired(tokens) {
|
|
374
|
+
if (!tokens.expiresAt) return false;
|
|
375
|
+
return Date.now() >= tokens.expiresAt * 1e3;
|
|
376
|
+
}
|
|
377
|
+
shouldRefresh(tokens) {
|
|
378
|
+
if (!tokens.expiresAt || !tokens.refreshToken) return false;
|
|
379
|
+
const bufferMs = this.refreshBufferSeconds * 1e3;
|
|
380
|
+
return Date.now() >= tokens.expiresAt * 1e3 - bufferMs;
|
|
381
|
+
}
|
|
382
|
+
async tryRefresh(tokens) {
|
|
383
|
+
if (!tokens.refreshToken || !this.refreshCallback) return null;
|
|
384
|
+
if (this.refreshPromise) {
|
|
385
|
+
return this.refreshPromise;
|
|
386
|
+
}
|
|
387
|
+
this.refreshPromise = this.executeRefresh(tokens.refreshToken);
|
|
388
|
+
try {
|
|
389
|
+
const result = await this.refreshPromise;
|
|
390
|
+
return result;
|
|
391
|
+
} finally {
|
|
392
|
+
this.refreshPromise = null;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
async executeRefresh(refreshToken) {
|
|
396
|
+
const callback = this.refreshCallback;
|
|
397
|
+
const newTokens = await callback(refreshToken);
|
|
398
|
+
await this.storage.setTokens(newTokens);
|
|
399
|
+
return newTokens;
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
// libs/sdk/src/types/common.ts
|
|
404
|
+
var FileVisibility = /* @__PURE__ */ ((FileVisibility2) => {
|
|
405
|
+
FileVisibility2["PRIVATE"] = "private";
|
|
406
|
+
FileVisibility2["SHARED"] = "shared";
|
|
407
|
+
FileVisibility2["PUBLIC"] = "public";
|
|
408
|
+
return FileVisibility2;
|
|
409
|
+
})(FileVisibility || {});
|
|
410
|
+
var GrantType = /* @__PURE__ */ ((GrantType2) => {
|
|
411
|
+
GrantType2["AUTHORIZATION_CODE"] = "authorization_code";
|
|
412
|
+
GrantType2["CLIENT_CREDENTIALS"] = "client_credentials";
|
|
413
|
+
GrantType2["REFRESH_TOKEN"] = "refresh_token";
|
|
414
|
+
return GrantType2;
|
|
415
|
+
})(GrantType || {});
|
|
416
|
+
var CodeChallengeMethod = /* @__PURE__ */ ((CodeChallengeMethod2) => {
|
|
417
|
+
CodeChallengeMethod2["PLAIN"] = "plain";
|
|
418
|
+
CodeChallengeMethod2["S256"] = "S256";
|
|
419
|
+
return CodeChallengeMethod2;
|
|
420
|
+
})(CodeChallengeMethod || {});
|
|
421
|
+
var SharePermission = /* @__PURE__ */ ((SharePermission2) => {
|
|
422
|
+
SharePermission2["READ"] = "read";
|
|
423
|
+
SharePermission2["WRITE"] = "write";
|
|
424
|
+
return SharePermission2;
|
|
425
|
+
})(SharePermission || {});
|
|
426
|
+
|
|
427
|
+
// libs/sdk/src/core/pkce.ts
|
|
428
|
+
function generateRandomBytes(length) {
|
|
429
|
+
if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.getRandomValues) {
|
|
430
|
+
const bytes = new Uint8Array(length);
|
|
431
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
432
|
+
return bytes;
|
|
433
|
+
}
|
|
434
|
+
const nodeCrypto = require("crypto");
|
|
435
|
+
return new Uint8Array(nodeCrypto.randomBytes(length));
|
|
436
|
+
}
|
|
437
|
+
function base64UrlEncode(buffer) {
|
|
438
|
+
let binary = "";
|
|
439
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
440
|
+
binary += String.fromCharCode(buffer[i]);
|
|
441
|
+
}
|
|
442
|
+
const base64 = btoa(binary);
|
|
443
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
444
|
+
}
|
|
445
|
+
async function sha256(input) {
|
|
446
|
+
if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
|
|
447
|
+
const encoder = new TextEncoder();
|
|
448
|
+
const data = encoder.encode(input);
|
|
449
|
+
const hash2 = await globalThis.crypto.subtle.digest("SHA-256", data);
|
|
450
|
+
return new Uint8Array(hash2);
|
|
451
|
+
}
|
|
452
|
+
const nodeCrypto = require("crypto");
|
|
453
|
+
const hash = nodeCrypto.createHash("sha256").update(input).digest();
|
|
454
|
+
return new Uint8Array(hash);
|
|
455
|
+
}
|
|
456
|
+
async function generatePKCEChallenge() {
|
|
457
|
+
const randomBytes = generateRandomBytes(32);
|
|
458
|
+
const codeVerifier = base64UrlEncode(randomBytes);
|
|
459
|
+
const hashBytes = await sha256(codeVerifier);
|
|
460
|
+
const codeChallenge = base64UrlEncode(hashBytes);
|
|
461
|
+
return {
|
|
462
|
+
codeVerifier,
|
|
463
|
+
codeChallenge,
|
|
464
|
+
codeChallengeMethod: "S256" /* S256 */
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// libs/sdk/src/core/realtime.ts
|
|
469
|
+
var import_socket = require("socket.io-client");
|
|
470
|
+
var RealtimeClient = class {
|
|
471
|
+
constructor(config) {
|
|
472
|
+
this.socket = null;
|
|
473
|
+
this.subscriptions = /* @__PURE__ */ new Map();
|
|
474
|
+
this.connecting = null;
|
|
475
|
+
// Store original where objects per room for reconnect resubscription
|
|
476
|
+
this.roomWhereMap = /* @__PURE__ */ new Map();
|
|
477
|
+
this.config = config;
|
|
478
|
+
}
|
|
479
|
+
async subscribe(projectId, collectionName, callback, onError, where) {
|
|
480
|
+
await this.ensureConnected();
|
|
481
|
+
const room = this.buildRoomKey(projectId, collectionName, where);
|
|
482
|
+
if (!this.subscriptions.has(room)) {
|
|
483
|
+
this.subscriptions.set(room, /* @__PURE__ */ new Set());
|
|
484
|
+
}
|
|
485
|
+
const callbacks = this.subscriptions.get(room);
|
|
486
|
+
callbacks?.add(callback);
|
|
487
|
+
if (callbacks?.size === 1) {
|
|
488
|
+
if (where && Object.keys(where).length > 0) {
|
|
489
|
+
this.roomWhereMap.set(room, where);
|
|
490
|
+
}
|
|
491
|
+
const payload = { projectId, collectionName };
|
|
492
|
+
if (where && Object.keys(where).length > 0) {
|
|
493
|
+
payload["where"] = where;
|
|
494
|
+
}
|
|
495
|
+
this.socket?.emit(
|
|
496
|
+
"subscribe",
|
|
497
|
+
payload,
|
|
498
|
+
(response) => {
|
|
499
|
+
if (response.error && onError) {
|
|
500
|
+
onError(new Error(response.error));
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
return () => {
|
|
506
|
+
const callbacks2 = this.subscriptions.get(room);
|
|
507
|
+
if (callbacks2) {
|
|
508
|
+
callbacks2.delete(callback);
|
|
509
|
+
if (callbacks2.size === 0) {
|
|
510
|
+
this.subscriptions.delete(room);
|
|
511
|
+
this.roomWhereMap.delete(room);
|
|
512
|
+
const payload = { projectId, collectionName };
|
|
513
|
+
if (where && Object.keys(where).length > 0) {
|
|
514
|
+
payload["where"] = where;
|
|
515
|
+
}
|
|
516
|
+
this.socket?.emit("unsubscribe", payload);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
disconnect() {
|
|
522
|
+
if (this.socket) {
|
|
523
|
+
this.socket.disconnect();
|
|
524
|
+
this.socket = null;
|
|
525
|
+
}
|
|
526
|
+
this.subscriptions.clear();
|
|
527
|
+
this.roomWhereMap.clear();
|
|
528
|
+
this.connecting = null;
|
|
529
|
+
}
|
|
530
|
+
buildRoomKey(projectId, collectionName, where) {
|
|
531
|
+
const base = `db:${projectId}:${collectionName}`;
|
|
532
|
+
if (!where || Object.keys(where).length === 0) {
|
|
533
|
+
return base;
|
|
534
|
+
}
|
|
535
|
+
const filterStr = Object.keys(where).sort().map((k) => `${k}=${String(where[k])}`).join("&");
|
|
536
|
+
return `${base}?${filterStr}`;
|
|
537
|
+
}
|
|
538
|
+
async ensureConnected() {
|
|
539
|
+
if (this.socket?.connected) return;
|
|
540
|
+
if (this.connecting) {
|
|
541
|
+
return this.connecting;
|
|
542
|
+
}
|
|
543
|
+
this.connecting = this.connect();
|
|
544
|
+
try {
|
|
545
|
+
await this.connecting;
|
|
546
|
+
} finally {
|
|
547
|
+
this.connecting = null;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
async connect() {
|
|
551
|
+
const token = await this.config.getToken();
|
|
552
|
+
if (!token) {
|
|
553
|
+
throw new Error("No authentication token available");
|
|
554
|
+
}
|
|
555
|
+
const wsUrl = this.config.baseUrl.replace(/\/api\/v\d+\/?$/, "");
|
|
556
|
+
return new Promise((resolve, reject) => {
|
|
557
|
+
let initialConnect = true;
|
|
558
|
+
this.socket = (0, import_socket.io)(`${wsUrl}/database`, {
|
|
559
|
+
auth: async (cb) => {
|
|
560
|
+
try {
|
|
561
|
+
const freshToken = await this.config.getToken();
|
|
562
|
+
cb({ token: freshToken });
|
|
563
|
+
} catch {
|
|
564
|
+
cb({ token: null });
|
|
565
|
+
}
|
|
566
|
+
},
|
|
567
|
+
transports: ["websocket"],
|
|
568
|
+
reconnection: false
|
|
569
|
+
// Disabled until first successful connect
|
|
570
|
+
});
|
|
571
|
+
this.socket.on("authenticated", () => {
|
|
572
|
+
if (initialConnect) {
|
|
573
|
+
initialConnect = false;
|
|
574
|
+
if (this.socket) {
|
|
575
|
+
this.socket.io.opts.reconnection = true;
|
|
576
|
+
this.socket.io.opts.reconnectionDelay = 1e3;
|
|
577
|
+
this.socket.io.opts.reconnectionDelayMax = 5e3;
|
|
578
|
+
this.socket.io.opts.reconnectionAttempts = 50;
|
|
579
|
+
}
|
|
580
|
+
resolve();
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
this.socket.on("connect", () => {
|
|
584
|
+
if (!initialConnect) {
|
|
585
|
+
this.resubscribeAll();
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
this.socket.on("connect_error", (err) => {
|
|
589
|
+
if (initialConnect) {
|
|
590
|
+
reject(new Error(`WebSocket connection failed: ${err.message}`));
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
this.socket.on("db:event", (event) => {
|
|
594
|
+
const base = `db:${event.projectId}:${event.collectionName}`;
|
|
595
|
+
const baseCallbacks = this.subscriptions.get(base);
|
|
596
|
+
if (baseCallbacks) {
|
|
597
|
+
for (const cb of baseCallbacks) {
|
|
598
|
+
try {
|
|
599
|
+
cb(event);
|
|
600
|
+
} catch {
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
for (const [room, callbacks] of this.subscriptions) {
|
|
605
|
+
if (room.startsWith(`${base}?`) && this.eventMatchesRoom(event, room)) {
|
|
606
|
+
for (const cb of callbacks) {
|
|
607
|
+
try {
|
|
608
|
+
cb(event);
|
|
609
|
+
} catch {
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
this.socket.on("disconnect", () => {
|
|
616
|
+
});
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
eventMatchesRoom(event, room) {
|
|
620
|
+
const base = `db:${event.projectId}:${event.collectionName}`;
|
|
621
|
+
if (room === base) return true;
|
|
622
|
+
if (!room.startsWith(`${base}?`)) return false;
|
|
623
|
+
const where = this.roomWhereMap.get(room);
|
|
624
|
+
if (!where) return true;
|
|
625
|
+
if (!event.document) return false;
|
|
626
|
+
for (const [key, value] of Object.entries(where)) {
|
|
627
|
+
const docValue = event.document[key];
|
|
628
|
+
if (Array.isArray(docValue)) {
|
|
629
|
+
if (!docValue.some((item) => String(item) === String(value))) {
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
632
|
+
} else if (String(docValue) !== String(value)) {
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return true;
|
|
637
|
+
}
|
|
638
|
+
resubscribeAll() {
|
|
639
|
+
for (const [room] of this.subscriptions) {
|
|
640
|
+
const queryIdx = room.indexOf("?");
|
|
641
|
+
const basePart = queryIdx >= 0 ? room.substring(0, queryIdx) : room;
|
|
642
|
+
const parts = basePart.split(":");
|
|
643
|
+
if (parts.length >= 3) {
|
|
644
|
+
const projectId = parts[1];
|
|
645
|
+
const collectionName = parts.slice(2).join(":");
|
|
646
|
+
const payload = { projectId, collectionName };
|
|
647
|
+
const where = this.roomWhereMap.get(room);
|
|
648
|
+
if (where) {
|
|
649
|
+
payload["where"] = where;
|
|
650
|
+
}
|
|
651
|
+
this.socket?.emit(
|
|
652
|
+
"subscribe",
|
|
653
|
+
payload,
|
|
654
|
+
(response) => {
|
|
655
|
+
if (response?.error) {
|
|
656
|
+
this.subscriptions.delete(room);
|
|
657
|
+
this.roomWhereMap.delete(room);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
// libs/sdk/src/modules/auth.module.ts
|
|
667
|
+
var AuthModule = class {
|
|
668
|
+
constructor(http, tokenManager, config) {
|
|
669
|
+
this.http = http;
|
|
670
|
+
this.tokenManager = tokenManager;
|
|
671
|
+
this.config = config;
|
|
672
|
+
this.tokenManager.setRefreshCallback(async (refreshToken) => {
|
|
673
|
+
const result = await this.refresh(refreshToken);
|
|
674
|
+
const expiresAt = result.expires_in ? Math.floor(Date.now() / 1e3) + result.expires_in : void 0;
|
|
675
|
+
return {
|
|
676
|
+
accessToken: result.access_token,
|
|
677
|
+
refreshToken: result.refresh_token,
|
|
678
|
+
expiresAt
|
|
679
|
+
};
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
async login(params) {
|
|
683
|
+
const response = await this.http.request({
|
|
684
|
+
method: "POST",
|
|
685
|
+
path: "/auth/login",
|
|
686
|
+
body: params
|
|
687
|
+
});
|
|
688
|
+
await this.storeTokensFromLogin(response);
|
|
689
|
+
return response;
|
|
690
|
+
}
|
|
691
|
+
async register(params) {
|
|
692
|
+
const response = await this.http.request({
|
|
693
|
+
method: "POST",
|
|
694
|
+
path: "/auth/register",
|
|
695
|
+
body: params
|
|
696
|
+
});
|
|
697
|
+
if (response.access_token) {
|
|
698
|
+
await this.storeTokensFromRegister(response);
|
|
699
|
+
}
|
|
700
|
+
return response;
|
|
701
|
+
}
|
|
702
|
+
async refresh(refreshToken) {
|
|
703
|
+
return this.http.request({
|
|
704
|
+
method: "POST",
|
|
705
|
+
path: "/auth/refresh",
|
|
706
|
+
body: { refreshToken }
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
async getProfile() {
|
|
710
|
+
return this.http.request({
|
|
711
|
+
method: "GET",
|
|
712
|
+
path: "/auth/me",
|
|
713
|
+
authenticated: true
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
async logout() {
|
|
717
|
+
await this.http.request({
|
|
718
|
+
method: "POST",
|
|
719
|
+
path: "/auth/logout",
|
|
720
|
+
authenticated: true
|
|
721
|
+
});
|
|
722
|
+
await this.tokenManager.clearTokens();
|
|
723
|
+
}
|
|
724
|
+
async verifyEmail(token) {
|
|
725
|
+
return this.http.request({
|
|
726
|
+
method: "GET",
|
|
727
|
+
path: "/auth/verify-email",
|
|
728
|
+
query: { token }
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
async resendVerification(email) {
|
|
732
|
+
return this.http.request({
|
|
733
|
+
method: "POST",
|
|
734
|
+
path: "/auth/resend-verification",
|
|
735
|
+
body: { email }
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
async getUserInfo() {
|
|
739
|
+
return this.http.request({
|
|
740
|
+
method: "GET",
|
|
741
|
+
path: this.config.userInfoEndpoint ?? "/auth/userinfo",
|
|
742
|
+
authenticated: true
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
getAuthorizationUrl(params) {
|
|
746
|
+
const baseUrl = this.config.apiUrl.replace(/\/+$/, "");
|
|
747
|
+
const endpoint = this.config.authorizationEndpoint ?? "/auth/authorize";
|
|
748
|
+
const url = new URL(`${baseUrl}${endpoint}`);
|
|
749
|
+
url.searchParams.set("client_id", this.config.clientId);
|
|
750
|
+
url.searchParams.set("redirect_uri", params.redirectUri);
|
|
751
|
+
url.searchParams.set("response_type", params.responseType ?? "code");
|
|
752
|
+
const scope = params.scope ?? this.config.scopes?.join(" ") ?? "openid";
|
|
753
|
+
url.searchParams.set("scope", scope);
|
|
754
|
+
if (params.state) {
|
|
755
|
+
url.searchParams.set("state", params.state);
|
|
756
|
+
}
|
|
757
|
+
if (params.codeChallenge) {
|
|
758
|
+
url.searchParams.set("code_challenge", params.codeChallenge);
|
|
759
|
+
}
|
|
760
|
+
if (params.codeChallengeMethod) {
|
|
761
|
+
url.searchParams.set(
|
|
762
|
+
"code_challenge_method",
|
|
763
|
+
params.codeChallengeMethod
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
return url.toString();
|
|
767
|
+
}
|
|
768
|
+
async exchangeCode(params) {
|
|
769
|
+
const body = {
|
|
770
|
+
grant_type: params.grantType ?? "authorization_code" /* AUTHORIZATION_CODE */,
|
|
771
|
+
code: params.code,
|
|
772
|
+
redirect_uri: params.redirectUri,
|
|
773
|
+
client_id: this.config.clientId
|
|
774
|
+
};
|
|
775
|
+
if (params.clientSecret) {
|
|
776
|
+
body["client_secret"] = params.clientSecret;
|
|
777
|
+
}
|
|
778
|
+
if (params.codeVerifier) {
|
|
779
|
+
body["code_verifier"] = params.codeVerifier;
|
|
780
|
+
}
|
|
781
|
+
const tokenEndpoint = this.config.tokenEndpoint ?? "/auth/token";
|
|
782
|
+
const response = await this.http.request({
|
|
783
|
+
method: "POST",
|
|
784
|
+
path: tokenEndpoint,
|
|
785
|
+
body
|
|
786
|
+
});
|
|
787
|
+
const expiresAt = response.expires_in ? Math.floor(Date.now() / 1e3) + response.expires_in : void 0;
|
|
788
|
+
await this.tokenManager.setTokens({
|
|
789
|
+
accessToken: response.access_token,
|
|
790
|
+
refreshToken: response.refresh_token,
|
|
791
|
+
expiresAt
|
|
792
|
+
});
|
|
793
|
+
return response;
|
|
794
|
+
}
|
|
795
|
+
async generatePKCE() {
|
|
796
|
+
return generatePKCEChallenge();
|
|
797
|
+
}
|
|
798
|
+
async getOpenIDConfiguration() {
|
|
799
|
+
return this.http.request({
|
|
800
|
+
method: "GET",
|
|
801
|
+
path: "/.well-known/openid-configuration"
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
async getJWKS() {
|
|
805
|
+
return this.http.request({
|
|
806
|
+
method: "GET",
|
|
807
|
+
path: "/.well-known/jwks.json"
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Request a password reset email.
|
|
812
|
+
* Always returns a generic message regardless of whether the email exists.
|
|
813
|
+
*/
|
|
814
|
+
async requestPasswordReset(email) {
|
|
815
|
+
return this.http.request({
|
|
816
|
+
method: "POST",
|
|
817
|
+
path: "/auth/request-password-reset",
|
|
818
|
+
body: { email }
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Reset password using a token received via email.
|
|
823
|
+
*/
|
|
824
|
+
async resetPassword(token, password) {
|
|
825
|
+
return this.http.request({
|
|
826
|
+
method: "POST",
|
|
827
|
+
path: "/auth/reset-password",
|
|
828
|
+
body: { token, password }
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Exchange a one-time verification code for tokens.
|
|
833
|
+
* Use this after email verification redirects the user with a ?loginCode= parameter.
|
|
834
|
+
*/
|
|
835
|
+
async exchangeVerificationCode(code) {
|
|
836
|
+
const response = await this.http.request({
|
|
837
|
+
method: "POST",
|
|
838
|
+
path: "/auth/exchange-code",
|
|
839
|
+
body: { code }
|
|
840
|
+
});
|
|
841
|
+
await this.storeTokensFromLogin(response);
|
|
842
|
+
return response;
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Resend a two-factor authentication code email.
|
|
846
|
+
* Call this when the user hasn't received the code or it expired.
|
|
847
|
+
*/
|
|
848
|
+
async resendTwoFactorCode(token) {
|
|
849
|
+
return this.http.request({
|
|
850
|
+
method: "POST",
|
|
851
|
+
path: "/auth/resend-two-factor-code",
|
|
852
|
+
body: { token }
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Verify a two-factor authentication code.
|
|
857
|
+
* Call this after catching SpacelrTwoFactorRequiredError from login().
|
|
858
|
+
*/
|
|
859
|
+
async verifyTwoFactor(params) {
|
|
860
|
+
const response = await this.http.request({
|
|
861
|
+
method: "POST",
|
|
862
|
+
path: "/auth/verify-two-factor",
|
|
863
|
+
body: { token: params.token, code: params.code }
|
|
864
|
+
});
|
|
865
|
+
await this.storeTokensFromLogin(response);
|
|
866
|
+
return response;
|
|
867
|
+
}
|
|
868
|
+
async storeTokensFromLogin(response) {
|
|
869
|
+
const expiresAt = response.expires_in ? Math.floor(Date.now() / 1e3) + response.expires_in : void 0;
|
|
870
|
+
await this.tokenManager.setTokens({
|
|
871
|
+
accessToken: response.access_token,
|
|
872
|
+
refreshToken: response.refresh_token,
|
|
873
|
+
expiresAt
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
async storeTokensFromRegister(response) {
|
|
877
|
+
if (!response.access_token) return;
|
|
878
|
+
await this.tokenManager.setTokens({
|
|
879
|
+
accessToken: response.access_token,
|
|
880
|
+
refreshToken: response.refresh_token
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
// libs/sdk/src/modules/storage.module.ts
|
|
886
|
+
var StorageModule = class {
|
|
887
|
+
constructor(http, tokenManager, config) {
|
|
888
|
+
this.http = http;
|
|
889
|
+
this.tokenManager = tokenManager;
|
|
890
|
+
this.config = config;
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Upload a file through the gateway (no direct MinIO access).
|
|
894
|
+
* Accepts a Blob/File (browser) or ArrayBuffer/Uint8Array (Node).
|
|
895
|
+
*/
|
|
896
|
+
async uploadFile(file, params, onProgress) {
|
|
897
|
+
const formData = new FormData();
|
|
898
|
+
const blob = file instanceof Blob ? file : new Blob([file], { type: params.mimeType });
|
|
899
|
+
formData.append("file", blob, params.filename);
|
|
900
|
+
if (params.visibility) formData.append("visibility", params.visibility);
|
|
901
|
+
if (params.description) formData.append("description", params.description);
|
|
902
|
+
const progressHandler = onProgress ? (e) => {
|
|
903
|
+
onProgress({
|
|
904
|
+
loaded: e.loaded,
|
|
905
|
+
total: e.total,
|
|
906
|
+
percentage: e.total > 0 ? Math.round(e.loaded / e.total * 100) : 0
|
|
907
|
+
});
|
|
908
|
+
} : void 0;
|
|
909
|
+
return this.http.uploadForm("/storage/files", formData, progressHandler);
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Upload a large file using multipart upload through the gateway.
|
|
913
|
+
* Splits into parts, uploads concurrently, and completes.
|
|
914
|
+
*/
|
|
915
|
+
async uploadLargeFile(file, params, onProgress) {
|
|
916
|
+
const fileSize = file instanceof Blob ? file.size : file.byteLength;
|
|
917
|
+
const concurrency = params.concurrency ?? 3;
|
|
918
|
+
const init = await this.initMultipartUpload({
|
|
919
|
+
filename: params.filename,
|
|
920
|
+
mimeType: params.mimeType,
|
|
921
|
+
totalSizeBytes: fileSize,
|
|
922
|
+
visibility: params.visibility,
|
|
923
|
+
description: params.description
|
|
924
|
+
});
|
|
925
|
+
const { partSize, totalParts, fileId } = init;
|
|
926
|
+
const completedParts = [];
|
|
927
|
+
let completedBytes = 0;
|
|
928
|
+
const partProgressMap = /* @__PURE__ */ new Map();
|
|
929
|
+
try {
|
|
930
|
+
const allPartNumbers = Array.from(
|
|
931
|
+
{ length: totalParts },
|
|
932
|
+
(_, i) => i + 1
|
|
933
|
+
);
|
|
934
|
+
for (let i = 0; i < allPartNumbers.length; i += concurrency) {
|
|
935
|
+
const batch = allPartNumbers.slice(i, i + concurrency);
|
|
936
|
+
const uploads = batch.map(async (partNumber) => {
|
|
937
|
+
const start = (partNumber - 1) * partSize;
|
|
938
|
+
const end = Math.min(start + partSize, fileSize);
|
|
939
|
+
const chunkSize = end - start;
|
|
940
|
+
const chunk = file instanceof Blob ? file.slice(start, end) : new Blob([file.slice(start, end)]);
|
|
941
|
+
const formData = new FormData();
|
|
942
|
+
formData.append("file", chunk, `part-${partNumber}`);
|
|
943
|
+
formData.append("partNumber", String(partNumber));
|
|
944
|
+
const partProgress = onProgress && typeof XMLHttpRequest !== "undefined" ? (e) => {
|
|
945
|
+
const ratio = e.total > 0 ? e.loaded / e.total : 0;
|
|
946
|
+
partProgressMap.set(partNumber, Math.min(ratio * chunkSize, chunkSize));
|
|
947
|
+
const inFlightBytes = Array.from(partProgressMap.values()).reduce((sum, v) => sum + v, 0);
|
|
948
|
+
const totalLoaded = Math.min(completedBytes + inFlightBytes, fileSize);
|
|
949
|
+
onProgress({
|
|
950
|
+
loaded: totalLoaded,
|
|
951
|
+
total: fileSize,
|
|
952
|
+
percentage: fileSize > 0 ? Math.min(100, Math.round(totalLoaded / fileSize * 100)) : 0
|
|
953
|
+
});
|
|
954
|
+
} : void 0;
|
|
955
|
+
const result = await this.http.uploadForm(
|
|
956
|
+
`/storage/files/${fileId}/multipart/upload-part`,
|
|
957
|
+
formData,
|
|
958
|
+
partProgress
|
|
959
|
+
);
|
|
960
|
+
partProgressMap.delete(partNumber);
|
|
961
|
+
completedBytes += chunkSize;
|
|
962
|
+
if (onProgress) {
|
|
963
|
+
onProgress({
|
|
964
|
+
loaded: Math.min(completedBytes, fileSize),
|
|
965
|
+
total: fileSize,
|
|
966
|
+
percentage: fileSize > 0 ? Math.min(100, Math.round(completedBytes / fileSize * 100)) : 0
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
completedParts.push({
|
|
970
|
+
partNumber,
|
|
971
|
+
etag: result.etag
|
|
972
|
+
});
|
|
973
|
+
});
|
|
974
|
+
await Promise.all(uploads);
|
|
975
|
+
partProgressMap.clear();
|
|
976
|
+
}
|
|
977
|
+
completedParts.sort((a, b) => a.partNumber - b.partNumber);
|
|
978
|
+
await this.completeMultipartUpload(fileId, completedParts);
|
|
979
|
+
return this.getFileInfo(fileId);
|
|
980
|
+
} catch (error) {
|
|
981
|
+
try {
|
|
982
|
+
await this.abortMultipartUpload(fileId);
|
|
983
|
+
} catch {
|
|
984
|
+
}
|
|
985
|
+
throw error;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
async initMultipartUpload(params) {
|
|
989
|
+
return this.http.request({
|
|
990
|
+
method: "POST",
|
|
991
|
+
path: "/storage/files/multipart/init",
|
|
992
|
+
body: params,
|
|
993
|
+
authenticated: true
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
async completeMultipartUpload(fileId, parts) {
|
|
997
|
+
return this.http.request({
|
|
998
|
+
method: "POST",
|
|
999
|
+
path: `/storage/files/${fileId}/multipart/complete`,
|
|
1000
|
+
body: { parts },
|
|
1001
|
+
authenticated: true
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
async abortMultipartUpload(fileId) {
|
|
1005
|
+
await this.http.request({
|
|
1006
|
+
method: "POST",
|
|
1007
|
+
path: `/storage/files/${fileId}/multipart/abort`,
|
|
1008
|
+
authenticated: true
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
async listFiles(params) {
|
|
1012
|
+
return this.http.request({
|
|
1013
|
+
method: "GET",
|
|
1014
|
+
path: "/storage/files",
|
|
1015
|
+
query: params,
|
|
1016
|
+
authenticated: true
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
async listSharedFiles(params) {
|
|
1020
|
+
return this.http.request({
|
|
1021
|
+
method: "GET",
|
|
1022
|
+
path: "/storage/shared",
|
|
1023
|
+
query: params,
|
|
1024
|
+
authenticated: true
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
async getFileInfo(fileId) {
|
|
1028
|
+
return this.http.request({
|
|
1029
|
+
method: "GET",
|
|
1030
|
+
path: `/storage/files/${fileId}`,
|
|
1031
|
+
authenticated: true
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
async downloadFile(fileId) {
|
|
1035
|
+
const baseUrl = this.config.apiUrl.replace(/\/+$/, "");
|
|
1036
|
+
const url = `${baseUrl}/storage/files/${fileId}/download`;
|
|
1037
|
+
const headers = {
|
|
1038
|
+
"x-client-id": this.config.clientId,
|
|
1039
|
+
"x-project-id": this.config.projectId
|
|
1040
|
+
};
|
|
1041
|
+
const token = await this.tokenManager.getAccessToken();
|
|
1042
|
+
if (token) {
|
|
1043
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
1044
|
+
}
|
|
1045
|
+
const response = await fetch(url, { headers });
|
|
1046
|
+
if (!response.ok) {
|
|
1047
|
+
throw new Error(`Download failed: ${response.status}`);
|
|
1048
|
+
}
|
|
1049
|
+
return response.blob();
|
|
1050
|
+
}
|
|
1051
|
+
async getDownloadUrl(fileId) {
|
|
1052
|
+
return this.http.request({
|
|
1053
|
+
method: "GET",
|
|
1054
|
+
path: `/storage/files/${fileId}/download-url`,
|
|
1055
|
+
authenticated: true
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
async deleteFile(fileId) {
|
|
1059
|
+
await this.http.request({
|
|
1060
|
+
method: "DELETE",
|
|
1061
|
+
path: `/storage/files/${fileId}`,
|
|
1062
|
+
authenticated: true
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
async shareFile(fileId, params) {
|
|
1066
|
+
await this.http.request({
|
|
1067
|
+
method: "POST",
|
|
1068
|
+
path: `/storage/files/${fileId}/share`,
|
|
1069
|
+
body: params,
|
|
1070
|
+
authenticated: true
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
async unshareFile(fileId, params) {
|
|
1074
|
+
await this.http.request({
|
|
1075
|
+
method: "POST",
|
|
1076
|
+
path: `/storage/files/${fileId}/unshare`,
|
|
1077
|
+
body: params,
|
|
1078
|
+
authenticated: true
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
async updateVisibility(fileId, visibility) {
|
|
1082
|
+
return this.http.request({
|
|
1083
|
+
method: "PATCH",
|
|
1084
|
+
path: `/storage/files/${fileId}/visibility`,
|
|
1085
|
+
body: { visibility },
|
|
1086
|
+
authenticated: true
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
async getQuota() {
|
|
1090
|
+
return this.http.request({
|
|
1091
|
+
method: "GET",
|
|
1092
|
+
path: "/storage/quota",
|
|
1093
|
+
authenticated: true
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
async getPublicFileUrl(fileId, projectId) {
|
|
1097
|
+
const baseUrl = this.config.apiUrl.replace(/\/+$/, "");
|
|
1098
|
+
const resolvedProjectId = projectId ?? this.config.projectId;
|
|
1099
|
+
return `${baseUrl}/public/files/${resolvedProjectId}/${fileId}`;
|
|
1100
|
+
}
|
|
1101
|
+
};
|
|
1102
|
+
|
|
1103
|
+
// libs/sdk/src/modules/database.module.ts
|
|
1104
|
+
var QueryBuilder = class {
|
|
1105
|
+
constructor(http, basePath, filter) {
|
|
1106
|
+
this.http = http;
|
|
1107
|
+
this.basePath = basePath;
|
|
1108
|
+
this._populate = [];
|
|
1109
|
+
this._filter = filter;
|
|
1110
|
+
}
|
|
1111
|
+
sort(sort) {
|
|
1112
|
+
this._sort = sort;
|
|
1113
|
+
return this;
|
|
1114
|
+
}
|
|
1115
|
+
limit(limit) {
|
|
1116
|
+
this._limit = limit;
|
|
1117
|
+
return this;
|
|
1118
|
+
}
|
|
1119
|
+
offset(offset) {
|
|
1120
|
+
this._offset = offset;
|
|
1121
|
+
return this;
|
|
1122
|
+
}
|
|
1123
|
+
select(fields) {
|
|
1124
|
+
this._fields = fields;
|
|
1125
|
+
return this;
|
|
1126
|
+
}
|
|
1127
|
+
populate(field, collection, foreignField) {
|
|
1128
|
+
this._populate.push({ field, collection, foreignField });
|
|
1129
|
+
return this;
|
|
1130
|
+
}
|
|
1131
|
+
async execute() {
|
|
1132
|
+
const query = {};
|
|
1133
|
+
if (this._filter) query["filter"] = JSON.stringify(this._filter);
|
|
1134
|
+
if (this._sort) query["sort"] = JSON.stringify(this._sort);
|
|
1135
|
+
if (this._limit !== void 0) query["limit"] = this._limit;
|
|
1136
|
+
if (this._offset !== void 0) query["offset"] = this._offset;
|
|
1137
|
+
if (this._fields) query["fields"] = this._fields.join(",");
|
|
1138
|
+
if (this._populate.length) {
|
|
1139
|
+
query["populate"] = this._populate.map(
|
|
1140
|
+
(p) => p.foreignField ? `${p.field}:${p.collection}:${p.foreignField}` : `${p.field}:${p.collection}`
|
|
1141
|
+
).join(",");
|
|
1142
|
+
}
|
|
1143
|
+
return this.http.request({
|
|
1144
|
+
method: "GET",
|
|
1145
|
+
path: this.basePath,
|
|
1146
|
+
query,
|
|
1147
|
+
authenticated: true
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
};
|
|
1151
|
+
var CollectionRef = class {
|
|
1152
|
+
constructor(http, realtime, projectId, collectionName) {
|
|
1153
|
+
this.http = http;
|
|
1154
|
+
this.realtime = realtime;
|
|
1155
|
+
this.projectId = projectId;
|
|
1156
|
+
this.collectionName = collectionName;
|
|
1157
|
+
this.basePath = `/db/${collectionName}`;
|
|
1158
|
+
}
|
|
1159
|
+
async insert(document) {
|
|
1160
|
+
return this.http.request({
|
|
1161
|
+
method: "POST",
|
|
1162
|
+
path: this.basePath,
|
|
1163
|
+
body: { documents: [document] },
|
|
1164
|
+
authenticated: true
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
async insertMany(documents) {
|
|
1168
|
+
return this.http.request({
|
|
1169
|
+
method: "POST",
|
|
1170
|
+
path: this.basePath,
|
|
1171
|
+
body: { documents },
|
|
1172
|
+
authenticated: true
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
find(filter) {
|
|
1176
|
+
return new QueryBuilder(this.http, this.basePath, filter);
|
|
1177
|
+
}
|
|
1178
|
+
async findById(id, options) {
|
|
1179
|
+
const query = {};
|
|
1180
|
+
if (options?.populate?.length) {
|
|
1181
|
+
query["populate"] = options.populate.map(
|
|
1182
|
+
(p) => p.foreignField ? `${p.field}:${p.collection}:${p.foreignField}` : `${p.field}:${p.collection}`
|
|
1183
|
+
).join(",");
|
|
1184
|
+
}
|
|
1185
|
+
return this.http.request({
|
|
1186
|
+
method: "GET",
|
|
1187
|
+
path: `${this.basePath}/${id}`,
|
|
1188
|
+
query,
|
|
1189
|
+
authenticated: true
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
async update(id, update) {
|
|
1193
|
+
return this.http.request({
|
|
1194
|
+
method: "PATCH",
|
|
1195
|
+
path: `${this.basePath}/${id}`,
|
|
1196
|
+
body: { update },
|
|
1197
|
+
authenticated: true
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
async delete(id) {
|
|
1201
|
+
return this.http.request({
|
|
1202
|
+
method: "DELETE",
|
|
1203
|
+
path: `${this.basePath}/${id}`,
|
|
1204
|
+
authenticated: true
|
|
1205
|
+
});
|
|
1206
|
+
}
|
|
1207
|
+
async count(filter) {
|
|
1208
|
+
const result = await this.http.request({
|
|
1209
|
+
method: "POST",
|
|
1210
|
+
path: `${this.basePath}/count`,
|
|
1211
|
+
body: { filter },
|
|
1212
|
+
authenticated: true
|
|
1213
|
+
});
|
|
1214
|
+
return result.count;
|
|
1215
|
+
}
|
|
1216
|
+
subscribe(handlers) {
|
|
1217
|
+
if (!this.realtime) {
|
|
1218
|
+
throw new Error("Realtime not available: no RealtimeClient configured");
|
|
1219
|
+
}
|
|
1220
|
+
let unsubscribeFn = null;
|
|
1221
|
+
let pendingUnsub = false;
|
|
1222
|
+
const callback = (event) => {
|
|
1223
|
+
switch (event.type) {
|
|
1224
|
+
case "insert":
|
|
1225
|
+
if (handlers.onInsert && event.document) {
|
|
1226
|
+
handlers.onInsert(event.document);
|
|
1227
|
+
}
|
|
1228
|
+
break;
|
|
1229
|
+
case "update":
|
|
1230
|
+
if (handlers.onUpdate && event.document) {
|
|
1231
|
+
handlers.onUpdate(event.document);
|
|
1232
|
+
}
|
|
1233
|
+
break;
|
|
1234
|
+
case "delete":
|
|
1235
|
+
if (handlers.onDelete) {
|
|
1236
|
+
handlers.onDelete(event.documentId);
|
|
1237
|
+
}
|
|
1238
|
+
break;
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
this.realtime.subscribe(this.projectId, this.collectionName, callback, handlers.onError, handlers.where).then((unsub) => {
|
|
1242
|
+
if (pendingUnsub) {
|
|
1243
|
+
unsub();
|
|
1244
|
+
} else {
|
|
1245
|
+
unsubscribeFn = unsub;
|
|
1246
|
+
}
|
|
1247
|
+
}).catch((err) => {
|
|
1248
|
+
if (handlers.onError) {
|
|
1249
|
+
handlers.onError(err instanceof Error ? err : new Error(String(err)));
|
|
1250
|
+
}
|
|
1251
|
+
});
|
|
1252
|
+
return () => {
|
|
1253
|
+
if (unsubscribeFn) {
|
|
1254
|
+
unsubscribeFn();
|
|
1255
|
+
} else {
|
|
1256
|
+
pendingUnsub = true;
|
|
1257
|
+
}
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
};
|
|
1261
|
+
var DatabaseModule = class {
|
|
1262
|
+
constructor(http, projectId, realtime) {
|
|
1263
|
+
this.http = http;
|
|
1264
|
+
this.projectId = projectId;
|
|
1265
|
+
this.realtime = realtime ?? null;
|
|
1266
|
+
}
|
|
1267
|
+
collection(name) {
|
|
1268
|
+
return new CollectionRef(this.http, this.realtime, this.projectId, name);
|
|
1269
|
+
}
|
|
1270
|
+
};
|
|
1271
|
+
|
|
1272
|
+
// libs/sdk/src/modules/notifications.module.ts
|
|
1273
|
+
var DEVICE_ID_KEY = "spacelr_device_id";
|
|
1274
|
+
var NotificationsModule = class {
|
|
1275
|
+
constructor(http) {
|
|
1276
|
+
this.http = http;
|
|
1277
|
+
this.customDeviceId = null;
|
|
1278
|
+
this.customDeviceName = null;
|
|
1279
|
+
}
|
|
1280
|
+
/** Set a custom device ID (e.g. from Capacitor Preferences for persistence beyond localStorage) */
|
|
1281
|
+
setDeviceId(id) {
|
|
1282
|
+
this.customDeviceId = id;
|
|
1283
|
+
}
|
|
1284
|
+
/** Set a custom device name (e.g. "iOS App", "macOS - Chrome") */
|
|
1285
|
+
setDeviceName(name) {
|
|
1286
|
+
this.customDeviceName = name;
|
|
1287
|
+
}
|
|
1288
|
+
/** Get or generate a stable device identifier. Custom ID takes priority over localStorage. */
|
|
1289
|
+
getDeviceId() {
|
|
1290
|
+
if (this.customDeviceId) return this.customDeviceId;
|
|
1291
|
+
if (typeof localStorage === "undefined") return void 0;
|
|
1292
|
+
let id = localStorage.getItem(DEVICE_ID_KEY);
|
|
1293
|
+
if (!id) {
|
|
1294
|
+
id = crypto.randomUUID();
|
|
1295
|
+
localStorage.setItem(DEVICE_ID_KEY, id);
|
|
1296
|
+
}
|
|
1297
|
+
return id;
|
|
1298
|
+
}
|
|
1299
|
+
/** Get device name: custom name > auto-detected from user agent > undefined */
|
|
1300
|
+
getDeviceName() {
|
|
1301
|
+
if (this.customDeviceName) return this.customDeviceName;
|
|
1302
|
+
return this.detectDeviceName();
|
|
1303
|
+
}
|
|
1304
|
+
/** Auto-detect a short device label from navigator.userAgent (e.g. "macOS - Chrome") */
|
|
1305
|
+
detectDeviceName() {
|
|
1306
|
+
if (typeof navigator === "undefined" || !navigator.userAgent) return void 0;
|
|
1307
|
+
const ua = navigator.userAgent;
|
|
1308
|
+
let os = "Unknown";
|
|
1309
|
+
if (/iPad|iPhone|iPod/.test(ua)) os = "iOS";
|
|
1310
|
+
else if (/Android/.test(ua)) os = "Android";
|
|
1311
|
+
else if (/Mac OS X/.test(ua)) os = "macOS";
|
|
1312
|
+
else if (/Windows/.test(ua)) os = "Windows";
|
|
1313
|
+
else if (/Linux/.test(ua)) os = "Linux";
|
|
1314
|
+
let browser = "Unknown";
|
|
1315
|
+
if (/Edg\//.test(ua)) browser = "Edge";
|
|
1316
|
+
else if (/OPR\/|Opera/.test(ua)) browser = "Opera";
|
|
1317
|
+
else if (/Chrome\//.test(ua)) browser = "Chrome";
|
|
1318
|
+
else if (/Safari\//.test(ua) && !/Chrome/.test(ua)) browser = "Safari";
|
|
1319
|
+
else if (/Firefox\//.test(ua)) browser = "Firefox";
|
|
1320
|
+
return `${os} - ${browser}`;
|
|
1321
|
+
}
|
|
1322
|
+
/** Get the VAPID public key for Web Push setup */
|
|
1323
|
+
async getVapidPublicKey() {
|
|
1324
|
+
return this.http.request({
|
|
1325
|
+
method: "GET",
|
|
1326
|
+
path: "/notifications/vapid-key",
|
|
1327
|
+
authenticated: true
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
/** Register a push subscription (web, android, or ios) */
|
|
1331
|
+
async subscribe(subscription, deviceName) {
|
|
1332
|
+
return this.http.request({
|
|
1333
|
+
method: "POST",
|
|
1334
|
+
path: "/notifications/subscribe",
|
|
1335
|
+
body: {
|
|
1336
|
+
...subscription,
|
|
1337
|
+
deviceName: deviceName ?? this.getDeviceName(),
|
|
1338
|
+
deviceId: this.getDeviceId()
|
|
1339
|
+
},
|
|
1340
|
+
authenticated: true
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
/** Unregister a push subscription */
|
|
1344
|
+
async unsubscribe(platform, identifier) {
|
|
1345
|
+
const body = { platform };
|
|
1346
|
+
if (platform === "web") {
|
|
1347
|
+
body["endpoint"] = identifier;
|
|
1348
|
+
} else {
|
|
1349
|
+
body["deviceToken"] = identifier;
|
|
1350
|
+
}
|
|
1351
|
+
return this.http.request({
|
|
1352
|
+
method: "DELETE",
|
|
1353
|
+
path: "/notifications/subscribe",
|
|
1354
|
+
body,
|
|
1355
|
+
authenticated: true
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1358
|
+
/** Get all subscriptions for the current user */
|
|
1359
|
+
async getSubscriptions() {
|
|
1360
|
+
return this.http.request({
|
|
1361
|
+
method: "GET",
|
|
1362
|
+
path: "/notifications/subscriptions",
|
|
1363
|
+
authenticated: true
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Helper: Register browser Web Push subscription.
|
|
1368
|
+
* Requests notification permission, subscribes via Push API,
|
|
1369
|
+
* and registers the subscription with the server.
|
|
1370
|
+
*/
|
|
1371
|
+
async registerBrowserPush(serviceWorkerRegistration, deviceName) {
|
|
1372
|
+
const { publicKey } = await this.getVapidPublicKey();
|
|
1373
|
+
if (!publicKey) {
|
|
1374
|
+
throw new Error(
|
|
1375
|
+
"VAPID public key not configured on the server"
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
const permission = await Notification.requestPermission();
|
|
1379
|
+
if (permission !== "granted") {
|
|
1380
|
+
throw new Error(
|
|
1381
|
+
`Notification permission denied: ${permission}`
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
const applicationServerKey = this.urlBase64ToUint8Array(publicKey);
|
|
1385
|
+
const pushSubscription = await serviceWorkerRegistration.pushManager.subscribe({
|
|
1386
|
+
userVisibleOnly: true,
|
|
1387
|
+
applicationServerKey
|
|
1388
|
+
});
|
|
1389
|
+
const subJson = pushSubscription.toJSON();
|
|
1390
|
+
const p256dh = subJson.keys?.["p256dh"];
|
|
1391
|
+
const auth = subJson.keys?.["auth"];
|
|
1392
|
+
if (!subJson.endpoint || !p256dh || !auth) {
|
|
1393
|
+
throw new Error("Invalid push subscription: missing endpoint or keys");
|
|
1394
|
+
}
|
|
1395
|
+
return this.subscribe(
|
|
1396
|
+
{
|
|
1397
|
+
platform: "web",
|
|
1398
|
+
endpoint: subJson.endpoint,
|
|
1399
|
+
keys: { p256dh, auth }
|
|
1400
|
+
},
|
|
1401
|
+
deviceName
|
|
1402
|
+
);
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Helper: Register a mobile device push token (FCM or APNs).
|
|
1406
|
+
*/
|
|
1407
|
+
async registerDevicePush(deviceToken, platform, deviceName) {
|
|
1408
|
+
return this.subscribe(
|
|
1409
|
+
{
|
|
1410
|
+
platform,
|
|
1411
|
+
deviceToken
|
|
1412
|
+
},
|
|
1413
|
+
deviceName
|
|
1414
|
+
);
|
|
1415
|
+
}
|
|
1416
|
+
urlBase64ToUint8Array(base64String) {
|
|
1417
|
+
const padding = "=".repeat((4 - base64String.length % 4) % 4);
|
|
1418
|
+
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
|
|
1419
|
+
const rawData = atob(base64);
|
|
1420
|
+
const outputArray = new Uint8Array(rawData.length);
|
|
1421
|
+
for (let i = 0; i < rawData.length; ++i) {
|
|
1422
|
+
outputArray[i] = rawData.charCodeAt(i);
|
|
1423
|
+
}
|
|
1424
|
+
return outputArray.buffer;
|
|
1425
|
+
}
|
|
1426
|
+
};
|
|
1427
|
+
|
|
1428
|
+
// libs/sdk/src/client.ts
|
|
1429
|
+
function createClient(config) {
|
|
1430
|
+
const tokenStorage = config.tokenStorage ?? (typeof window !== "undefined" && typeof window.localStorage !== "undefined" ? new BrowserTokenStorage() : new MemoryTokenStorage());
|
|
1431
|
+
const tokenManager = new TokenManager(
|
|
1432
|
+
tokenStorage,
|
|
1433
|
+
config.refreshBufferSeconds ?? 60
|
|
1434
|
+
);
|
|
1435
|
+
const httpClient = new HttpClient(config, tokenManager);
|
|
1436
|
+
const realtime = new RealtimeClient({
|
|
1437
|
+
baseUrl: config.apiUrl,
|
|
1438
|
+
getToken: () => tokenManager.getAccessToken()
|
|
1439
|
+
});
|
|
1440
|
+
const auth = new AuthModule(httpClient, tokenManager, config);
|
|
1441
|
+
const storage = new StorageModule(httpClient, tokenManager, config);
|
|
1442
|
+
const db = new DatabaseModule(httpClient, config.projectId, realtime);
|
|
1443
|
+
const notifications = new NotificationsModule(httpClient);
|
|
1444
|
+
return {
|
|
1445
|
+
auth,
|
|
1446
|
+
storage,
|
|
1447
|
+
db,
|
|
1448
|
+
notifications,
|
|
1449
|
+
disconnect() {
|
|
1450
|
+
realtime.disconnect();
|
|
1451
|
+
}
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1455
|
+
0 && (module.exports = {
|
|
1456
|
+
BrowserTokenStorage,
|
|
1457
|
+
CodeChallengeMethod,
|
|
1458
|
+
FileVisibility,
|
|
1459
|
+
GrantType,
|
|
1460
|
+
MemoryTokenStorage,
|
|
1461
|
+
SharePermission,
|
|
1462
|
+
SpacelrAuthError,
|
|
1463
|
+
SpacelrEmailVerificationRequiredError,
|
|
1464
|
+
SpacelrError,
|
|
1465
|
+
SpacelrNetworkError,
|
|
1466
|
+
SpacelrTimeoutError,
|
|
1467
|
+
SpacelrTwoFactorRequiredError,
|
|
1468
|
+
createClient,
|
|
1469
|
+
generatePKCEChallenge
|
|
1470
|
+
});
|
|
1471
|
+
//# sourceMappingURL=index.js.map
|