@fluxbase/sdk 0.0.1-rc.26 → 0.0.1-rc.28
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 +390 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +264 -35
- package/dist/index.d.ts +264 -35
- package/dist/index.js +390 -67
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -163,16 +163,19 @@ var FluxbaseAuth = class {
|
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
/**
|
|
166
|
-
* Get the current session
|
|
166
|
+
* Get the current session (Supabase-compatible)
|
|
167
|
+
* Returns the session from the client-side cache without making a network request
|
|
167
168
|
*/
|
|
168
|
-
getSession() {
|
|
169
|
-
return this.session;
|
|
169
|
+
async getSession() {
|
|
170
|
+
return { data: { session: this.session }, error: null };
|
|
170
171
|
}
|
|
171
172
|
/**
|
|
172
|
-
* Get the current user
|
|
173
|
+
* Get the current user (Supabase-compatible)
|
|
174
|
+
* Returns the user from the client-side session without making a network request
|
|
175
|
+
* For server-side validation, use getCurrentUser() instead
|
|
173
176
|
*/
|
|
174
|
-
getUser() {
|
|
175
|
-
return this.session?.user ?? null;
|
|
177
|
+
async getUser() {
|
|
178
|
+
return { data: { user: this.session?.user ?? null }, error: null };
|
|
176
179
|
}
|
|
177
180
|
/**
|
|
178
181
|
* Get the current access token
|
|
@@ -181,9 +184,9 @@ var FluxbaseAuth = class {
|
|
|
181
184
|
return this.session?.access_token ?? null;
|
|
182
185
|
}
|
|
183
186
|
/**
|
|
184
|
-
* Listen to auth state changes
|
|
187
|
+
* Listen to auth state changes (Supabase-compatible)
|
|
185
188
|
* @param callback - Function called when auth state changes
|
|
186
|
-
* @returns
|
|
189
|
+
* @returns Object containing subscription data
|
|
187
190
|
*
|
|
188
191
|
* @example
|
|
189
192
|
* ```typescript
|
|
@@ -197,11 +200,12 @@ var FluxbaseAuth = class {
|
|
|
197
200
|
*/
|
|
198
201
|
onAuthStateChange(callback) {
|
|
199
202
|
this.stateChangeListeners.add(callback);
|
|
200
|
-
|
|
203
|
+
const subscription = {
|
|
201
204
|
unsubscribe: () => {
|
|
202
205
|
this.stateChangeListeners.delete(callback);
|
|
203
206
|
}
|
|
204
207
|
};
|
|
208
|
+
return { data: { subscription } };
|
|
205
209
|
}
|
|
206
210
|
/**
|
|
207
211
|
* Sign in with email and password
|
|
@@ -218,7 +222,7 @@ var FluxbaseAuth = class {
|
|
|
218
222
|
...authResponse,
|
|
219
223
|
expires_at: Date.now() + authResponse.expires_in * 1e3
|
|
220
224
|
};
|
|
221
|
-
this.
|
|
225
|
+
this.setSessionInternal(session);
|
|
222
226
|
return session;
|
|
223
227
|
});
|
|
224
228
|
}
|
|
@@ -243,7 +247,7 @@ var FluxbaseAuth = class {
|
|
|
243
247
|
...response,
|
|
244
248
|
expires_at: Date.now() + response.expires_in * 1e3
|
|
245
249
|
};
|
|
246
|
-
this.
|
|
250
|
+
this.setSessionInternal(session);
|
|
247
251
|
return { user: session.user, session };
|
|
248
252
|
});
|
|
249
253
|
}
|
|
@@ -260,9 +264,10 @@ var FluxbaseAuth = class {
|
|
|
260
264
|
});
|
|
261
265
|
}
|
|
262
266
|
/**
|
|
263
|
-
* Refresh the
|
|
267
|
+
* Refresh the session (Supabase-compatible)
|
|
268
|
+
* Returns a new session with refreshed tokens
|
|
264
269
|
*/
|
|
265
|
-
async
|
|
270
|
+
async refreshSession() {
|
|
266
271
|
return wrapAsync(async () => {
|
|
267
272
|
if (!this.session?.refresh_token) {
|
|
268
273
|
throw new Error("No refresh token available");
|
|
@@ -277,8 +282,8 @@ var FluxbaseAuth = class {
|
|
|
277
282
|
...response,
|
|
278
283
|
expires_at: Date.now() + response.expires_in * 1e3
|
|
279
284
|
};
|
|
280
|
-
this.
|
|
281
|
-
return { session };
|
|
285
|
+
this.setSessionInternal(session, "TOKEN_REFRESHED");
|
|
286
|
+
return { session, user: session.user };
|
|
282
287
|
});
|
|
283
288
|
}
|
|
284
289
|
/**
|
|
@@ -311,10 +316,28 @@ var FluxbaseAuth = class {
|
|
|
311
316
|
});
|
|
312
317
|
}
|
|
313
318
|
/**
|
|
314
|
-
* Set the
|
|
319
|
+
* Set the session manually (Supabase-compatible)
|
|
320
|
+
* Useful for restoring a session from storage or SSR scenarios
|
|
321
|
+
* @param session - Object containing access_token and refresh_token
|
|
322
|
+
* @returns Promise with session data
|
|
315
323
|
*/
|
|
316
|
-
|
|
317
|
-
|
|
324
|
+
async setSession(session) {
|
|
325
|
+
return wrapAsync(async () => {
|
|
326
|
+
const authSession = {
|
|
327
|
+
access_token: session.access_token,
|
|
328
|
+
refresh_token: session.refresh_token,
|
|
329
|
+
user: null,
|
|
330
|
+
// Will be populated by getCurrentUser
|
|
331
|
+
expires_in: 3600,
|
|
332
|
+
// Default, will be updated on refresh
|
|
333
|
+
expires_at: Date.now() + 3600 * 1e3
|
|
334
|
+
};
|
|
335
|
+
this.fetch.setAuthToken(session.access_token);
|
|
336
|
+
const user = await this.fetch.get("/api/v1/auth/user");
|
|
337
|
+
authSession.user = user;
|
|
338
|
+
this.setSessionInternal(authSession, "SIGNED_IN");
|
|
339
|
+
return { user, session: authSession };
|
|
340
|
+
});
|
|
318
341
|
}
|
|
319
342
|
/**
|
|
320
343
|
* Setup 2FA for the current user
|
|
@@ -387,7 +410,7 @@ var FluxbaseAuth = class {
|
|
|
387
410
|
...response,
|
|
388
411
|
expires_at: Date.now() + response.expires_in * 1e3
|
|
389
412
|
};
|
|
390
|
-
this.
|
|
413
|
+
this.setSessionInternal(session, "MFA_CHALLENGE_VERIFIED");
|
|
391
414
|
return { user: session.user, session };
|
|
392
415
|
});
|
|
393
416
|
}
|
|
@@ -476,7 +499,7 @@ var FluxbaseAuth = class {
|
|
|
476
499
|
...response,
|
|
477
500
|
expires_at: Date.now() + response.expires_in * 1e3
|
|
478
501
|
};
|
|
479
|
-
this.
|
|
502
|
+
this.setSessionInternal(session);
|
|
480
503
|
return { user: session.user, session };
|
|
481
504
|
});
|
|
482
505
|
}
|
|
@@ -493,7 +516,7 @@ var FluxbaseAuth = class {
|
|
|
493
516
|
...response,
|
|
494
517
|
expires_at: Date.now() + response.expires_in * 1e3
|
|
495
518
|
};
|
|
496
|
-
this.
|
|
519
|
+
this.setSessionInternal(session);
|
|
497
520
|
return { user: session.user, session };
|
|
498
521
|
});
|
|
499
522
|
}
|
|
@@ -542,7 +565,7 @@ var FluxbaseAuth = class {
|
|
|
542
565
|
...response,
|
|
543
566
|
expires_at: Date.now() + response.expires_in * 1e3
|
|
544
567
|
};
|
|
545
|
-
this.
|
|
568
|
+
this.setSessionInternal(session);
|
|
546
569
|
return { user: session.user, session };
|
|
547
570
|
});
|
|
548
571
|
}
|
|
@@ -572,7 +595,7 @@ var FluxbaseAuth = class {
|
|
|
572
595
|
/**
|
|
573
596
|
* Internal: Set the session and persist it
|
|
574
597
|
*/
|
|
575
|
-
|
|
598
|
+
setSessionInternal(session, event = "SIGNED_IN") {
|
|
576
599
|
this.session = session;
|
|
577
600
|
this.fetch.setAuthToken(session.access_token);
|
|
578
601
|
this.saveSession();
|
|
@@ -616,7 +639,7 @@ var FluxbaseAuth = class {
|
|
|
616
639
|
const delay = refreshAt - Date.now();
|
|
617
640
|
if (delay > 0) {
|
|
618
641
|
this.refreshTimer = setTimeout(async () => {
|
|
619
|
-
const result = await this.
|
|
642
|
+
const result = await this.refreshSession();
|
|
620
643
|
if (result.error) {
|
|
621
644
|
console.error("Failed to refresh token:", result.error);
|
|
622
645
|
this.clearSession();
|
|
@@ -640,10 +663,14 @@ var FluxbaseAuth = class {
|
|
|
640
663
|
|
|
641
664
|
// src/realtime.ts
|
|
642
665
|
var RealtimeChannel = class {
|
|
643
|
-
constructor(url, channelName, token = null) {
|
|
666
|
+
constructor(url, channelName, token = null, config = {}) {
|
|
644
667
|
this.ws = null;
|
|
645
668
|
this.callbacks = /* @__PURE__ */ new Map();
|
|
669
|
+
this.presenceCallbacks = /* @__PURE__ */ new Map();
|
|
670
|
+
this.broadcastCallbacks = /* @__PURE__ */ new Map();
|
|
646
671
|
this.subscriptionConfig = null;
|
|
672
|
+
this._presenceState = {};
|
|
673
|
+
this.myPresenceKey = null;
|
|
647
674
|
this.reconnectAttempts = 0;
|
|
648
675
|
this.maxReconnectAttempts = 10;
|
|
649
676
|
this.reconnectDelay = 1e3;
|
|
@@ -651,6 +678,7 @@ var RealtimeChannel = class {
|
|
|
651
678
|
this.url = url;
|
|
652
679
|
this.channelName = channelName;
|
|
653
680
|
this.token = token;
|
|
681
|
+
this.config = config;
|
|
654
682
|
}
|
|
655
683
|
// Implementation
|
|
656
684
|
on(event, configOrCallback, callback) {
|
|
@@ -663,6 +691,20 @@ var RealtimeChannel = class {
|
|
|
663
691
|
this.callbacks.set(eventType, /* @__PURE__ */ new Set());
|
|
664
692
|
}
|
|
665
693
|
this.callbacks.get(eventType).add(actualCallback);
|
|
694
|
+
} else if (event === "broadcast" && typeof configOrCallback !== "function") {
|
|
695
|
+
const config = configOrCallback;
|
|
696
|
+
const actualCallback = callback;
|
|
697
|
+
if (!this.broadcastCallbacks.has(config.event)) {
|
|
698
|
+
this.broadcastCallbacks.set(config.event, /* @__PURE__ */ new Set());
|
|
699
|
+
}
|
|
700
|
+
this.broadcastCallbacks.get(config.event).add(actualCallback);
|
|
701
|
+
} else if (event === "presence" && typeof configOrCallback !== "function") {
|
|
702
|
+
const config = configOrCallback;
|
|
703
|
+
const actualCallback = callback;
|
|
704
|
+
if (!this.presenceCallbacks.has(config.event)) {
|
|
705
|
+
this.presenceCallbacks.set(config.event, /* @__PURE__ */ new Set());
|
|
706
|
+
}
|
|
707
|
+
this.presenceCallbacks.get(config.event).add(actualCallback);
|
|
666
708
|
} else {
|
|
667
709
|
const actualEvent = event;
|
|
668
710
|
const actualCallback = configOrCallback;
|
|
@@ -712,7 +754,7 @@ var RealtimeChannel = class {
|
|
|
712
754
|
async unsubscribe(timeout) {
|
|
713
755
|
return new Promise((resolve) => {
|
|
714
756
|
if (this.ws) {
|
|
715
|
-
this.
|
|
757
|
+
this.sendMessage({
|
|
716
758
|
type: "unsubscribe",
|
|
717
759
|
channel: this.channelName
|
|
718
760
|
});
|
|
@@ -735,6 +777,131 @@ var RealtimeChannel = class {
|
|
|
735
777
|
}
|
|
736
778
|
});
|
|
737
779
|
}
|
|
780
|
+
/**
|
|
781
|
+
* Send a broadcast message to all subscribers on this channel
|
|
782
|
+
*
|
|
783
|
+
* @param message - Broadcast message with type, event, and payload
|
|
784
|
+
* @returns Promise resolving to status
|
|
785
|
+
*
|
|
786
|
+
* @example
|
|
787
|
+
* ```typescript
|
|
788
|
+
* await channel.send({
|
|
789
|
+
* type: 'broadcast',
|
|
790
|
+
* event: 'cursor-pos',
|
|
791
|
+
* payload: { x: 100, y: 200 }
|
|
792
|
+
* })
|
|
793
|
+
* ```
|
|
794
|
+
*/
|
|
795
|
+
async send(message) {
|
|
796
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
797
|
+
return "error";
|
|
798
|
+
}
|
|
799
|
+
try {
|
|
800
|
+
this.ws.send(
|
|
801
|
+
JSON.stringify({
|
|
802
|
+
type: "broadcast",
|
|
803
|
+
channel: this.channelName,
|
|
804
|
+
event: message.event,
|
|
805
|
+
payload: message.payload
|
|
806
|
+
})
|
|
807
|
+
);
|
|
808
|
+
if (this.config.broadcast?.ack) {
|
|
809
|
+
return "ok";
|
|
810
|
+
}
|
|
811
|
+
return "ok";
|
|
812
|
+
} catch (error) {
|
|
813
|
+
console.error("[Fluxbase Realtime] Failed to send broadcast:", error);
|
|
814
|
+
return "error";
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Track user presence on this channel
|
|
819
|
+
*
|
|
820
|
+
* @param state - Presence state to track
|
|
821
|
+
* @returns Promise resolving to status
|
|
822
|
+
*
|
|
823
|
+
* @example
|
|
824
|
+
* ```typescript
|
|
825
|
+
* await channel.track({
|
|
826
|
+
* user_id: 123,
|
|
827
|
+
* status: 'online'
|
|
828
|
+
* })
|
|
829
|
+
* ```
|
|
830
|
+
*/
|
|
831
|
+
async track(state) {
|
|
832
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
833
|
+
return "error";
|
|
834
|
+
}
|
|
835
|
+
try {
|
|
836
|
+
if (!this.myPresenceKey) {
|
|
837
|
+
this.myPresenceKey = this.config.presence?.key || `presence-${Math.random().toString(36).substr(2, 9)}`;
|
|
838
|
+
}
|
|
839
|
+
this.ws.send(
|
|
840
|
+
JSON.stringify({
|
|
841
|
+
type: "presence",
|
|
842
|
+
channel: this.channelName,
|
|
843
|
+
event: "track",
|
|
844
|
+
payload: {
|
|
845
|
+
key: this.myPresenceKey,
|
|
846
|
+
state
|
|
847
|
+
}
|
|
848
|
+
})
|
|
849
|
+
);
|
|
850
|
+
return "ok";
|
|
851
|
+
} catch (error) {
|
|
852
|
+
console.error("[Fluxbase Realtime] Failed to track presence:", error);
|
|
853
|
+
return "error";
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Stop tracking presence on this channel
|
|
858
|
+
*
|
|
859
|
+
* @returns Promise resolving to status
|
|
860
|
+
*
|
|
861
|
+
* @example
|
|
862
|
+
* ```typescript
|
|
863
|
+
* await channel.untrack()
|
|
864
|
+
* ```
|
|
865
|
+
*/
|
|
866
|
+
async untrack() {
|
|
867
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
868
|
+
return "error";
|
|
869
|
+
}
|
|
870
|
+
if (!this.myPresenceKey) {
|
|
871
|
+
return "ok";
|
|
872
|
+
}
|
|
873
|
+
try {
|
|
874
|
+
this.ws.send(
|
|
875
|
+
JSON.stringify({
|
|
876
|
+
type: "presence",
|
|
877
|
+
channel: this.channelName,
|
|
878
|
+
event: "untrack",
|
|
879
|
+
payload: {
|
|
880
|
+
key: this.myPresenceKey
|
|
881
|
+
}
|
|
882
|
+
})
|
|
883
|
+
);
|
|
884
|
+
this.myPresenceKey = null;
|
|
885
|
+
return "ok";
|
|
886
|
+
} catch (error) {
|
|
887
|
+
console.error("[Fluxbase Realtime] Failed to untrack presence:", error);
|
|
888
|
+
return "error";
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Get current presence state for all users on this channel
|
|
893
|
+
*
|
|
894
|
+
* @returns Current presence state
|
|
895
|
+
*
|
|
896
|
+
* @example
|
|
897
|
+
* ```typescript
|
|
898
|
+
* const state = channel.presenceState()
|
|
899
|
+
* console.log('Online users:', Object.keys(state).length)
|
|
900
|
+
* ```
|
|
901
|
+
*/
|
|
902
|
+
presenceState() {
|
|
903
|
+
return { ...this._presenceState };
|
|
904
|
+
}
|
|
738
905
|
/**
|
|
739
906
|
* Internal: Connect to WebSocket
|
|
740
907
|
*/
|
|
@@ -759,7 +926,7 @@ var RealtimeChannel = class {
|
|
|
759
926
|
if (this.subscriptionConfig) {
|
|
760
927
|
subscribeMessage.config = this.subscriptionConfig;
|
|
761
928
|
}
|
|
762
|
-
this.
|
|
929
|
+
this.sendMessage(subscribeMessage);
|
|
763
930
|
this.startHeartbeat();
|
|
764
931
|
};
|
|
765
932
|
this.ws.onmessage = (event) => {
|
|
@@ -792,7 +959,7 @@ var RealtimeChannel = class {
|
|
|
792
959
|
/**
|
|
793
960
|
* Internal: Send a message
|
|
794
961
|
*/
|
|
795
|
-
|
|
962
|
+
sendMessage(message) {
|
|
796
963
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
797
964
|
this.ws.send(JSON.stringify(message));
|
|
798
965
|
}
|
|
@@ -803,11 +970,18 @@ var RealtimeChannel = class {
|
|
|
803
970
|
handleMessage(message) {
|
|
804
971
|
switch (message.type) {
|
|
805
972
|
case "heartbeat":
|
|
806
|
-
this.send({ type: "heartbeat" });
|
|
973
|
+
this.ws?.send(JSON.stringify({ type: "heartbeat" }));
|
|
807
974
|
break;
|
|
808
975
|
case "broadcast":
|
|
809
|
-
if (message.
|
|
810
|
-
this.
|
|
976
|
+
if (message.broadcast) {
|
|
977
|
+
this.handleBroadcastMessage(message.broadcast);
|
|
978
|
+
} else if (message.payload) {
|
|
979
|
+
this.handlePostgresChanges(message.payload);
|
|
980
|
+
}
|
|
981
|
+
break;
|
|
982
|
+
case "presence":
|
|
983
|
+
if (message.presence) {
|
|
984
|
+
this.handlePresenceMessage(message.presence);
|
|
811
985
|
}
|
|
812
986
|
break;
|
|
813
987
|
case "ack":
|
|
@@ -821,7 +995,48 @@ var RealtimeChannel = class {
|
|
|
821
995
|
/**
|
|
822
996
|
* Internal: Handle broadcast message
|
|
823
997
|
*/
|
|
824
|
-
|
|
998
|
+
handleBroadcastMessage(message) {
|
|
999
|
+
const event = message.event;
|
|
1000
|
+
const payload = {
|
|
1001
|
+
event,
|
|
1002
|
+
payload: message.payload
|
|
1003
|
+
};
|
|
1004
|
+
if (!this.config.broadcast?.self && message.self) {
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
const callbacks = this.broadcastCallbacks.get(event);
|
|
1008
|
+
if (callbacks) {
|
|
1009
|
+
callbacks.forEach((callback) => callback(payload));
|
|
1010
|
+
}
|
|
1011
|
+
const wildcardCallbacks = this.broadcastCallbacks.get("*");
|
|
1012
|
+
if (wildcardCallbacks) {
|
|
1013
|
+
wildcardCallbacks.forEach((callback) => callback(payload));
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Internal: Handle presence message
|
|
1018
|
+
*/
|
|
1019
|
+
handlePresenceMessage(message) {
|
|
1020
|
+
const event = message.event;
|
|
1021
|
+
const payload = {
|
|
1022
|
+
event,
|
|
1023
|
+
key: message.key,
|
|
1024
|
+
newPresences: message.newPresences,
|
|
1025
|
+
leftPresences: message.leftPresences,
|
|
1026
|
+
currentPresences: message.currentPresences || this._presenceState
|
|
1027
|
+
};
|
|
1028
|
+
if (message.currentPresences) {
|
|
1029
|
+
this._presenceState = message.currentPresences;
|
|
1030
|
+
}
|
|
1031
|
+
const callbacks = this.presenceCallbacks.get(event);
|
|
1032
|
+
if (callbacks) {
|
|
1033
|
+
callbacks.forEach((callback) => callback(payload));
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
/**
|
|
1037
|
+
* Internal: Handle postgres_changes message
|
|
1038
|
+
*/
|
|
1039
|
+
handlePostgresChanges(payload) {
|
|
825
1040
|
const supabasePayload = {
|
|
826
1041
|
eventType: payload.type || payload.eventType,
|
|
827
1042
|
schema: payload.schema,
|
|
@@ -845,7 +1060,7 @@ var RealtimeChannel = class {
|
|
|
845
1060
|
*/
|
|
846
1061
|
startHeartbeat() {
|
|
847
1062
|
this.heartbeatInterval = setInterval(() => {
|
|
848
|
-
this.
|
|
1063
|
+
this.sendMessage({ type: "heartbeat" });
|
|
849
1064
|
}, 3e4);
|
|
850
1065
|
}
|
|
851
1066
|
/**
|
|
@@ -882,17 +1097,57 @@ var FluxbaseRealtime = class {
|
|
|
882
1097
|
this.token = token;
|
|
883
1098
|
}
|
|
884
1099
|
/**
|
|
885
|
-
* Create or get a channel
|
|
1100
|
+
* Create or get a channel with optional configuration
|
|
1101
|
+
*
|
|
886
1102
|
* @param channelName - Channel name (e.g., 'table:public.products')
|
|
1103
|
+
* @param config - Optional channel configuration
|
|
1104
|
+
* @returns RealtimeChannel instance
|
|
1105
|
+
*
|
|
1106
|
+
* @example
|
|
1107
|
+
* ```typescript
|
|
1108
|
+
* const channel = realtime.channel('room-1', {
|
|
1109
|
+
* broadcast: { self: true, ack: true },
|
|
1110
|
+
* presence: { key: 'user-123' }
|
|
1111
|
+
* })
|
|
1112
|
+
* ```
|
|
887
1113
|
*/
|
|
888
|
-
channel(channelName) {
|
|
889
|
-
|
|
890
|
-
|
|
1114
|
+
channel(channelName, config) {
|
|
1115
|
+
const configKey = config ? JSON.stringify(config) : "";
|
|
1116
|
+
const key = `${channelName}:${configKey}`;
|
|
1117
|
+
if (this.channels.has(key)) {
|
|
1118
|
+
return this.channels.get(key);
|
|
891
1119
|
}
|
|
892
|
-
const channel = new RealtimeChannel(
|
|
893
|
-
|
|
1120
|
+
const channel = new RealtimeChannel(
|
|
1121
|
+
this.url,
|
|
1122
|
+
channelName,
|
|
1123
|
+
this.token,
|
|
1124
|
+
config
|
|
1125
|
+
);
|
|
1126
|
+
this.channels.set(key, channel);
|
|
894
1127
|
return channel;
|
|
895
1128
|
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Remove a specific channel
|
|
1131
|
+
*
|
|
1132
|
+
* @param channel - The channel to remove
|
|
1133
|
+
* @returns Promise resolving to status
|
|
1134
|
+
*
|
|
1135
|
+
* @example
|
|
1136
|
+
* ```typescript
|
|
1137
|
+
* const channel = realtime.channel('room-1')
|
|
1138
|
+
* await realtime.removeChannel(channel)
|
|
1139
|
+
* ```
|
|
1140
|
+
*/
|
|
1141
|
+
async removeChannel(channel) {
|
|
1142
|
+
await channel.unsubscribe();
|
|
1143
|
+
for (const [key, ch] of this.channels.entries()) {
|
|
1144
|
+
if (ch === channel) {
|
|
1145
|
+
this.channels.delete(key);
|
|
1146
|
+
return "ok";
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
return "error";
|
|
1150
|
+
}
|
|
896
1151
|
/**
|
|
897
1152
|
* Remove all channels
|
|
898
1153
|
*/
|
|
@@ -902,8 +1157,9 @@ var FluxbaseRealtime = class {
|
|
|
902
1157
|
}
|
|
903
1158
|
/**
|
|
904
1159
|
* Update auth token for all channels
|
|
1160
|
+
* @param token - The new auth token
|
|
905
1161
|
*/
|
|
906
|
-
|
|
1162
|
+
setAuth(token) {
|
|
907
1163
|
this.token = token;
|
|
908
1164
|
}
|
|
909
1165
|
};
|
|
@@ -937,15 +1193,20 @@ var StorageBucket = class {
|
|
|
937
1193
|
if (options?.upsert !== void 0) {
|
|
938
1194
|
formData.append("upsert", String(options.upsert));
|
|
939
1195
|
}
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1196
|
+
let response;
|
|
1197
|
+
if (options?.onUploadProgress) {
|
|
1198
|
+
response = await this.uploadWithProgress(path, formData, options.onUploadProgress);
|
|
1199
|
+
} else {
|
|
1200
|
+
response = await this.fetch.request(
|
|
1201
|
+
`/api/v1/storage/${this.bucketName}/${path}`,
|
|
1202
|
+
{
|
|
1203
|
+
method: "POST",
|
|
1204
|
+
body: formData,
|
|
1205
|
+
headers: {}
|
|
1206
|
+
// Let browser set Content-Type for FormData
|
|
1207
|
+
}
|
|
1208
|
+
);
|
|
1209
|
+
}
|
|
949
1210
|
return {
|
|
950
1211
|
data: {
|
|
951
1212
|
id: response.id || response.key || path,
|
|
@@ -958,6 +1219,57 @@ var StorageBucket = class {
|
|
|
958
1219
|
return { data: null, error };
|
|
959
1220
|
}
|
|
960
1221
|
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Upload with progress tracking using XMLHttpRequest
|
|
1224
|
+
* @private
|
|
1225
|
+
*/
|
|
1226
|
+
uploadWithProgress(path, formData, onProgress) {
|
|
1227
|
+
return new Promise((resolve, reject) => {
|
|
1228
|
+
const xhr = new XMLHttpRequest();
|
|
1229
|
+
const url = `${this.fetch["baseUrl"]}/api/v1/storage/${this.bucketName}/${path}`;
|
|
1230
|
+
xhr.upload.addEventListener("progress", (event) => {
|
|
1231
|
+
if (event.lengthComputable) {
|
|
1232
|
+
const percentage = Math.round(event.loaded / event.total * 100);
|
|
1233
|
+
onProgress({
|
|
1234
|
+
loaded: event.loaded,
|
|
1235
|
+
total: event.total,
|
|
1236
|
+
percentage
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
});
|
|
1240
|
+
xhr.addEventListener("load", () => {
|
|
1241
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
1242
|
+
try {
|
|
1243
|
+
const response = JSON.parse(xhr.responseText);
|
|
1244
|
+
resolve(response);
|
|
1245
|
+
} catch (e) {
|
|
1246
|
+
resolve(xhr.responseText);
|
|
1247
|
+
}
|
|
1248
|
+
} else {
|
|
1249
|
+
try {
|
|
1250
|
+
const errorData = JSON.parse(xhr.responseText);
|
|
1251
|
+
reject(new Error(errorData.error || xhr.statusText));
|
|
1252
|
+
} catch (e) {
|
|
1253
|
+
reject(new Error(xhr.statusText));
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
});
|
|
1257
|
+
xhr.addEventListener("error", () => {
|
|
1258
|
+
reject(new Error("Upload failed"));
|
|
1259
|
+
});
|
|
1260
|
+
xhr.addEventListener("abort", () => {
|
|
1261
|
+
reject(new Error("Upload aborted"));
|
|
1262
|
+
});
|
|
1263
|
+
xhr.open("POST", url);
|
|
1264
|
+
const headers = this.fetch["defaultHeaders"];
|
|
1265
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
1266
|
+
if (key.toLowerCase() !== "content-type") {
|
|
1267
|
+
xhr.setRequestHeader(key, value);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
xhr.send(formData);
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
961
1273
|
/**
|
|
962
1274
|
* Download a file from the bucket
|
|
963
1275
|
* @param path - The path/key of the file
|
|
@@ -4284,7 +4596,7 @@ var FluxbaseClient = class {
|
|
|
4284
4596
|
const originalSetAuthToken = this.fetch.setAuthToken.bind(this.fetch);
|
|
4285
4597
|
this.fetch.setAuthToken = (token) => {
|
|
4286
4598
|
originalSetAuthToken(token);
|
|
4287
|
-
this.realtime.
|
|
4599
|
+
this.realtime.setAuth(token);
|
|
4288
4600
|
};
|
|
4289
4601
|
}
|
|
4290
4602
|
/**
|
|
@@ -4308,37 +4620,48 @@ var FluxbaseClient = class {
|
|
|
4308
4620
|
*/
|
|
4309
4621
|
setAuthToken(token) {
|
|
4310
4622
|
this.fetch.setAuthToken(token);
|
|
4311
|
-
this.realtime.
|
|
4623
|
+
this.realtime.setAuth(token);
|
|
4312
4624
|
}
|
|
4313
4625
|
/**
|
|
4314
|
-
* Create or get a realtime channel (Supabase-compatible
|
|
4315
|
-
*
|
|
4316
|
-
* This is a convenience method that delegates to client.realtime.channel().
|
|
4317
|
-
* Both patterns work identically:
|
|
4318
|
-
* - client.channel('room-1') - Supabase-style
|
|
4319
|
-
* - client.realtime.channel('room-1') - Fluxbase-style
|
|
4626
|
+
* Create or get a realtime channel (Supabase-compatible)
|
|
4320
4627
|
*
|
|
4321
4628
|
* @param name - Channel name
|
|
4629
|
+
* @param config - Optional channel configuration
|
|
4322
4630
|
* @returns RealtimeChannel instance
|
|
4323
4631
|
*
|
|
4324
4632
|
* @example
|
|
4325
4633
|
* ```typescript
|
|
4326
|
-
*
|
|
4327
|
-
*
|
|
4328
|
-
*
|
|
4329
|
-
*
|
|
4330
|
-
*
|
|
4331
|
-
*
|
|
4332
|
-
* }, (payload) => {
|
|
4333
|
-
* console.log('Change:', payload)
|
|
4634
|
+
* const channel = client.channel('room-1', {
|
|
4635
|
+
* broadcast: { self: true },
|
|
4636
|
+
* presence: { key: 'user-123' }
|
|
4637
|
+
* })
|
|
4638
|
+
* .on('broadcast', { event: 'message' }, (payload) => {
|
|
4639
|
+
* console.log('Message:', payload)
|
|
4334
4640
|
* })
|
|
4335
4641
|
* .subscribe()
|
|
4336
4642
|
* ```
|
|
4337
4643
|
*
|
|
4338
4644
|
* @category Realtime
|
|
4339
4645
|
*/
|
|
4340
|
-
channel(name) {
|
|
4341
|
-
return this.realtime.channel(name);
|
|
4646
|
+
channel(name, config) {
|
|
4647
|
+
return this.realtime.channel(name, config);
|
|
4648
|
+
}
|
|
4649
|
+
/**
|
|
4650
|
+
* Remove a realtime channel (Supabase-compatible)
|
|
4651
|
+
*
|
|
4652
|
+
* @param channel - The channel to remove
|
|
4653
|
+
* @returns Promise resolving to status
|
|
4654
|
+
*
|
|
4655
|
+
* @example
|
|
4656
|
+
* ```typescript
|
|
4657
|
+
* const channel = client.channel('room-1')
|
|
4658
|
+
* await client.removeChannel(channel)
|
|
4659
|
+
* ```
|
|
4660
|
+
*
|
|
4661
|
+
* @category Realtime
|
|
4662
|
+
*/
|
|
4663
|
+
removeChannel(channel) {
|
|
4664
|
+
return this.realtime.removeChannel(channel);
|
|
4342
4665
|
}
|
|
4343
4666
|
/**
|
|
4344
4667
|
* Get the internal HTTP client
|