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