@spacelr/sdk 0.1.4 → 0.1.6
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 +135 -1
- package/dist/index.d.ts +135 -1
- package/dist/index.js +302 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +302 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -70,6 +70,9 @@ var HttpClient = class {
|
|
|
70
70
|
this.tokenManager = tokenManager;
|
|
71
71
|
}
|
|
72
72
|
async request(options) {
|
|
73
|
+
return this.requestWithRetry(options, false);
|
|
74
|
+
}
|
|
75
|
+
async requestWithRetry(options, isRetry) {
|
|
73
76
|
const url = this.buildUrl(options.path, options.query);
|
|
74
77
|
const headers = await this.buildHeaders(options);
|
|
75
78
|
const timeout = this.config.timeout ?? 3e4;
|
|
@@ -86,6 +89,11 @@ var HttpClient = class {
|
|
|
86
89
|
});
|
|
87
90
|
const responseBody = await this.parseResponse(response);
|
|
88
91
|
if (!response.ok) {
|
|
92
|
+
if (options.authenticated && response.status === 401) {
|
|
93
|
+
if (await this.recoverFromAuthFailure(isRetry)) {
|
|
94
|
+
return this.requestWithRetry(options, true);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
89
97
|
this.throwHttpError(response.status, responseBody);
|
|
90
98
|
}
|
|
91
99
|
return this.extractData(responseBody);
|
|
@@ -102,12 +110,45 @@ var HttpClient = class {
|
|
|
102
110
|
}
|
|
103
111
|
}
|
|
104
112
|
async uploadForm(path, formData, onProgress) {
|
|
113
|
+
return this.uploadFormWithRetry(path, formData, onProgress, false);
|
|
114
|
+
}
|
|
115
|
+
async uploadFormWithRetry(path, formData, onProgress, isRetry) {
|
|
105
116
|
const url = this.buildUrl(path);
|
|
106
117
|
const headers = await this.buildFormHeaders();
|
|
107
118
|
const timeoutMs = this.config.timeout ?? 3e4;
|
|
108
|
-
|
|
109
|
-
|
|
119
|
+
try {
|
|
120
|
+
if (onProgress) {
|
|
121
|
+
return await this.uploadFormWithProgress(url, headers, formData, onProgress, timeoutMs);
|
|
122
|
+
}
|
|
123
|
+
return await this.uploadFormOnce(url, headers, formData, timeoutMs);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
if (error instanceof SpacelrAuthError && error.statusCode === 401) {
|
|
126
|
+
if (await this.recoverFromAuthFailure(isRetry)) {
|
|
127
|
+
return this.uploadFormWithRetry(path, formData, onProgress, true);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
throw error;
|
|
110
131
|
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Shared handler for a 401 on an authenticated request. Returns true if the
|
|
135
|
+
* caller should retry with a refreshed token. 403 is never routed here —
|
|
136
|
+
* it means "forbidden for this action" and must not trigger logout.
|
|
137
|
+
*
|
|
138
|
+
* `emitAuthLost('unauthorized')` is deduped inside TokenManager, so it's a
|
|
139
|
+
* no-op if `executeRefresh` already emitted 'refresh-failed'.
|
|
140
|
+
*/
|
|
141
|
+
async recoverFromAuthFailure(isRetry) {
|
|
142
|
+
if (isRetry) {
|
|
143
|
+
this.tokenManager.emitAuthLost("unauthorized");
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
const refreshed = await this.tokenManager.forceRefresh();
|
|
147
|
+
if (refreshed) return true;
|
|
148
|
+
this.tokenManager.emitAuthLost("unauthorized");
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
async uploadFormOnce(url, headers, formData, timeoutMs) {
|
|
111
152
|
const controller = new AbortController();
|
|
112
153
|
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
113
154
|
try {
|
|
@@ -153,6 +194,8 @@ var HttpClient = class {
|
|
|
153
194
|
if (!contentType.includes("application/json")) {
|
|
154
195
|
if (xhr.status >= 200 && xhr.status < 300) {
|
|
155
196
|
resolve({ success: true, data: xhr.responseText });
|
|
197
|
+
} else if (xhr.status === 401 || xhr.status === 403) {
|
|
198
|
+
reject(new SpacelrAuthError(`HTTP ${xhr.status}`, xhr.status));
|
|
156
199
|
} else {
|
|
157
200
|
reject(new SpacelrNetworkError(`HTTP ${xhr.status}: ${xhr.responseText.slice(0, 200)}`));
|
|
158
201
|
}
|
|
@@ -309,6 +352,12 @@ var TokenManager = class {
|
|
|
309
352
|
constructor(storage, refreshBufferSeconds = 60) {
|
|
310
353
|
this.refreshCallback = null;
|
|
311
354
|
this.refreshPromise = null;
|
|
355
|
+
this.tokenRefreshedListeners = /* @__PURE__ */ new Set();
|
|
356
|
+
this.authLostListeners = /* @__PURE__ */ new Set();
|
|
357
|
+
// Guard so callbacks that run during auth-loss handling (e.g. a logout()
|
|
358
|
+
// that hits `/auth/logout` with a dead token) don't re-emit and loop.
|
|
359
|
+
// Cleared by setTokens() / clearTokens() to re-arm for the next session.
|
|
360
|
+
this.authLostEmitted = false;
|
|
312
361
|
this.storage = storage ?? new MemoryTokenStorage();
|
|
313
362
|
this.refreshBufferSeconds = refreshBufferSeconds;
|
|
314
363
|
}
|
|
@@ -330,14 +379,53 @@ var TokenManager = class {
|
|
|
330
379
|
}
|
|
331
380
|
async setTokens(tokens) {
|
|
332
381
|
await this.storage.setTokens(tokens);
|
|
382
|
+
this.authLostEmitted = false;
|
|
333
383
|
}
|
|
334
384
|
async clearTokens() {
|
|
335
385
|
await this.storage.clearTokens();
|
|
336
386
|
this.refreshPromise = null;
|
|
387
|
+
this.authLostEmitted = false;
|
|
337
388
|
}
|
|
338
389
|
async getStoredTokens() {
|
|
339
390
|
return this.storage.getTokens();
|
|
340
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* Force a refresh using the current stored refresh token.
|
|
394
|
+
* Returns the new access token, or null if refresh is not possible or fails.
|
|
395
|
+
* A rejecting custom `TokenStorage.getTokens()` is treated as "no tokens"
|
|
396
|
+
* so callers (notably `HttpClient`'s 401 retry path) see a clean `null`
|
|
397
|
+
* rather than an exception that would get wrapped as a network error.
|
|
398
|
+
*/
|
|
399
|
+
async forceRefresh() {
|
|
400
|
+
if (this.authLostEmitted) return null;
|
|
401
|
+
let tokens;
|
|
402
|
+
try {
|
|
403
|
+
tokens = await this.storage.getTokens();
|
|
404
|
+
} catch {
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
if (!tokens) return null;
|
|
408
|
+
const refreshed = await this.tryRefresh(tokens);
|
|
409
|
+
return refreshed?.accessToken ?? null;
|
|
410
|
+
}
|
|
411
|
+
onTokenRefreshed(listener) {
|
|
412
|
+
this.tokenRefreshedListeners.add(listener);
|
|
413
|
+
return () => this.tokenRefreshedListeners.delete(listener);
|
|
414
|
+
}
|
|
415
|
+
onAuthLost(listener) {
|
|
416
|
+
this.authLostListeners.add(listener);
|
|
417
|
+
return () => this.authLostListeners.delete(listener);
|
|
418
|
+
}
|
|
419
|
+
emitAuthLost(reason) {
|
|
420
|
+
if (this.authLostEmitted) return;
|
|
421
|
+
this.authLostEmitted = true;
|
|
422
|
+
for (const listener of this.authLostListeners) {
|
|
423
|
+
try {
|
|
424
|
+
listener(reason);
|
|
425
|
+
} catch {
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
341
429
|
isTokenExpired(tokens) {
|
|
342
430
|
if (!tokens.expiresAt) return false;
|
|
343
431
|
return Date.now() >= tokens.expiresAt * 1e3;
|
|
@@ -350,21 +438,40 @@ var TokenManager = class {
|
|
|
350
438
|
async tryRefresh(tokens) {
|
|
351
439
|
if (!tokens.refreshToken || !this.refreshCallback) return null;
|
|
352
440
|
if (this.refreshPromise) {
|
|
353
|
-
|
|
441
|
+
try {
|
|
442
|
+
return await this.refreshPromise;
|
|
443
|
+
} catch {
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
354
446
|
}
|
|
355
447
|
this.refreshPromise = this.executeRefresh(tokens.refreshToken);
|
|
356
448
|
try {
|
|
357
|
-
|
|
358
|
-
|
|
449
|
+
return await this.refreshPromise;
|
|
450
|
+
} catch {
|
|
451
|
+
return null;
|
|
359
452
|
} finally {
|
|
360
453
|
this.refreshPromise = null;
|
|
361
454
|
}
|
|
362
455
|
}
|
|
363
456
|
async executeRefresh(refreshToken) {
|
|
364
457
|
const callback = this.refreshCallback;
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
458
|
+
try {
|
|
459
|
+
const newTokens = await callback(refreshToken);
|
|
460
|
+
await this.storage.setTokens(newTokens);
|
|
461
|
+
this.emitTokenRefreshed(newTokens);
|
|
462
|
+
return newTokens;
|
|
463
|
+
} catch (error) {
|
|
464
|
+
this.emitAuthLost("refresh-failed");
|
|
465
|
+
throw error;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
emitTokenRefreshed(tokens) {
|
|
469
|
+
for (const listener of this.tokenRefreshedListeners) {
|
|
470
|
+
try {
|
|
471
|
+
listener(tokens);
|
|
472
|
+
} catch {
|
|
473
|
+
}
|
|
474
|
+
}
|
|
368
475
|
}
|
|
369
476
|
};
|
|
370
477
|
|
|
@@ -435,6 +542,7 @@ async function generatePKCEChallenge() {
|
|
|
435
542
|
|
|
436
543
|
// libs/sdk/src/core/realtime.ts
|
|
437
544
|
import { io } from "socket.io-client";
|
|
545
|
+
var REBUILD_RETRY_DELAY_MS = 5e3;
|
|
438
546
|
var RealtimeClient = class {
|
|
439
547
|
constructor(config) {
|
|
440
548
|
this.socket = null;
|
|
@@ -442,9 +550,26 @@ var RealtimeClient = class {
|
|
|
442
550
|
this.connecting = null;
|
|
443
551
|
// Store original where objects per room for reconnect resubscription
|
|
444
552
|
this.roomWhereMap = /* @__PURE__ */ new Map();
|
|
553
|
+
this.unsubscribeFromTokenRefreshed = null;
|
|
554
|
+
// Wake-up listeners (browser only) — recover from long OS suspends where
|
|
555
|
+
// socket.io's internal reconnect loop may have already given up.
|
|
556
|
+
this.onVisibilityChange = null;
|
|
557
|
+
this.onOnline = null;
|
|
558
|
+
// Set by `disconnect()`; checked by async paths (rebuildSocket, deferred
|
|
559
|
+
// retries) to avoid reviving a client the consumer has torn down.
|
|
560
|
+
this.disposed = false;
|
|
561
|
+
// Scheduled retry after a failed `rebuildSocket()`; cleared when it fires
|
|
562
|
+
// or when `disconnect()` tears down.
|
|
563
|
+
this.rebuildRetryTimer = null;
|
|
564
|
+
this.connectionState = "disconnected";
|
|
565
|
+
this.connectionStateListeners = /* @__PURE__ */ new Set();
|
|
445
566
|
this.config = config;
|
|
446
567
|
}
|
|
447
568
|
async subscribe(projectId, collectionName, callback, onError, where) {
|
|
569
|
+
this.ensureWakeListeners();
|
|
570
|
+
if (this.connectionState === "disconnected") {
|
|
571
|
+
this.setConnectionState("reconnecting");
|
|
572
|
+
}
|
|
448
573
|
await this.ensureConnected();
|
|
449
574
|
const room = this.buildRoomKey(projectId, collectionName, where);
|
|
450
575
|
if (!this.subscriptions.has(room)) {
|
|
@@ -486,7 +611,24 @@ var RealtimeClient = class {
|
|
|
486
611
|
}
|
|
487
612
|
};
|
|
488
613
|
}
|
|
614
|
+
/**
|
|
615
|
+
* Tear down the realtime client permanently. After this call the instance
|
|
616
|
+
* is disposed — subsequent `subscribe()` calls will not re-establish a
|
|
617
|
+
* connection. Create a new `RealtimeClient` (via `createClient`) if you
|
|
618
|
+
* need to reconnect after a logout.
|
|
619
|
+
*/
|
|
489
620
|
disconnect() {
|
|
621
|
+
this.setConnectionState("disconnected");
|
|
622
|
+
this.disposed = true;
|
|
623
|
+
if (this.rebuildRetryTimer) {
|
|
624
|
+
clearTimeout(this.rebuildRetryTimer);
|
|
625
|
+
this.rebuildRetryTimer = null;
|
|
626
|
+
}
|
|
627
|
+
if (this.unsubscribeFromTokenRefreshed) {
|
|
628
|
+
this.unsubscribeFromTokenRefreshed();
|
|
629
|
+
this.unsubscribeFromTokenRefreshed = null;
|
|
630
|
+
}
|
|
631
|
+
this.detachWakeListeners();
|
|
490
632
|
if (this.socket) {
|
|
491
633
|
this.socket.disconnect();
|
|
492
634
|
this.socket = null;
|
|
@@ -495,6 +637,23 @@ var RealtimeClient = class {
|
|
|
495
637
|
this.roomWhereMap.clear();
|
|
496
638
|
this.connecting = null;
|
|
497
639
|
}
|
|
640
|
+
getConnectionState() {
|
|
641
|
+
return this.connectionState;
|
|
642
|
+
}
|
|
643
|
+
onConnectionStateChanged(listener) {
|
|
644
|
+
this.connectionStateListeners.add(listener);
|
|
645
|
+
return () => this.connectionStateListeners.delete(listener);
|
|
646
|
+
}
|
|
647
|
+
setConnectionState(next) {
|
|
648
|
+
if (this.connectionState === next) return;
|
|
649
|
+
this.connectionState = next;
|
|
650
|
+
for (const listener of this.connectionStateListeners) {
|
|
651
|
+
try {
|
|
652
|
+
listener(next);
|
|
653
|
+
} catch {
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
498
657
|
buildRoomKey(projectId, collectionName, where) {
|
|
499
658
|
const base = `db:${projectId}:${collectionName}`;
|
|
500
659
|
if (!where || Object.keys(where).length === 0) {
|
|
@@ -537,6 +696,8 @@ var RealtimeClient = class {
|
|
|
537
696
|
// Disabled until first successful connect
|
|
538
697
|
});
|
|
539
698
|
this.socket.on("authenticated", () => {
|
|
699
|
+
if (this.disposed) return;
|
|
700
|
+
this.setConnectionState("connected");
|
|
540
701
|
if (initialConnect) {
|
|
541
702
|
initialConnect = false;
|
|
542
703
|
if (this.socket) {
|
|
@@ -545,11 +706,15 @@ var RealtimeClient = class {
|
|
|
545
706
|
this.socket.io.opts.reconnectionDelayMax = 5e3;
|
|
546
707
|
this.socket.io.opts.reconnectionAttempts = 50;
|
|
547
708
|
}
|
|
709
|
+
if (this.config.onTokenRefreshed && !this.unsubscribeFromTokenRefreshed) {
|
|
710
|
+
this.unsubscribeFromTokenRefreshed = this.config.onTokenRefreshed(
|
|
711
|
+
(accessToken) => {
|
|
712
|
+
this.socket?.emit("reauthenticate", { token: accessToken });
|
|
713
|
+
}
|
|
714
|
+
);
|
|
715
|
+
}
|
|
548
716
|
resolve();
|
|
549
|
-
}
|
|
550
|
-
});
|
|
551
|
-
this.socket.on("connect", () => {
|
|
552
|
-
if (!initialConnect) {
|
|
717
|
+
} else {
|
|
553
718
|
this.resubscribeAll();
|
|
554
719
|
}
|
|
555
720
|
});
|
|
@@ -558,6 +723,9 @@ var RealtimeClient = class {
|
|
|
558
723
|
reject(new Error(`WebSocket connection failed: ${err.message}`));
|
|
559
724
|
}
|
|
560
725
|
});
|
|
726
|
+
this.socket.io.on("reconnect_failed", () => {
|
|
727
|
+
void this.rebuildSocket();
|
|
728
|
+
});
|
|
561
729
|
this.socket.on("db:event", (event) => {
|
|
562
730
|
const base = `db:${event.projectId}:${event.collectionName}`;
|
|
563
731
|
const baseCallbacks = this.subscriptions.get(base);
|
|
@@ -581,6 +749,9 @@ var RealtimeClient = class {
|
|
|
581
749
|
}
|
|
582
750
|
});
|
|
583
751
|
this.socket.on("disconnect", () => {
|
|
752
|
+
if (!this.disposed) {
|
|
753
|
+
this.setConnectionState("reconnecting");
|
|
754
|
+
}
|
|
584
755
|
});
|
|
585
756
|
});
|
|
586
757
|
}
|
|
@@ -603,6 +774,70 @@ var RealtimeClient = class {
|
|
|
603
774
|
}
|
|
604
775
|
return true;
|
|
605
776
|
}
|
|
777
|
+
/**
|
|
778
|
+
* Tear down the current socket and create a fresh one. Used by both
|
|
779
|
+
* `reconnect_failed` (socket.io gave up) and the visibility/online wake-up
|
|
780
|
+
* listeners. Preserves the `subscriptions` map so existing rooms can be
|
|
781
|
+
* replayed to the server on the new connection. If the rebuild itself
|
|
782
|
+
* fails it schedules one retry after 5 s — beyond that we rely on the
|
|
783
|
+
* next wake-up event (visibility/online) to try again.
|
|
784
|
+
*/
|
|
785
|
+
async rebuildSocket() {
|
|
786
|
+
if (this.disposed || this.connecting) return;
|
|
787
|
+
const previous = this.socket;
|
|
788
|
+
this.socket = null;
|
|
789
|
+
if (previous) previous.disconnect();
|
|
790
|
+
this.setConnectionState("reconnecting");
|
|
791
|
+
try {
|
|
792
|
+
await this.ensureConnected();
|
|
793
|
+
this.handlePostRebuild();
|
|
794
|
+
} catch {
|
|
795
|
+
this.scheduleRebuildRetry();
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
handlePostRebuild() {
|
|
799
|
+
const newSocket = this.socket;
|
|
800
|
+
if (this.disposed) {
|
|
801
|
+
this.socket = null;
|
|
802
|
+
newSocket?.disconnect();
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
if (this.subscriptions.size > 0) {
|
|
806
|
+
this.resubscribeAll();
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
scheduleRebuildRetry() {
|
|
810
|
+
if (this.disposed || this.rebuildRetryTimer) return;
|
|
811
|
+
this.rebuildRetryTimer = setTimeout(() => {
|
|
812
|
+
this.rebuildRetryTimer = null;
|
|
813
|
+
void this.rebuildSocket();
|
|
814
|
+
}, REBUILD_RETRY_DELAY_MS);
|
|
815
|
+
this.rebuildRetryTimer.unref?.();
|
|
816
|
+
}
|
|
817
|
+
ensureWakeListeners() {
|
|
818
|
+
if (typeof document === "undefined" || typeof window === "undefined") return;
|
|
819
|
+
if (this.onVisibilityChange !== null) return;
|
|
820
|
+
const wakeIfUnhealthy = () => {
|
|
821
|
+
if (typeof document !== "undefined" && document.visibilityState !== "visible") return;
|
|
822
|
+
if (this.socket?.connected) return;
|
|
823
|
+
if (this.connecting) return;
|
|
824
|
+
void this.rebuildSocket();
|
|
825
|
+
};
|
|
826
|
+
this.onVisibilityChange = wakeIfUnhealthy;
|
|
827
|
+
this.onOnline = wakeIfUnhealthy;
|
|
828
|
+
document.addEventListener("visibilitychange", this.onVisibilityChange);
|
|
829
|
+
window.addEventListener("online", this.onOnline);
|
|
830
|
+
}
|
|
831
|
+
detachWakeListeners() {
|
|
832
|
+
if (typeof document !== "undefined" && this.onVisibilityChange) {
|
|
833
|
+
document.removeEventListener("visibilitychange", this.onVisibilityChange);
|
|
834
|
+
}
|
|
835
|
+
if (typeof window !== "undefined" && this.onOnline) {
|
|
836
|
+
window.removeEventListener("online", this.onOnline);
|
|
837
|
+
}
|
|
838
|
+
this.onVisibilityChange = null;
|
|
839
|
+
this.onOnline = null;
|
|
840
|
+
}
|
|
606
841
|
resubscribeAll() {
|
|
607
842
|
for (const [room] of this.subscriptions) {
|
|
608
843
|
const queryIdx = room.indexOf("?");
|
|
@@ -682,11 +917,14 @@ var AuthModule = class {
|
|
|
682
917
|
});
|
|
683
918
|
}
|
|
684
919
|
async logout() {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
920
|
+
try {
|
|
921
|
+
await this.http.request({
|
|
922
|
+
method: "POST",
|
|
923
|
+
path: "/auth/logout",
|
|
924
|
+
authenticated: true
|
|
925
|
+
});
|
|
926
|
+
} catch {
|
|
927
|
+
}
|
|
690
928
|
await this.tokenManager.clearTokens();
|
|
691
929
|
}
|
|
692
930
|
async verifyEmail(token) {
|
|
@@ -1124,11 +1362,11 @@ var CollectionRef = class {
|
|
|
1124
1362
|
this.collectionName = collectionName;
|
|
1125
1363
|
this.basePath = `/db/${collectionName}`;
|
|
1126
1364
|
}
|
|
1127
|
-
async insert(
|
|
1365
|
+
async insert(document2) {
|
|
1128
1366
|
return this.http.request({
|
|
1129
1367
|
method: "POST",
|
|
1130
1368
|
path: this.basePath,
|
|
1131
|
-
body: { documents: [
|
|
1369
|
+
body: { documents: [document2] },
|
|
1132
1370
|
authenticated: true
|
|
1133
1371
|
});
|
|
1134
1372
|
}
|
|
@@ -1393,6 +1631,29 @@ var NotificationsModule = class {
|
|
|
1393
1631
|
}
|
|
1394
1632
|
};
|
|
1395
1633
|
|
|
1634
|
+
// libs/sdk/src/modules/functions.module.ts
|
|
1635
|
+
var FunctionsModule = class {
|
|
1636
|
+
constructor(http) {
|
|
1637
|
+
this.http = http;
|
|
1638
|
+
}
|
|
1639
|
+
/**
|
|
1640
|
+
* Invoke a function via webhook trigger.
|
|
1641
|
+
* Calls POST /api/v1/functions/:projectId/:functionId/invoke
|
|
1642
|
+
* with X-Webhook-Secret header.
|
|
1643
|
+
*/
|
|
1644
|
+
async invoke(projectId, functionId, options) {
|
|
1645
|
+
return this.http.request({
|
|
1646
|
+
method: "POST",
|
|
1647
|
+
path: `/api/v1/functions/${encodeURIComponent(projectId)}/${encodeURIComponent(functionId)}/invoke`,
|
|
1648
|
+
headers: {
|
|
1649
|
+
"X-Webhook-Secret": options.secret
|
|
1650
|
+
},
|
|
1651
|
+
body: options.payload ?? {},
|
|
1652
|
+
authenticated: false
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
};
|
|
1656
|
+
|
|
1396
1657
|
// libs/sdk/src/client.ts
|
|
1397
1658
|
function createClient(config) {
|
|
1398
1659
|
const tokenStorage = config.tokenStorage ?? (typeof window !== "undefined" && typeof window.localStorage !== "undefined" ? new BrowserTokenStorage() : new MemoryTokenStorage());
|
|
@@ -1403,19 +1664,40 @@ function createClient(config) {
|
|
|
1403
1664
|
const httpClient = new HttpClient(config, tokenManager);
|
|
1404
1665
|
const realtime = new RealtimeClient({
|
|
1405
1666
|
baseUrl: config.apiUrl,
|
|
1406
|
-
getToken: () => tokenManager.getAccessToken()
|
|
1667
|
+
getToken: () => tokenManager.getAccessToken(),
|
|
1668
|
+
onTokenRefreshed: (listener) => tokenManager.onTokenRefreshed((tokens) => listener(tokens.accessToken))
|
|
1407
1669
|
});
|
|
1408
1670
|
const auth = new AuthModule(httpClient, tokenManager, config);
|
|
1409
1671
|
const storage = new StorageModule(httpClient, tokenManager, config);
|
|
1410
1672
|
const db = new DatabaseModule(httpClient, config.projectId, realtime);
|
|
1411
1673
|
const notifications = new NotificationsModule(httpClient);
|
|
1674
|
+
const functions = new FunctionsModule(httpClient);
|
|
1412
1675
|
return {
|
|
1413
1676
|
auth,
|
|
1414
1677
|
storage,
|
|
1415
1678
|
db,
|
|
1416
1679
|
notifications,
|
|
1680
|
+
functions,
|
|
1681
|
+
setTokens(tokens) {
|
|
1682
|
+
return tokenManager.setTokens(tokens);
|
|
1683
|
+
},
|
|
1684
|
+
clearTokens() {
|
|
1685
|
+
return tokenManager.clearTokens();
|
|
1686
|
+
},
|
|
1417
1687
|
disconnect() {
|
|
1418
1688
|
realtime.disconnect();
|
|
1689
|
+
},
|
|
1690
|
+
onAuthLost(listener) {
|
|
1691
|
+
return tokenManager.onAuthLost(listener);
|
|
1692
|
+
},
|
|
1693
|
+
onTokenRefreshed(listener) {
|
|
1694
|
+
return tokenManager.onTokenRefreshed(listener);
|
|
1695
|
+
},
|
|
1696
|
+
onConnectionStateChanged(listener) {
|
|
1697
|
+
return realtime.onConnectionStateChanged(listener);
|
|
1698
|
+
},
|
|
1699
|
+
getConnectionState() {
|
|
1700
|
+
return realtime.getConnectionState();
|
|
1419
1701
|
}
|
|
1420
1702
|
};
|
|
1421
1703
|
}
|