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