@fluxbase/sdk 0.0.1-rc.27 → 0.0.1-rc.29
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 +345 -76
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +324 -61
- package/dist/index.d.ts +324 -61
- package/dist/index.js +345 -76
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -210,8 +210,8 @@ var FluxbaseAuth = class {
|
|
|
210
210
|
return { data: { subscription } };
|
|
211
211
|
}
|
|
212
212
|
/**
|
|
213
|
-
* Sign in with email and password
|
|
214
|
-
* Returns
|
|
213
|
+
* Sign in with email and password (Supabase-compatible)
|
|
214
|
+
* Returns { user, session } if successful, or SignInWith2FAResponse if 2FA is required
|
|
215
215
|
*/
|
|
216
216
|
async signIn(credentials) {
|
|
217
217
|
return wrapAsync(async () => {
|
|
@@ -225,19 +225,21 @@ var FluxbaseAuth = class {
|
|
|
225
225
|
expires_at: Date.now() + authResponse.expires_in * 1e3
|
|
226
226
|
};
|
|
227
227
|
this.setSessionInternal(session);
|
|
228
|
-
return session;
|
|
228
|
+
return { user: session.user, session };
|
|
229
229
|
});
|
|
230
230
|
}
|
|
231
231
|
/**
|
|
232
|
-
* Sign in with email and password
|
|
232
|
+
* Sign in with email and password (Supabase-compatible)
|
|
233
233
|
* Alias for signIn() to maintain compatibility with common authentication patterns
|
|
234
|
-
* Returns
|
|
234
|
+
* Returns { user, session } if successful, or SignInWith2FAResponse if 2FA is required
|
|
235
235
|
*/
|
|
236
236
|
async signInWithPassword(credentials) {
|
|
237
237
|
return this.signIn(credentials);
|
|
238
238
|
}
|
|
239
239
|
/**
|
|
240
|
-
* Sign up with email and password
|
|
240
|
+
* Sign up with email and password (Supabase-compatible)
|
|
241
|
+
* Returns session when email confirmation is disabled
|
|
242
|
+
* Returns null session when email confirmation is required
|
|
241
243
|
*/
|
|
242
244
|
async signUp(credentials) {
|
|
243
245
|
return wrapAsync(async () => {
|
|
@@ -245,12 +247,15 @@ var FluxbaseAuth = class {
|
|
|
245
247
|
"/api/v1/auth/signup",
|
|
246
248
|
credentials
|
|
247
249
|
);
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
250
|
+
if (response.access_token && response.refresh_token) {
|
|
251
|
+
const session = {
|
|
252
|
+
...response,
|
|
253
|
+
expires_at: Date.now() + response.expires_in * 1e3
|
|
254
|
+
};
|
|
255
|
+
this.setSessionInternal(session);
|
|
256
|
+
return { user: response.user, session };
|
|
257
|
+
}
|
|
258
|
+
return { user: response.user, session: null };
|
|
254
259
|
});
|
|
255
260
|
}
|
|
256
261
|
/**
|
|
@@ -285,7 +290,7 @@ var FluxbaseAuth = class {
|
|
|
285
290
|
expires_at: Date.now() + response.expires_in * 1e3
|
|
286
291
|
};
|
|
287
292
|
this.setSessionInternal(session, "TOKEN_REFRESHED");
|
|
288
|
-
return {
|
|
293
|
+
return { user: session.user, session };
|
|
289
294
|
});
|
|
290
295
|
}
|
|
291
296
|
/**
|
|
@@ -342,8 +347,9 @@ var FluxbaseAuth = class {
|
|
|
342
347
|
});
|
|
343
348
|
}
|
|
344
349
|
/**
|
|
345
|
-
* Setup 2FA for the current user
|
|
346
|
-
*
|
|
350
|
+
* Setup 2FA for the current user (Supabase-compatible)
|
|
351
|
+
* Enrolls a new MFA factor and returns TOTP details
|
|
352
|
+
* @returns Promise with factor id, type, and TOTP setup details
|
|
347
353
|
*/
|
|
348
354
|
async setup2FA() {
|
|
349
355
|
return wrapAsync(async () => {
|
|
@@ -356,8 +362,10 @@ var FluxbaseAuth = class {
|
|
|
356
362
|
});
|
|
357
363
|
}
|
|
358
364
|
/**
|
|
359
|
-
* Enable 2FA after verifying the TOTP code
|
|
360
|
-
*
|
|
365
|
+
* Enable 2FA after verifying the TOTP code (Supabase-compatible)
|
|
366
|
+
* Verifies the TOTP code and returns new tokens with MFA session
|
|
367
|
+
* @param code - TOTP code from authenticator app
|
|
368
|
+
* @returns Promise with access_token, refresh_token, and user
|
|
361
369
|
*/
|
|
362
370
|
async enable2FA(code) {
|
|
363
371
|
return wrapAsync(async () => {
|
|
@@ -371,8 +379,10 @@ var FluxbaseAuth = class {
|
|
|
371
379
|
});
|
|
372
380
|
}
|
|
373
381
|
/**
|
|
374
|
-
* Disable 2FA for the current user
|
|
375
|
-
*
|
|
382
|
+
* Disable 2FA for the current user (Supabase-compatible)
|
|
383
|
+
* Unenrolls the MFA factor
|
|
384
|
+
* @param password - User password for confirmation
|
|
385
|
+
* @returns Promise with unenrolled factor id
|
|
376
386
|
*/
|
|
377
387
|
async disable2FA(password) {
|
|
378
388
|
return wrapAsync(async () => {
|
|
@@ -386,7 +396,9 @@ var FluxbaseAuth = class {
|
|
|
386
396
|
});
|
|
387
397
|
}
|
|
388
398
|
/**
|
|
389
|
-
* Check 2FA status for the current user
|
|
399
|
+
* Check 2FA status for the current user (Supabase-compatible)
|
|
400
|
+
* Lists all enrolled MFA factors
|
|
401
|
+
* @returns Promise with all factors and TOTP factors
|
|
390
402
|
*/
|
|
391
403
|
async get2FAStatus() {
|
|
392
404
|
return wrapAsync(async () => {
|
|
@@ -399,8 +411,10 @@ var FluxbaseAuth = class {
|
|
|
399
411
|
});
|
|
400
412
|
}
|
|
401
413
|
/**
|
|
402
|
-
* Verify 2FA code during login
|
|
414
|
+
* Verify 2FA code during login (Supabase-compatible)
|
|
403
415
|
* Call this after signIn returns requires_2fa: true
|
|
416
|
+
* @param request - User ID and TOTP code
|
|
417
|
+
* @returns Promise with access_token, refresh_token, and user
|
|
404
418
|
*/
|
|
405
419
|
async verify2FA(request) {
|
|
406
420
|
return wrapAsync(async () => {
|
|
@@ -408,31 +422,36 @@ var FluxbaseAuth = class {
|
|
|
408
422
|
"/api/v1/auth/2fa/verify",
|
|
409
423
|
request
|
|
410
424
|
);
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
425
|
+
if (response.access_token && response.refresh_token) {
|
|
426
|
+
const session = {
|
|
427
|
+
user: response.user,
|
|
428
|
+
access_token: response.access_token,
|
|
429
|
+
refresh_token: response.refresh_token,
|
|
430
|
+
expires_in: response.expires_in || 3600,
|
|
431
|
+
expires_at: Date.now() + (response.expires_in || 3600) * 1e3
|
|
432
|
+
};
|
|
433
|
+
this.setSessionInternal(session, "MFA_CHALLENGE_VERIFIED");
|
|
434
|
+
}
|
|
435
|
+
return response;
|
|
417
436
|
});
|
|
418
437
|
}
|
|
419
438
|
/**
|
|
420
|
-
* Send password reset email
|
|
439
|
+
* Send password reset email (Supabase-compatible)
|
|
421
440
|
* Sends a password reset link to the provided email address
|
|
422
441
|
* @param email - Email address to send reset link to
|
|
442
|
+
* @returns Promise with OTP-style response
|
|
423
443
|
*/
|
|
424
444
|
async sendPasswordReset(email) {
|
|
425
445
|
return wrapAsync(async () => {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
{ email }
|
|
429
|
-
);
|
|
446
|
+
await this.fetch.post("/api/v1/auth/password/reset", { email });
|
|
447
|
+
return { user: null, session: null };
|
|
430
448
|
});
|
|
431
449
|
}
|
|
432
450
|
/**
|
|
433
451
|
* Supabase-compatible alias for sendPasswordReset()
|
|
434
452
|
* @param email - Email address to send reset link to
|
|
435
453
|
* @param _options - Optional redirect configuration (currently not used)
|
|
454
|
+
* @returns Promise with OTP-style response
|
|
436
455
|
*/
|
|
437
456
|
async resetPasswordForEmail(email, _options) {
|
|
438
457
|
return this.sendPasswordReset(email);
|
|
@@ -453,36 +472,42 @@ var FluxbaseAuth = class {
|
|
|
453
472
|
});
|
|
454
473
|
}
|
|
455
474
|
/**
|
|
456
|
-
* Reset password with token
|
|
475
|
+
* Reset password with token (Supabase-compatible)
|
|
457
476
|
* Complete the password reset process with a valid token
|
|
458
477
|
* @param token - Password reset token
|
|
459
478
|
* @param newPassword - New password to set
|
|
479
|
+
* @returns Promise with user and new session
|
|
460
480
|
*/
|
|
461
481
|
async resetPassword(token, newPassword) {
|
|
462
482
|
return wrapAsync(async () => {
|
|
463
|
-
|
|
483
|
+
const response = await this.fetch.post(
|
|
464
484
|
"/api/v1/auth/password/reset/confirm",
|
|
465
485
|
{
|
|
466
486
|
token,
|
|
467
487
|
new_password: newPassword
|
|
468
488
|
}
|
|
469
489
|
);
|
|
490
|
+
const session = {
|
|
491
|
+
...response,
|
|
492
|
+
expires_at: Date.now() + response.expires_in * 1e3
|
|
493
|
+
};
|
|
494
|
+
this.setSessionInternal(session, "PASSWORD_RECOVERY");
|
|
495
|
+
return { user: session.user, session };
|
|
470
496
|
});
|
|
471
497
|
}
|
|
472
498
|
/**
|
|
473
|
-
* Send magic link for passwordless authentication
|
|
499
|
+
* Send magic link for passwordless authentication (Supabase-compatible)
|
|
474
500
|
* @param email - Email address to send magic link to
|
|
475
501
|
* @param options - Optional configuration for magic link
|
|
502
|
+
* @returns Promise with OTP-style response
|
|
476
503
|
*/
|
|
477
504
|
async sendMagicLink(email, options) {
|
|
478
505
|
return wrapAsync(async () => {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
}
|
|
485
|
-
);
|
|
506
|
+
await this.fetch.post("/api/v1/auth/magiclink", {
|
|
507
|
+
email,
|
|
508
|
+
redirect_to: options?.redirect_to
|
|
509
|
+
});
|
|
510
|
+
return { user: null, session: null };
|
|
486
511
|
});
|
|
487
512
|
}
|
|
488
513
|
/**
|
|
@@ -665,10 +690,14 @@ var FluxbaseAuth = class {
|
|
|
665
690
|
|
|
666
691
|
// src/realtime.ts
|
|
667
692
|
var RealtimeChannel = class {
|
|
668
|
-
constructor(url, channelName, token = null) {
|
|
693
|
+
constructor(url, channelName, token = null, config = {}) {
|
|
669
694
|
this.ws = null;
|
|
670
695
|
this.callbacks = /* @__PURE__ */ new Map();
|
|
696
|
+
this.presenceCallbacks = /* @__PURE__ */ new Map();
|
|
697
|
+
this.broadcastCallbacks = /* @__PURE__ */ new Map();
|
|
671
698
|
this.subscriptionConfig = null;
|
|
699
|
+
this._presenceState = {};
|
|
700
|
+
this.myPresenceKey = null;
|
|
672
701
|
this.reconnectAttempts = 0;
|
|
673
702
|
this.maxReconnectAttempts = 10;
|
|
674
703
|
this.reconnectDelay = 1e3;
|
|
@@ -676,6 +705,7 @@ var RealtimeChannel = class {
|
|
|
676
705
|
this.url = url;
|
|
677
706
|
this.channelName = channelName;
|
|
678
707
|
this.token = token;
|
|
708
|
+
this.config = config;
|
|
679
709
|
}
|
|
680
710
|
// Implementation
|
|
681
711
|
on(event, configOrCallback, callback) {
|
|
@@ -688,6 +718,20 @@ var RealtimeChannel = class {
|
|
|
688
718
|
this.callbacks.set(eventType, /* @__PURE__ */ new Set());
|
|
689
719
|
}
|
|
690
720
|
this.callbacks.get(eventType).add(actualCallback);
|
|
721
|
+
} else if (event === "broadcast" && typeof configOrCallback !== "function") {
|
|
722
|
+
const config = configOrCallback;
|
|
723
|
+
const actualCallback = callback;
|
|
724
|
+
if (!this.broadcastCallbacks.has(config.event)) {
|
|
725
|
+
this.broadcastCallbacks.set(config.event, /* @__PURE__ */ new Set());
|
|
726
|
+
}
|
|
727
|
+
this.broadcastCallbacks.get(config.event).add(actualCallback);
|
|
728
|
+
} else if (event === "presence" && typeof configOrCallback !== "function") {
|
|
729
|
+
const config = configOrCallback;
|
|
730
|
+
const actualCallback = callback;
|
|
731
|
+
if (!this.presenceCallbacks.has(config.event)) {
|
|
732
|
+
this.presenceCallbacks.set(config.event, /* @__PURE__ */ new Set());
|
|
733
|
+
}
|
|
734
|
+
this.presenceCallbacks.get(config.event).add(actualCallback);
|
|
691
735
|
} else {
|
|
692
736
|
const actualEvent = event;
|
|
693
737
|
const actualCallback = configOrCallback;
|
|
@@ -737,7 +781,7 @@ var RealtimeChannel = class {
|
|
|
737
781
|
async unsubscribe(timeout) {
|
|
738
782
|
return new Promise((resolve) => {
|
|
739
783
|
if (this.ws) {
|
|
740
|
-
this.
|
|
784
|
+
this.sendMessage({
|
|
741
785
|
type: "unsubscribe",
|
|
742
786
|
channel: this.channelName
|
|
743
787
|
});
|
|
@@ -760,6 +804,131 @@ var RealtimeChannel = class {
|
|
|
760
804
|
}
|
|
761
805
|
});
|
|
762
806
|
}
|
|
807
|
+
/**
|
|
808
|
+
* Send a broadcast message to all subscribers on this channel
|
|
809
|
+
*
|
|
810
|
+
* @param message - Broadcast message with type, event, and payload
|
|
811
|
+
* @returns Promise resolving to status
|
|
812
|
+
*
|
|
813
|
+
* @example
|
|
814
|
+
* ```typescript
|
|
815
|
+
* await channel.send({
|
|
816
|
+
* type: 'broadcast',
|
|
817
|
+
* event: 'cursor-pos',
|
|
818
|
+
* payload: { x: 100, y: 200 }
|
|
819
|
+
* })
|
|
820
|
+
* ```
|
|
821
|
+
*/
|
|
822
|
+
async send(message) {
|
|
823
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
824
|
+
return "error";
|
|
825
|
+
}
|
|
826
|
+
try {
|
|
827
|
+
this.ws.send(
|
|
828
|
+
JSON.stringify({
|
|
829
|
+
type: "broadcast",
|
|
830
|
+
channel: this.channelName,
|
|
831
|
+
event: message.event,
|
|
832
|
+
payload: message.payload
|
|
833
|
+
})
|
|
834
|
+
);
|
|
835
|
+
if (this.config.broadcast?.ack) {
|
|
836
|
+
return "ok";
|
|
837
|
+
}
|
|
838
|
+
return "ok";
|
|
839
|
+
} catch (error) {
|
|
840
|
+
console.error("[Fluxbase Realtime] Failed to send broadcast:", error);
|
|
841
|
+
return "error";
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Track user presence on this channel
|
|
846
|
+
*
|
|
847
|
+
* @param state - Presence state to track
|
|
848
|
+
* @returns Promise resolving to status
|
|
849
|
+
*
|
|
850
|
+
* @example
|
|
851
|
+
* ```typescript
|
|
852
|
+
* await channel.track({
|
|
853
|
+
* user_id: 123,
|
|
854
|
+
* status: 'online'
|
|
855
|
+
* })
|
|
856
|
+
* ```
|
|
857
|
+
*/
|
|
858
|
+
async track(state) {
|
|
859
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
860
|
+
return "error";
|
|
861
|
+
}
|
|
862
|
+
try {
|
|
863
|
+
if (!this.myPresenceKey) {
|
|
864
|
+
this.myPresenceKey = this.config.presence?.key || `presence-${Math.random().toString(36).substr(2, 9)}`;
|
|
865
|
+
}
|
|
866
|
+
this.ws.send(
|
|
867
|
+
JSON.stringify({
|
|
868
|
+
type: "presence",
|
|
869
|
+
channel: this.channelName,
|
|
870
|
+
event: "track",
|
|
871
|
+
payload: {
|
|
872
|
+
key: this.myPresenceKey,
|
|
873
|
+
state
|
|
874
|
+
}
|
|
875
|
+
})
|
|
876
|
+
);
|
|
877
|
+
return "ok";
|
|
878
|
+
} catch (error) {
|
|
879
|
+
console.error("[Fluxbase Realtime] Failed to track presence:", error);
|
|
880
|
+
return "error";
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Stop tracking presence on this channel
|
|
885
|
+
*
|
|
886
|
+
* @returns Promise resolving to status
|
|
887
|
+
*
|
|
888
|
+
* @example
|
|
889
|
+
* ```typescript
|
|
890
|
+
* await channel.untrack()
|
|
891
|
+
* ```
|
|
892
|
+
*/
|
|
893
|
+
async untrack() {
|
|
894
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
895
|
+
return "error";
|
|
896
|
+
}
|
|
897
|
+
if (!this.myPresenceKey) {
|
|
898
|
+
return "ok";
|
|
899
|
+
}
|
|
900
|
+
try {
|
|
901
|
+
this.ws.send(
|
|
902
|
+
JSON.stringify({
|
|
903
|
+
type: "presence",
|
|
904
|
+
channel: this.channelName,
|
|
905
|
+
event: "untrack",
|
|
906
|
+
payload: {
|
|
907
|
+
key: this.myPresenceKey
|
|
908
|
+
}
|
|
909
|
+
})
|
|
910
|
+
);
|
|
911
|
+
this.myPresenceKey = null;
|
|
912
|
+
return "ok";
|
|
913
|
+
} catch (error) {
|
|
914
|
+
console.error("[Fluxbase Realtime] Failed to untrack presence:", error);
|
|
915
|
+
return "error";
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Get current presence state for all users on this channel
|
|
920
|
+
*
|
|
921
|
+
* @returns Current presence state
|
|
922
|
+
*
|
|
923
|
+
* @example
|
|
924
|
+
* ```typescript
|
|
925
|
+
* const state = channel.presenceState()
|
|
926
|
+
* console.log('Online users:', Object.keys(state).length)
|
|
927
|
+
* ```
|
|
928
|
+
*/
|
|
929
|
+
presenceState() {
|
|
930
|
+
return { ...this._presenceState };
|
|
931
|
+
}
|
|
763
932
|
/**
|
|
764
933
|
* Internal: Connect to WebSocket
|
|
765
934
|
*/
|
|
@@ -784,7 +953,7 @@ var RealtimeChannel = class {
|
|
|
784
953
|
if (this.subscriptionConfig) {
|
|
785
954
|
subscribeMessage.config = this.subscriptionConfig;
|
|
786
955
|
}
|
|
787
|
-
this.
|
|
956
|
+
this.sendMessage(subscribeMessage);
|
|
788
957
|
this.startHeartbeat();
|
|
789
958
|
};
|
|
790
959
|
this.ws.onmessage = (event) => {
|
|
@@ -817,7 +986,7 @@ var RealtimeChannel = class {
|
|
|
817
986
|
/**
|
|
818
987
|
* Internal: Send a message
|
|
819
988
|
*/
|
|
820
|
-
|
|
989
|
+
sendMessage(message) {
|
|
821
990
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
822
991
|
this.ws.send(JSON.stringify(message));
|
|
823
992
|
}
|
|
@@ -828,11 +997,18 @@ var RealtimeChannel = class {
|
|
|
828
997
|
handleMessage(message) {
|
|
829
998
|
switch (message.type) {
|
|
830
999
|
case "heartbeat":
|
|
831
|
-
this.send({ type: "heartbeat" });
|
|
1000
|
+
this.ws?.send(JSON.stringify({ type: "heartbeat" }));
|
|
832
1001
|
break;
|
|
833
1002
|
case "broadcast":
|
|
834
|
-
if (message.
|
|
835
|
-
this.
|
|
1003
|
+
if (message.broadcast) {
|
|
1004
|
+
this.handleBroadcastMessage(message.broadcast);
|
|
1005
|
+
} else if (message.payload) {
|
|
1006
|
+
this.handlePostgresChanges(message.payload);
|
|
1007
|
+
}
|
|
1008
|
+
break;
|
|
1009
|
+
case "presence":
|
|
1010
|
+
if (message.presence) {
|
|
1011
|
+
this.handlePresenceMessage(message.presence);
|
|
836
1012
|
}
|
|
837
1013
|
break;
|
|
838
1014
|
case "ack":
|
|
@@ -846,7 +1022,48 @@ var RealtimeChannel = class {
|
|
|
846
1022
|
/**
|
|
847
1023
|
* Internal: Handle broadcast message
|
|
848
1024
|
*/
|
|
849
|
-
|
|
1025
|
+
handleBroadcastMessage(message) {
|
|
1026
|
+
const event = message.event;
|
|
1027
|
+
const payload = {
|
|
1028
|
+
event,
|
|
1029
|
+
payload: message.payload
|
|
1030
|
+
};
|
|
1031
|
+
if (!this.config.broadcast?.self && message.self) {
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
const callbacks = this.broadcastCallbacks.get(event);
|
|
1035
|
+
if (callbacks) {
|
|
1036
|
+
callbacks.forEach((callback) => callback(payload));
|
|
1037
|
+
}
|
|
1038
|
+
const wildcardCallbacks = this.broadcastCallbacks.get("*");
|
|
1039
|
+
if (wildcardCallbacks) {
|
|
1040
|
+
wildcardCallbacks.forEach((callback) => callback(payload));
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Internal: Handle presence message
|
|
1045
|
+
*/
|
|
1046
|
+
handlePresenceMessage(message) {
|
|
1047
|
+
const event = message.event;
|
|
1048
|
+
const payload = {
|
|
1049
|
+
event,
|
|
1050
|
+
key: message.key,
|
|
1051
|
+
newPresences: message.newPresences,
|
|
1052
|
+
leftPresences: message.leftPresences,
|
|
1053
|
+
currentPresences: message.currentPresences || this._presenceState
|
|
1054
|
+
};
|
|
1055
|
+
if (message.currentPresences) {
|
|
1056
|
+
this._presenceState = message.currentPresences;
|
|
1057
|
+
}
|
|
1058
|
+
const callbacks = this.presenceCallbacks.get(event);
|
|
1059
|
+
if (callbacks) {
|
|
1060
|
+
callbacks.forEach((callback) => callback(payload));
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Internal: Handle postgres_changes message
|
|
1065
|
+
*/
|
|
1066
|
+
handlePostgresChanges(payload) {
|
|
850
1067
|
const supabasePayload = {
|
|
851
1068
|
eventType: payload.type || payload.eventType,
|
|
852
1069
|
schema: payload.schema,
|
|
@@ -870,7 +1087,7 @@ var RealtimeChannel = class {
|
|
|
870
1087
|
*/
|
|
871
1088
|
startHeartbeat() {
|
|
872
1089
|
this.heartbeatInterval = setInterval(() => {
|
|
873
|
-
this.
|
|
1090
|
+
this.sendMessage({ type: "heartbeat" });
|
|
874
1091
|
}, 3e4);
|
|
875
1092
|
}
|
|
876
1093
|
/**
|
|
@@ -907,17 +1124,57 @@ var FluxbaseRealtime = class {
|
|
|
907
1124
|
this.token = token;
|
|
908
1125
|
}
|
|
909
1126
|
/**
|
|
910
|
-
* Create or get a channel
|
|
1127
|
+
* Create or get a channel with optional configuration
|
|
1128
|
+
*
|
|
911
1129
|
* @param channelName - Channel name (e.g., 'table:public.products')
|
|
1130
|
+
* @param config - Optional channel configuration
|
|
1131
|
+
* @returns RealtimeChannel instance
|
|
1132
|
+
*
|
|
1133
|
+
* @example
|
|
1134
|
+
* ```typescript
|
|
1135
|
+
* const channel = realtime.channel('room-1', {
|
|
1136
|
+
* broadcast: { self: true, ack: true },
|
|
1137
|
+
* presence: { key: 'user-123' }
|
|
1138
|
+
* })
|
|
1139
|
+
* ```
|
|
912
1140
|
*/
|
|
913
|
-
channel(channelName) {
|
|
914
|
-
|
|
915
|
-
|
|
1141
|
+
channel(channelName, config) {
|
|
1142
|
+
const configKey = config ? JSON.stringify(config) : "";
|
|
1143
|
+
const key = `${channelName}:${configKey}`;
|
|
1144
|
+
if (this.channels.has(key)) {
|
|
1145
|
+
return this.channels.get(key);
|
|
916
1146
|
}
|
|
917
|
-
const channel = new RealtimeChannel(
|
|
918
|
-
|
|
1147
|
+
const channel = new RealtimeChannel(
|
|
1148
|
+
this.url,
|
|
1149
|
+
channelName,
|
|
1150
|
+
this.token,
|
|
1151
|
+
config
|
|
1152
|
+
);
|
|
1153
|
+
this.channels.set(key, channel);
|
|
919
1154
|
return channel;
|
|
920
1155
|
}
|
|
1156
|
+
/**
|
|
1157
|
+
* Remove a specific channel
|
|
1158
|
+
*
|
|
1159
|
+
* @param channel - The channel to remove
|
|
1160
|
+
* @returns Promise resolving to status
|
|
1161
|
+
*
|
|
1162
|
+
* @example
|
|
1163
|
+
* ```typescript
|
|
1164
|
+
* const channel = realtime.channel('room-1')
|
|
1165
|
+
* await realtime.removeChannel(channel)
|
|
1166
|
+
* ```
|
|
1167
|
+
*/
|
|
1168
|
+
async removeChannel(channel) {
|
|
1169
|
+
await channel.unsubscribe();
|
|
1170
|
+
for (const [key, ch] of this.channels.entries()) {
|
|
1171
|
+
if (ch === channel) {
|
|
1172
|
+
this.channels.delete(key);
|
|
1173
|
+
return "ok";
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
return "error";
|
|
1177
|
+
}
|
|
921
1178
|
/**
|
|
922
1179
|
* Remove all channels
|
|
923
1180
|
*/
|
|
@@ -927,8 +1184,9 @@ var FluxbaseRealtime = class {
|
|
|
927
1184
|
}
|
|
928
1185
|
/**
|
|
929
1186
|
* Update auth token for all channels
|
|
1187
|
+
* @param token - The new auth token
|
|
930
1188
|
*/
|
|
931
|
-
|
|
1189
|
+
setAuth(token) {
|
|
932
1190
|
this.token = token;
|
|
933
1191
|
}
|
|
934
1192
|
};
|
|
@@ -4365,7 +4623,7 @@ var FluxbaseClient = class {
|
|
|
4365
4623
|
const originalSetAuthToken = this.fetch.setAuthToken.bind(this.fetch);
|
|
4366
4624
|
this.fetch.setAuthToken = (token) => {
|
|
4367
4625
|
originalSetAuthToken(token);
|
|
4368
|
-
this.realtime.
|
|
4626
|
+
this.realtime.setAuth(token);
|
|
4369
4627
|
};
|
|
4370
4628
|
}
|
|
4371
4629
|
/**
|
|
@@ -4389,37 +4647,48 @@ var FluxbaseClient = class {
|
|
|
4389
4647
|
*/
|
|
4390
4648
|
setAuthToken(token) {
|
|
4391
4649
|
this.fetch.setAuthToken(token);
|
|
4392
|
-
this.realtime.
|
|
4650
|
+
this.realtime.setAuth(token);
|
|
4393
4651
|
}
|
|
4394
4652
|
/**
|
|
4395
|
-
* Create or get a realtime channel (Supabase-compatible
|
|
4396
|
-
*
|
|
4397
|
-
* This is a convenience method that delegates to client.realtime.channel().
|
|
4398
|
-
* Both patterns work identically:
|
|
4399
|
-
* - client.channel('room-1') - Supabase-style
|
|
4400
|
-
* - client.realtime.channel('room-1') - Fluxbase-style
|
|
4653
|
+
* Create or get a realtime channel (Supabase-compatible)
|
|
4401
4654
|
*
|
|
4402
4655
|
* @param name - Channel name
|
|
4656
|
+
* @param config - Optional channel configuration
|
|
4403
4657
|
* @returns RealtimeChannel instance
|
|
4404
4658
|
*
|
|
4405
4659
|
* @example
|
|
4406
4660
|
* ```typescript
|
|
4407
|
-
*
|
|
4408
|
-
*
|
|
4409
|
-
*
|
|
4410
|
-
*
|
|
4411
|
-
*
|
|
4412
|
-
*
|
|
4413
|
-
* }, (payload) => {
|
|
4414
|
-
* console.log('Change:', payload)
|
|
4661
|
+
* const channel = client.channel('room-1', {
|
|
4662
|
+
* broadcast: { self: true },
|
|
4663
|
+
* presence: { key: 'user-123' }
|
|
4664
|
+
* })
|
|
4665
|
+
* .on('broadcast', { event: 'message' }, (payload) => {
|
|
4666
|
+
* console.log('Message:', payload)
|
|
4415
4667
|
* })
|
|
4416
4668
|
* .subscribe()
|
|
4417
4669
|
* ```
|
|
4418
4670
|
*
|
|
4419
4671
|
* @category Realtime
|
|
4420
4672
|
*/
|
|
4421
|
-
channel(name) {
|
|
4422
|
-
return this.realtime.channel(name);
|
|
4673
|
+
channel(name, config) {
|
|
4674
|
+
return this.realtime.channel(name, config);
|
|
4675
|
+
}
|
|
4676
|
+
/**
|
|
4677
|
+
* Remove a realtime channel (Supabase-compatible)
|
|
4678
|
+
*
|
|
4679
|
+
* @param channel - The channel to remove
|
|
4680
|
+
* @returns Promise resolving to status
|
|
4681
|
+
*
|
|
4682
|
+
* @example
|
|
4683
|
+
* ```typescript
|
|
4684
|
+
* const channel = client.channel('room-1')
|
|
4685
|
+
* await client.removeChannel(channel)
|
|
4686
|
+
* ```
|
|
4687
|
+
*
|
|
4688
|
+
* @category Realtime
|
|
4689
|
+
*/
|
|
4690
|
+
removeChannel(channel) {
|
|
4691
|
+
return this.realtime.removeChannel(channel);
|
|
4423
4692
|
}
|
|
4424
4693
|
/**
|
|
4425
4694
|
* Get the internal HTTP client
|