@blinkdotnew/sdk 0.14.5 → 0.14.7
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/README.md +6 -5
- package/dist/index.d.mts +30 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +244 -61
- package/dist/index.mjs +244 -61
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -955,6 +955,9 @@ blink.analytics.clearAttribution()
|
|
|
955
955
|
|
|
956
956
|
### Realtime Operations
|
|
957
957
|
|
|
958
|
+
**🎉 Zero-Boilerplate Connection Management!**
|
|
959
|
+
All connection states, queuing, and reconnection are handled automatically. No more "CONNECTING state" errors!
|
|
960
|
+
|
|
958
961
|
```typescript
|
|
959
962
|
// 🔥 Real-time Messaging & Presence (NEW!)
|
|
960
963
|
// Perfect for chat apps, live collaboration, multiplayer games, and live updates
|
|
@@ -1317,17 +1320,15 @@ useEffect(() => {
|
|
|
1317
1320
|
}
|
|
1318
1321
|
}, [user.id]) // ✅ Only user.id dependency
|
|
1319
1322
|
|
|
1320
|
-
//
|
|
1321
|
-
|
|
1322
|
-
await blink.realtime.publish('room', 'message', data)
|
|
1323
|
-
}
|
|
1323
|
+
// Publish works automatically - no connection checks needed!
|
|
1324
|
+
await blink.realtime.publish('room', 'message', data)
|
|
1324
1325
|
```
|
|
1325
1326
|
|
|
1326
1327
|
**Rules:**
|
|
1327
1328
|
1. **useEffect dependency**: `[user.id]` not `[user]`
|
|
1328
1329
|
2. **Use useRef**: Store user data to avoid re-connections
|
|
1329
1330
|
3. **Add isMounted**: Prevent operations on unmounted components
|
|
1330
|
-
4. **
|
|
1331
|
+
4. **Zero connection management**: SDK handles all connection states automatically
|
|
1331
1332
|
|
|
1332
1333
|
### React
|
|
1333
1334
|
|
package/dist/index.d.mts
CHANGED
|
@@ -469,6 +469,7 @@ interface RealtimeChannel {
|
|
|
469
469
|
before?: string;
|
|
470
470
|
after?: string;
|
|
471
471
|
}): Promise<RealtimeMessage[]>;
|
|
472
|
+
isReady(): boolean;
|
|
472
473
|
}
|
|
473
474
|
interface RealtimeSubscribeOptions {
|
|
474
475
|
userId?: string;
|
|
@@ -1512,10 +1513,19 @@ declare class BlinkRealtimeChannel implements RealtimeChannel {
|
|
|
1512
1513
|
private presenceCallbacks;
|
|
1513
1514
|
private websocket;
|
|
1514
1515
|
private isSubscribed;
|
|
1516
|
+
private isConnected;
|
|
1517
|
+
private isConnecting;
|
|
1515
1518
|
private reconnectTimer;
|
|
1516
1519
|
private heartbeatTimer;
|
|
1517
1520
|
private reconnectAttempts;
|
|
1521
|
+
private messageQueue;
|
|
1522
|
+
private pendingSubscription;
|
|
1523
|
+
private connectionPromise;
|
|
1518
1524
|
constructor(channelName: string, httpClient: HttpClient, projectId: string);
|
|
1525
|
+
/**
|
|
1526
|
+
* Check if channel is ready for publishing
|
|
1527
|
+
*/
|
|
1528
|
+
isReady(): boolean;
|
|
1519
1529
|
subscribe(options?: {
|
|
1520
1530
|
userId?: string;
|
|
1521
1531
|
metadata?: Record<string, any>;
|
|
@@ -1533,7 +1543,27 @@ declare class BlinkRealtimeChannel implements RealtimeChannel {
|
|
|
1533
1543
|
before?: string;
|
|
1534
1544
|
after?: string;
|
|
1535
1545
|
}): Promise<RealtimeMessage[]>;
|
|
1546
|
+
/**
|
|
1547
|
+
* Ensure WebSocket connection is established and ready
|
|
1548
|
+
*/
|
|
1549
|
+
private ensureConnected;
|
|
1550
|
+
/**
|
|
1551
|
+
* Send a message, queuing if socket not ready
|
|
1552
|
+
*/
|
|
1553
|
+
private sendMessage;
|
|
1554
|
+
/**
|
|
1555
|
+
* Send a queued message and set up response handling
|
|
1556
|
+
*/
|
|
1557
|
+
private sendQueuedMessage;
|
|
1558
|
+
/**
|
|
1559
|
+
* Flush all queued messages when connection becomes ready
|
|
1560
|
+
*/
|
|
1561
|
+
private flushMessageQueue;
|
|
1536
1562
|
private connectWebSocket;
|
|
1563
|
+
/**
|
|
1564
|
+
* Reject all queued messages with the given error
|
|
1565
|
+
*/
|
|
1566
|
+
private rejectQueuedMessages;
|
|
1537
1567
|
private handleWebSocketMessage;
|
|
1538
1568
|
private startHeartbeat;
|
|
1539
1569
|
private scheduleReconnect;
|
package/dist/index.d.ts
CHANGED
|
@@ -469,6 +469,7 @@ interface RealtimeChannel {
|
|
|
469
469
|
before?: string;
|
|
470
470
|
after?: string;
|
|
471
471
|
}): Promise<RealtimeMessage[]>;
|
|
472
|
+
isReady(): boolean;
|
|
472
473
|
}
|
|
473
474
|
interface RealtimeSubscribeOptions {
|
|
474
475
|
userId?: string;
|
|
@@ -1512,10 +1513,19 @@ declare class BlinkRealtimeChannel implements RealtimeChannel {
|
|
|
1512
1513
|
private presenceCallbacks;
|
|
1513
1514
|
private websocket;
|
|
1514
1515
|
private isSubscribed;
|
|
1516
|
+
private isConnected;
|
|
1517
|
+
private isConnecting;
|
|
1515
1518
|
private reconnectTimer;
|
|
1516
1519
|
private heartbeatTimer;
|
|
1517
1520
|
private reconnectAttempts;
|
|
1521
|
+
private messageQueue;
|
|
1522
|
+
private pendingSubscription;
|
|
1523
|
+
private connectionPromise;
|
|
1518
1524
|
constructor(channelName: string, httpClient: HttpClient, projectId: string);
|
|
1525
|
+
/**
|
|
1526
|
+
* Check if channel is ready for publishing
|
|
1527
|
+
*/
|
|
1528
|
+
isReady(): boolean;
|
|
1519
1529
|
subscribe(options?: {
|
|
1520
1530
|
userId?: string;
|
|
1521
1531
|
metadata?: Record<string, any>;
|
|
@@ -1533,7 +1543,27 @@ declare class BlinkRealtimeChannel implements RealtimeChannel {
|
|
|
1533
1543
|
before?: string;
|
|
1534
1544
|
after?: string;
|
|
1535
1545
|
}): Promise<RealtimeMessage[]>;
|
|
1546
|
+
/**
|
|
1547
|
+
* Ensure WebSocket connection is established and ready
|
|
1548
|
+
*/
|
|
1549
|
+
private ensureConnected;
|
|
1550
|
+
/**
|
|
1551
|
+
* Send a message, queuing if socket not ready
|
|
1552
|
+
*/
|
|
1553
|
+
private sendMessage;
|
|
1554
|
+
/**
|
|
1555
|
+
* Send a queued message and set up response handling
|
|
1556
|
+
*/
|
|
1557
|
+
private sendQueuedMessage;
|
|
1558
|
+
/**
|
|
1559
|
+
* Flush all queued messages when connection becomes ready
|
|
1560
|
+
*/
|
|
1561
|
+
private flushMessageQueue;
|
|
1536
1562
|
private connectWebSocket;
|
|
1563
|
+
/**
|
|
1564
|
+
* Reject all queued messages with the given error
|
|
1565
|
+
*/
|
|
1566
|
+
private rejectQueuedMessages;
|
|
1537
1567
|
private handleWebSocketMessage;
|
|
1538
1568
|
private startHeartbeat;
|
|
1539
1569
|
private scheduleReconnect;
|
package/dist/index.js
CHANGED
|
@@ -1274,7 +1274,13 @@ var BlinkAuth = class {
|
|
|
1274
1274
|
*/
|
|
1275
1275
|
onAuthStateChanged(callback) {
|
|
1276
1276
|
this.listeners.add(callback);
|
|
1277
|
-
|
|
1277
|
+
queueMicrotask(() => {
|
|
1278
|
+
try {
|
|
1279
|
+
callback(this.authState);
|
|
1280
|
+
} catch (error) {
|
|
1281
|
+
console.error("Error in auth state change callback:", error);
|
|
1282
|
+
}
|
|
1283
|
+
});
|
|
1278
1284
|
return () => {
|
|
1279
1285
|
this.listeners.delete(callback);
|
|
1280
1286
|
};
|
|
@@ -2751,40 +2757,77 @@ var BlinkRealtimeChannel = class {
|
|
|
2751
2757
|
presenceCallbacks = [];
|
|
2752
2758
|
websocket = null;
|
|
2753
2759
|
isSubscribed = false;
|
|
2760
|
+
isConnected = false;
|
|
2761
|
+
isConnecting = false;
|
|
2754
2762
|
reconnectTimer = null;
|
|
2755
2763
|
heartbeatTimer = null;
|
|
2756
2764
|
reconnectAttempts = 0;
|
|
2765
|
+
// Message queuing for when socket is not ready
|
|
2766
|
+
messageQueue = [];
|
|
2767
|
+
pendingSubscription = null;
|
|
2768
|
+
// Connection promise for awaiting readiness
|
|
2769
|
+
connectionPromise = null;
|
|
2770
|
+
/**
|
|
2771
|
+
* Check if channel is ready for publishing
|
|
2772
|
+
*/
|
|
2773
|
+
isReady() {
|
|
2774
|
+
return this.isConnected && this.isSubscribed;
|
|
2775
|
+
}
|
|
2757
2776
|
async subscribe(options = {}) {
|
|
2758
2777
|
if (this.isSubscribed) {
|
|
2759
2778
|
return;
|
|
2760
2779
|
}
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
if (this.
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
payload: {
|
|
2767
|
-
channel: this.channelName,
|
|
2768
|
-
userId: options.userId,
|
|
2769
|
-
metadata: options.metadata
|
|
2770
|
-
}
|
|
2771
|
-
};
|
|
2772
|
-
this.websocket.send(JSON.stringify(subscribeMessage));
|
|
2773
|
-
this.isSubscribed = true;
|
|
2774
|
-
this.startHeartbeat();
|
|
2780
|
+
await this.ensureConnected();
|
|
2781
|
+
return new Promise((resolve, reject) => {
|
|
2782
|
+
if (this.pendingSubscription) {
|
|
2783
|
+
clearTimeout(this.pendingSubscription.timeout);
|
|
2784
|
+
this.pendingSubscription.reject(new BlinkRealtimeError("Subscription cancelled by new subscription request"));
|
|
2775
2785
|
}
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2786
|
+
const timeout = setTimeout(() => {
|
|
2787
|
+
this.pendingSubscription = null;
|
|
2788
|
+
reject(new BlinkRealtimeError("Subscription timeout - no acknowledgment from server"));
|
|
2789
|
+
}, 1e4);
|
|
2790
|
+
this.pendingSubscription = {
|
|
2791
|
+
options,
|
|
2792
|
+
resolve: () => {
|
|
2793
|
+
clearTimeout(timeout);
|
|
2794
|
+
this.pendingSubscription = null;
|
|
2795
|
+
this.isSubscribed = true;
|
|
2796
|
+
this.startHeartbeat();
|
|
2797
|
+
resolve();
|
|
2798
|
+
},
|
|
2799
|
+
reject: (error) => {
|
|
2800
|
+
clearTimeout(timeout);
|
|
2801
|
+
this.pendingSubscription = null;
|
|
2802
|
+
reject(error);
|
|
2803
|
+
},
|
|
2804
|
+
timeout
|
|
2805
|
+
};
|
|
2806
|
+
const subscribeMessage = {
|
|
2807
|
+
type: "subscribe",
|
|
2808
|
+
payload: {
|
|
2809
|
+
channel: this.channelName,
|
|
2810
|
+
userId: options.userId,
|
|
2811
|
+
metadata: options.metadata
|
|
2812
|
+
}
|
|
2813
|
+
};
|
|
2814
|
+
this.sendMessage(JSON.stringify(subscribeMessage)).catch((error) => {
|
|
2815
|
+
if (this.pendingSubscription) {
|
|
2816
|
+
this.pendingSubscription.reject(error);
|
|
2817
|
+
}
|
|
2818
|
+
});
|
|
2819
|
+
});
|
|
2782
2820
|
}
|
|
2783
2821
|
async unsubscribe() {
|
|
2784
2822
|
if (!this.isSubscribed) {
|
|
2785
2823
|
return;
|
|
2786
2824
|
}
|
|
2787
|
-
if (this.
|
|
2825
|
+
if (this.pendingSubscription) {
|
|
2826
|
+
clearTimeout(this.pendingSubscription.timeout);
|
|
2827
|
+
this.pendingSubscription.reject(new BlinkRealtimeError("Subscription cancelled by unsubscribe"));
|
|
2828
|
+
this.pendingSubscription = null;
|
|
2829
|
+
}
|
|
2830
|
+
if (this.websocket && this.websocket.readyState === 1) {
|
|
2788
2831
|
const unsubscribeMessage = {
|
|
2789
2832
|
type: "unsubscribe",
|
|
2790
2833
|
payload: {
|
|
@@ -2796,42 +2839,18 @@ var BlinkRealtimeChannel = class {
|
|
|
2796
2839
|
this.cleanup();
|
|
2797
2840
|
}
|
|
2798
2841
|
async publish(type, data, options = {}) {
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
clearTimeout(timeout);
|
|
2812
|
-
this.websocket.removeEventListener("message", handleResponse);
|
|
2813
|
-
resolve(message.payload.messageId);
|
|
2814
|
-
} else if (message.type === "error") {
|
|
2815
|
-
clearTimeout(timeout);
|
|
2816
|
-
this.websocket.removeEventListener("message", handleResponse);
|
|
2817
|
-
reject(new BlinkRealtimeError(`Server error: ${message.payload.error}`));
|
|
2818
|
-
}
|
|
2819
|
-
} catch (err) {
|
|
2820
|
-
}
|
|
2821
|
-
};
|
|
2822
|
-
this.websocket.addEventListener("message", handleResponse);
|
|
2823
|
-
const publishMessage = {
|
|
2824
|
-
type: "publish",
|
|
2825
|
-
payload: {
|
|
2826
|
-
channel: this.channelName,
|
|
2827
|
-
type,
|
|
2828
|
-
data,
|
|
2829
|
-
userId: options.userId,
|
|
2830
|
-
metadata: options.metadata
|
|
2831
|
-
}
|
|
2832
|
-
};
|
|
2833
|
-
this.websocket.send(JSON.stringify(publishMessage));
|
|
2834
|
-
});
|
|
2842
|
+
await this.ensureConnected();
|
|
2843
|
+
const publishMessage = {
|
|
2844
|
+
type: "publish",
|
|
2845
|
+
payload: {
|
|
2846
|
+
channel: this.channelName,
|
|
2847
|
+
type,
|
|
2848
|
+
data,
|
|
2849
|
+
userId: options.userId,
|
|
2850
|
+
metadata: options.metadata
|
|
2851
|
+
}
|
|
2852
|
+
};
|
|
2853
|
+
return this.sendMessage(JSON.stringify(publishMessage));
|
|
2835
2854
|
}
|
|
2836
2855
|
onMessage(callback) {
|
|
2837
2856
|
this.messageCallbacks.push(callback);
|
|
@@ -2878,10 +2897,130 @@ var BlinkRealtimeChannel = class {
|
|
|
2878
2897
|
);
|
|
2879
2898
|
}
|
|
2880
2899
|
}
|
|
2900
|
+
/**
|
|
2901
|
+
* Ensure WebSocket connection is established and ready
|
|
2902
|
+
*/
|
|
2903
|
+
async ensureConnected() {
|
|
2904
|
+
if (this.isConnected && this.websocket?.readyState === 1) {
|
|
2905
|
+
return;
|
|
2906
|
+
}
|
|
2907
|
+
if (this.connectionPromise) {
|
|
2908
|
+
return this.connectionPromise;
|
|
2909
|
+
}
|
|
2910
|
+
this.connectionPromise = this.connectWebSocket();
|
|
2911
|
+
try {
|
|
2912
|
+
await this.connectionPromise;
|
|
2913
|
+
} finally {
|
|
2914
|
+
this.connectionPromise = null;
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
/**
|
|
2918
|
+
* Send a message, queuing if socket not ready
|
|
2919
|
+
*/
|
|
2920
|
+
sendMessage(message) {
|
|
2921
|
+
return new Promise((resolve, reject) => {
|
|
2922
|
+
let messageObj;
|
|
2923
|
+
try {
|
|
2924
|
+
messageObj = JSON.parse(message);
|
|
2925
|
+
} catch (error) {
|
|
2926
|
+
reject(new BlinkRealtimeError("Invalid message format"));
|
|
2927
|
+
return;
|
|
2928
|
+
}
|
|
2929
|
+
const timeout = setTimeout(() => {
|
|
2930
|
+
const index = this.messageQueue.findIndex((q) => q.resolve === resolve);
|
|
2931
|
+
if (index > -1) {
|
|
2932
|
+
this.messageQueue.splice(index, 1);
|
|
2933
|
+
}
|
|
2934
|
+
reject(new BlinkRealtimeError("Message send timeout - no response from server"));
|
|
2935
|
+
}, 1e4);
|
|
2936
|
+
const queuedMessage = {
|
|
2937
|
+
message,
|
|
2938
|
+
resolve,
|
|
2939
|
+
reject,
|
|
2940
|
+
timeout
|
|
2941
|
+
};
|
|
2942
|
+
if (this.websocket && this.websocket.readyState === 1) {
|
|
2943
|
+
if (messageObj.type === "publish") {
|
|
2944
|
+
this.sendQueuedMessage(queuedMessage);
|
|
2945
|
+
} else {
|
|
2946
|
+
this.websocket.send(message);
|
|
2947
|
+
clearTimeout(timeout);
|
|
2948
|
+
resolve("sent");
|
|
2949
|
+
}
|
|
2950
|
+
} else {
|
|
2951
|
+
this.messageQueue.push(queuedMessage);
|
|
2952
|
+
}
|
|
2953
|
+
});
|
|
2954
|
+
}
|
|
2955
|
+
/**
|
|
2956
|
+
* Send a queued message and set up response handling
|
|
2957
|
+
*/
|
|
2958
|
+
sendQueuedMessage(queuedMessage) {
|
|
2959
|
+
const { message, resolve, reject, timeout } = queuedMessage;
|
|
2960
|
+
const handleResponse = (event) => {
|
|
2961
|
+
try {
|
|
2962
|
+
const response = JSON.parse(event.data);
|
|
2963
|
+
if (response.type === "published" && response.payload.channel === this.channelName) {
|
|
2964
|
+
clearTimeout(timeout);
|
|
2965
|
+
this.websocket.removeEventListener("message", handleResponse);
|
|
2966
|
+
resolve(response.payload.messageId);
|
|
2967
|
+
} else if (response.type === "error") {
|
|
2968
|
+
clearTimeout(timeout);
|
|
2969
|
+
this.websocket.removeEventListener("message", handleResponse);
|
|
2970
|
+
reject(new BlinkRealtimeError(`Server error: ${response.payload.error}`));
|
|
2971
|
+
}
|
|
2972
|
+
} catch (err) {
|
|
2973
|
+
}
|
|
2974
|
+
};
|
|
2975
|
+
this.websocket.addEventListener("message", handleResponse);
|
|
2976
|
+
this.websocket.send(message);
|
|
2977
|
+
}
|
|
2978
|
+
/**
|
|
2979
|
+
* Flush all queued messages when connection becomes ready
|
|
2980
|
+
*/
|
|
2981
|
+
flushMessageQueue() {
|
|
2982
|
+
if (!this.websocket || this.websocket.readyState !== 1) {
|
|
2983
|
+
return;
|
|
2984
|
+
}
|
|
2985
|
+
const queue = [...this.messageQueue];
|
|
2986
|
+
this.messageQueue = [];
|
|
2987
|
+
queue.forEach((queuedMessage) => {
|
|
2988
|
+
try {
|
|
2989
|
+
const messageObj = JSON.parse(queuedMessage.message);
|
|
2990
|
+
if (messageObj.type === "publish") {
|
|
2991
|
+
this.sendQueuedMessage(queuedMessage);
|
|
2992
|
+
} else {
|
|
2993
|
+
this.websocket.send(queuedMessage.message);
|
|
2994
|
+
clearTimeout(queuedMessage.timeout);
|
|
2995
|
+
queuedMessage.resolve("sent");
|
|
2996
|
+
}
|
|
2997
|
+
} catch (error) {
|
|
2998
|
+
clearTimeout(queuedMessage.timeout);
|
|
2999
|
+
queuedMessage.reject(new BlinkRealtimeError("Invalid queued message format"));
|
|
3000
|
+
}
|
|
3001
|
+
});
|
|
3002
|
+
}
|
|
2881
3003
|
async connectWebSocket() {
|
|
2882
3004
|
if (this.websocket && this.websocket.readyState === 1) {
|
|
3005
|
+
this.isConnected = true;
|
|
2883
3006
|
return;
|
|
2884
3007
|
}
|
|
3008
|
+
if (this.isConnecting) {
|
|
3009
|
+
return new Promise((resolve, reject) => {
|
|
3010
|
+
const checkConnection = () => {
|
|
3011
|
+
if (this.isConnected) {
|
|
3012
|
+
resolve();
|
|
3013
|
+
} else if (!this.isConnecting) {
|
|
3014
|
+
reject(new BlinkRealtimeError("Connection failed"));
|
|
3015
|
+
} else {
|
|
3016
|
+
setTimeout(checkConnection, 100);
|
|
3017
|
+
}
|
|
3018
|
+
};
|
|
3019
|
+
checkConnection();
|
|
3020
|
+
});
|
|
3021
|
+
}
|
|
3022
|
+
this.isConnecting = true;
|
|
3023
|
+
this.isConnected = false;
|
|
2885
3024
|
return new Promise((resolve, reject) => {
|
|
2886
3025
|
try {
|
|
2887
3026
|
const httpClient = this.httpClient;
|
|
@@ -2892,12 +3031,16 @@ var BlinkRealtimeChannel = class {
|
|
|
2892
3031
|
const WSClass = getWebSocketClass();
|
|
2893
3032
|
this.websocket = new WSClass(wsUrl);
|
|
2894
3033
|
if (!this.websocket) {
|
|
3034
|
+
this.isConnecting = false;
|
|
2895
3035
|
reject(new BlinkRealtimeError("Failed to create WebSocket instance"));
|
|
2896
3036
|
return;
|
|
2897
3037
|
}
|
|
2898
3038
|
this.websocket.onopen = () => {
|
|
2899
3039
|
console.log(`\u{1F517} Connected to realtime for project ${this.projectId}`);
|
|
3040
|
+
this.isConnecting = false;
|
|
3041
|
+
this.isConnected = true;
|
|
2900
3042
|
this.reconnectAttempts = 0;
|
|
3043
|
+
this.flushMessageQueue();
|
|
2901
3044
|
resolve();
|
|
2902
3045
|
};
|
|
2903
3046
|
this.websocket.onmessage = (event) => {
|
|
@@ -2910,25 +3053,48 @@ var BlinkRealtimeChannel = class {
|
|
|
2910
3053
|
};
|
|
2911
3054
|
this.websocket.onclose = () => {
|
|
2912
3055
|
console.log(`\u{1F50C} Disconnected from realtime for project ${this.projectId}`);
|
|
3056
|
+
this.isConnecting = false;
|
|
3057
|
+
this.isConnected = false;
|
|
2913
3058
|
this.isSubscribed = false;
|
|
3059
|
+
this.rejectQueuedMessages(new BlinkRealtimeError("WebSocket connection closed"));
|
|
3060
|
+
if (this.pendingSubscription) {
|
|
3061
|
+
clearTimeout(this.pendingSubscription.timeout);
|
|
3062
|
+
this.pendingSubscription.reject(new BlinkRealtimeError("Connection closed during subscription"));
|
|
3063
|
+
this.pendingSubscription = null;
|
|
3064
|
+
}
|
|
2914
3065
|
this.scheduleReconnect();
|
|
2915
3066
|
};
|
|
2916
3067
|
this.websocket.onerror = (error) => {
|
|
2917
3068
|
console.error("WebSocket error:", error);
|
|
2918
3069
|
console.error("WebSocket URL was:", wsUrl);
|
|
2919
3070
|
console.error("WebSocket readyState:", this.websocket?.readyState);
|
|
3071
|
+
this.isConnecting = false;
|
|
3072
|
+
this.isConnected = false;
|
|
2920
3073
|
reject(new BlinkRealtimeError(`WebSocket connection failed to ${wsUrl}`));
|
|
2921
3074
|
};
|
|
2922
3075
|
setTimeout(() => {
|
|
2923
3076
|
if (this.websocket?.readyState !== 1) {
|
|
3077
|
+
this.isConnecting = false;
|
|
2924
3078
|
reject(new BlinkRealtimeError("WebSocket connection timeout"));
|
|
2925
3079
|
}
|
|
2926
|
-
},
|
|
3080
|
+
}, 1e4);
|
|
2927
3081
|
} catch (error) {
|
|
3082
|
+
this.isConnecting = false;
|
|
2928
3083
|
reject(new BlinkRealtimeError(`Failed to create WebSocket connection: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
2929
3084
|
}
|
|
2930
3085
|
});
|
|
2931
3086
|
}
|
|
3087
|
+
/**
|
|
3088
|
+
* Reject all queued messages with the given error
|
|
3089
|
+
*/
|
|
3090
|
+
rejectQueuedMessages(error) {
|
|
3091
|
+
const queue = [...this.messageQueue];
|
|
3092
|
+
this.messageQueue = [];
|
|
3093
|
+
queue.forEach((queuedMessage) => {
|
|
3094
|
+
clearTimeout(queuedMessage.timeout);
|
|
3095
|
+
queuedMessage.reject(error);
|
|
3096
|
+
});
|
|
3097
|
+
}
|
|
2932
3098
|
handleWebSocketMessage(message) {
|
|
2933
3099
|
switch (message.type) {
|
|
2934
3100
|
case "message":
|
|
@@ -2952,6 +3118,9 @@ var BlinkRealtimeChannel = class {
|
|
|
2952
3118
|
break;
|
|
2953
3119
|
case "subscribed":
|
|
2954
3120
|
console.log(`\u2705 Subscribed to channel: ${message.payload.channel}`);
|
|
3121
|
+
if (this.pendingSubscription && message.payload.channel === this.channelName) {
|
|
3122
|
+
this.pendingSubscription.resolve();
|
|
3123
|
+
}
|
|
2955
3124
|
break;
|
|
2956
3125
|
case "unsubscribed":
|
|
2957
3126
|
console.log(`\u274C Unsubscribed from channel: ${message.payload.channel}`);
|
|
@@ -2962,6 +3131,9 @@ var BlinkRealtimeChannel = class {
|
|
|
2962
3131
|
break;
|
|
2963
3132
|
case "error":
|
|
2964
3133
|
console.error("Realtime error:", message.payload.error);
|
|
3134
|
+
if (this.pendingSubscription && message.payload.channel === this.channelName) {
|
|
3135
|
+
this.pendingSubscription.reject(new BlinkRealtimeError(`Subscription error: ${message.payload.error}`));
|
|
3136
|
+
}
|
|
2965
3137
|
break;
|
|
2966
3138
|
default:
|
|
2967
3139
|
console.log("Unknown message type:", message.type);
|
|
@@ -2981,16 +3153,19 @@ var BlinkRealtimeChannel = class {
|
|
|
2981
3153
|
if (this.reconnectTimer) {
|
|
2982
3154
|
clearTimeout(this.reconnectTimer);
|
|
2983
3155
|
}
|
|
3156
|
+
if (!this.isSubscribed && !this.pendingSubscription) {
|
|
3157
|
+
return;
|
|
3158
|
+
}
|
|
2984
3159
|
this.reconnectAttempts++;
|
|
2985
3160
|
const baseDelay = Math.min(3e4, Math.pow(2, this.reconnectAttempts) * 1e3);
|
|
2986
3161
|
const jitter = Math.random() * 1e3;
|
|
2987
3162
|
const delay = baseDelay + jitter;
|
|
2988
3163
|
console.log(`\u{1F504} Scheduling reconnect attempt ${this.reconnectAttempts} in ${Math.round(delay)}ms`);
|
|
2989
3164
|
this.reconnectTimer = globalThis.setTimeout(async () => {
|
|
2990
|
-
if (this.isSubscribed) {
|
|
3165
|
+
if (this.isSubscribed || this.pendingSubscription) {
|
|
2991
3166
|
try {
|
|
2992
3167
|
await this.connectWebSocket();
|
|
2993
|
-
if (this.websocket) {
|
|
3168
|
+
if (this.isSubscribed && this.websocket) {
|
|
2994
3169
|
const subscribeMessage = {
|
|
2995
3170
|
type: "subscribe",
|
|
2996
3171
|
payload: {
|
|
@@ -3009,6 +3184,14 @@ var BlinkRealtimeChannel = class {
|
|
|
3009
3184
|
}
|
|
3010
3185
|
cleanup() {
|
|
3011
3186
|
this.isSubscribed = false;
|
|
3187
|
+
this.isConnected = false;
|
|
3188
|
+
this.isConnecting = false;
|
|
3189
|
+
if (this.pendingSubscription) {
|
|
3190
|
+
clearTimeout(this.pendingSubscription.timeout);
|
|
3191
|
+
this.pendingSubscription.reject(new BlinkRealtimeError("Channel cleanup"));
|
|
3192
|
+
this.pendingSubscription = null;
|
|
3193
|
+
}
|
|
3194
|
+
this.rejectQueuedMessages(new BlinkRealtimeError("Channel cleanup"));
|
|
3012
3195
|
if (this.heartbeatTimer) {
|
|
3013
3196
|
clearInterval(this.heartbeatTimer);
|
|
3014
3197
|
this.heartbeatTimer = null;
|
package/dist/index.mjs
CHANGED
|
@@ -1272,7 +1272,13 @@ var BlinkAuth = class {
|
|
|
1272
1272
|
*/
|
|
1273
1273
|
onAuthStateChanged(callback) {
|
|
1274
1274
|
this.listeners.add(callback);
|
|
1275
|
-
|
|
1275
|
+
queueMicrotask(() => {
|
|
1276
|
+
try {
|
|
1277
|
+
callback(this.authState);
|
|
1278
|
+
} catch (error) {
|
|
1279
|
+
console.error("Error in auth state change callback:", error);
|
|
1280
|
+
}
|
|
1281
|
+
});
|
|
1276
1282
|
return () => {
|
|
1277
1283
|
this.listeners.delete(callback);
|
|
1278
1284
|
};
|
|
@@ -2749,40 +2755,77 @@ var BlinkRealtimeChannel = class {
|
|
|
2749
2755
|
presenceCallbacks = [];
|
|
2750
2756
|
websocket = null;
|
|
2751
2757
|
isSubscribed = false;
|
|
2758
|
+
isConnected = false;
|
|
2759
|
+
isConnecting = false;
|
|
2752
2760
|
reconnectTimer = null;
|
|
2753
2761
|
heartbeatTimer = null;
|
|
2754
2762
|
reconnectAttempts = 0;
|
|
2763
|
+
// Message queuing for when socket is not ready
|
|
2764
|
+
messageQueue = [];
|
|
2765
|
+
pendingSubscription = null;
|
|
2766
|
+
// Connection promise for awaiting readiness
|
|
2767
|
+
connectionPromise = null;
|
|
2768
|
+
/**
|
|
2769
|
+
* Check if channel is ready for publishing
|
|
2770
|
+
*/
|
|
2771
|
+
isReady() {
|
|
2772
|
+
return this.isConnected && this.isSubscribed;
|
|
2773
|
+
}
|
|
2755
2774
|
async subscribe(options = {}) {
|
|
2756
2775
|
if (this.isSubscribed) {
|
|
2757
2776
|
return;
|
|
2758
2777
|
}
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
if (this.
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
payload: {
|
|
2765
|
-
channel: this.channelName,
|
|
2766
|
-
userId: options.userId,
|
|
2767
|
-
metadata: options.metadata
|
|
2768
|
-
}
|
|
2769
|
-
};
|
|
2770
|
-
this.websocket.send(JSON.stringify(subscribeMessage));
|
|
2771
|
-
this.isSubscribed = true;
|
|
2772
|
-
this.startHeartbeat();
|
|
2778
|
+
await this.ensureConnected();
|
|
2779
|
+
return new Promise((resolve, reject) => {
|
|
2780
|
+
if (this.pendingSubscription) {
|
|
2781
|
+
clearTimeout(this.pendingSubscription.timeout);
|
|
2782
|
+
this.pendingSubscription.reject(new BlinkRealtimeError("Subscription cancelled by new subscription request"));
|
|
2773
2783
|
}
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2784
|
+
const timeout = setTimeout(() => {
|
|
2785
|
+
this.pendingSubscription = null;
|
|
2786
|
+
reject(new BlinkRealtimeError("Subscription timeout - no acknowledgment from server"));
|
|
2787
|
+
}, 1e4);
|
|
2788
|
+
this.pendingSubscription = {
|
|
2789
|
+
options,
|
|
2790
|
+
resolve: () => {
|
|
2791
|
+
clearTimeout(timeout);
|
|
2792
|
+
this.pendingSubscription = null;
|
|
2793
|
+
this.isSubscribed = true;
|
|
2794
|
+
this.startHeartbeat();
|
|
2795
|
+
resolve();
|
|
2796
|
+
},
|
|
2797
|
+
reject: (error) => {
|
|
2798
|
+
clearTimeout(timeout);
|
|
2799
|
+
this.pendingSubscription = null;
|
|
2800
|
+
reject(error);
|
|
2801
|
+
},
|
|
2802
|
+
timeout
|
|
2803
|
+
};
|
|
2804
|
+
const subscribeMessage = {
|
|
2805
|
+
type: "subscribe",
|
|
2806
|
+
payload: {
|
|
2807
|
+
channel: this.channelName,
|
|
2808
|
+
userId: options.userId,
|
|
2809
|
+
metadata: options.metadata
|
|
2810
|
+
}
|
|
2811
|
+
};
|
|
2812
|
+
this.sendMessage(JSON.stringify(subscribeMessage)).catch((error) => {
|
|
2813
|
+
if (this.pendingSubscription) {
|
|
2814
|
+
this.pendingSubscription.reject(error);
|
|
2815
|
+
}
|
|
2816
|
+
});
|
|
2817
|
+
});
|
|
2780
2818
|
}
|
|
2781
2819
|
async unsubscribe() {
|
|
2782
2820
|
if (!this.isSubscribed) {
|
|
2783
2821
|
return;
|
|
2784
2822
|
}
|
|
2785
|
-
if (this.
|
|
2823
|
+
if (this.pendingSubscription) {
|
|
2824
|
+
clearTimeout(this.pendingSubscription.timeout);
|
|
2825
|
+
this.pendingSubscription.reject(new BlinkRealtimeError("Subscription cancelled by unsubscribe"));
|
|
2826
|
+
this.pendingSubscription = null;
|
|
2827
|
+
}
|
|
2828
|
+
if (this.websocket && this.websocket.readyState === 1) {
|
|
2786
2829
|
const unsubscribeMessage = {
|
|
2787
2830
|
type: "unsubscribe",
|
|
2788
2831
|
payload: {
|
|
@@ -2794,42 +2837,18 @@ var BlinkRealtimeChannel = class {
|
|
|
2794
2837
|
this.cleanup();
|
|
2795
2838
|
}
|
|
2796
2839
|
async publish(type, data, options = {}) {
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
clearTimeout(timeout);
|
|
2810
|
-
this.websocket.removeEventListener("message", handleResponse);
|
|
2811
|
-
resolve(message.payload.messageId);
|
|
2812
|
-
} else if (message.type === "error") {
|
|
2813
|
-
clearTimeout(timeout);
|
|
2814
|
-
this.websocket.removeEventListener("message", handleResponse);
|
|
2815
|
-
reject(new BlinkRealtimeError(`Server error: ${message.payload.error}`));
|
|
2816
|
-
}
|
|
2817
|
-
} catch (err) {
|
|
2818
|
-
}
|
|
2819
|
-
};
|
|
2820
|
-
this.websocket.addEventListener("message", handleResponse);
|
|
2821
|
-
const publishMessage = {
|
|
2822
|
-
type: "publish",
|
|
2823
|
-
payload: {
|
|
2824
|
-
channel: this.channelName,
|
|
2825
|
-
type,
|
|
2826
|
-
data,
|
|
2827
|
-
userId: options.userId,
|
|
2828
|
-
metadata: options.metadata
|
|
2829
|
-
}
|
|
2830
|
-
};
|
|
2831
|
-
this.websocket.send(JSON.stringify(publishMessage));
|
|
2832
|
-
});
|
|
2840
|
+
await this.ensureConnected();
|
|
2841
|
+
const publishMessage = {
|
|
2842
|
+
type: "publish",
|
|
2843
|
+
payload: {
|
|
2844
|
+
channel: this.channelName,
|
|
2845
|
+
type,
|
|
2846
|
+
data,
|
|
2847
|
+
userId: options.userId,
|
|
2848
|
+
metadata: options.metadata
|
|
2849
|
+
}
|
|
2850
|
+
};
|
|
2851
|
+
return this.sendMessage(JSON.stringify(publishMessage));
|
|
2833
2852
|
}
|
|
2834
2853
|
onMessage(callback) {
|
|
2835
2854
|
this.messageCallbacks.push(callback);
|
|
@@ -2876,10 +2895,130 @@ var BlinkRealtimeChannel = class {
|
|
|
2876
2895
|
);
|
|
2877
2896
|
}
|
|
2878
2897
|
}
|
|
2898
|
+
/**
|
|
2899
|
+
* Ensure WebSocket connection is established and ready
|
|
2900
|
+
*/
|
|
2901
|
+
async ensureConnected() {
|
|
2902
|
+
if (this.isConnected && this.websocket?.readyState === 1) {
|
|
2903
|
+
return;
|
|
2904
|
+
}
|
|
2905
|
+
if (this.connectionPromise) {
|
|
2906
|
+
return this.connectionPromise;
|
|
2907
|
+
}
|
|
2908
|
+
this.connectionPromise = this.connectWebSocket();
|
|
2909
|
+
try {
|
|
2910
|
+
await this.connectionPromise;
|
|
2911
|
+
} finally {
|
|
2912
|
+
this.connectionPromise = null;
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
/**
|
|
2916
|
+
* Send a message, queuing if socket not ready
|
|
2917
|
+
*/
|
|
2918
|
+
sendMessage(message) {
|
|
2919
|
+
return new Promise((resolve, reject) => {
|
|
2920
|
+
let messageObj;
|
|
2921
|
+
try {
|
|
2922
|
+
messageObj = JSON.parse(message);
|
|
2923
|
+
} catch (error) {
|
|
2924
|
+
reject(new BlinkRealtimeError("Invalid message format"));
|
|
2925
|
+
return;
|
|
2926
|
+
}
|
|
2927
|
+
const timeout = setTimeout(() => {
|
|
2928
|
+
const index = this.messageQueue.findIndex((q) => q.resolve === resolve);
|
|
2929
|
+
if (index > -1) {
|
|
2930
|
+
this.messageQueue.splice(index, 1);
|
|
2931
|
+
}
|
|
2932
|
+
reject(new BlinkRealtimeError("Message send timeout - no response from server"));
|
|
2933
|
+
}, 1e4);
|
|
2934
|
+
const queuedMessage = {
|
|
2935
|
+
message,
|
|
2936
|
+
resolve,
|
|
2937
|
+
reject,
|
|
2938
|
+
timeout
|
|
2939
|
+
};
|
|
2940
|
+
if (this.websocket && this.websocket.readyState === 1) {
|
|
2941
|
+
if (messageObj.type === "publish") {
|
|
2942
|
+
this.sendQueuedMessage(queuedMessage);
|
|
2943
|
+
} else {
|
|
2944
|
+
this.websocket.send(message);
|
|
2945
|
+
clearTimeout(timeout);
|
|
2946
|
+
resolve("sent");
|
|
2947
|
+
}
|
|
2948
|
+
} else {
|
|
2949
|
+
this.messageQueue.push(queuedMessage);
|
|
2950
|
+
}
|
|
2951
|
+
});
|
|
2952
|
+
}
|
|
2953
|
+
/**
|
|
2954
|
+
* Send a queued message and set up response handling
|
|
2955
|
+
*/
|
|
2956
|
+
sendQueuedMessage(queuedMessage) {
|
|
2957
|
+
const { message, resolve, reject, timeout } = queuedMessage;
|
|
2958
|
+
const handleResponse = (event) => {
|
|
2959
|
+
try {
|
|
2960
|
+
const response = JSON.parse(event.data);
|
|
2961
|
+
if (response.type === "published" && response.payload.channel === this.channelName) {
|
|
2962
|
+
clearTimeout(timeout);
|
|
2963
|
+
this.websocket.removeEventListener("message", handleResponse);
|
|
2964
|
+
resolve(response.payload.messageId);
|
|
2965
|
+
} else if (response.type === "error") {
|
|
2966
|
+
clearTimeout(timeout);
|
|
2967
|
+
this.websocket.removeEventListener("message", handleResponse);
|
|
2968
|
+
reject(new BlinkRealtimeError(`Server error: ${response.payload.error}`));
|
|
2969
|
+
}
|
|
2970
|
+
} catch (err) {
|
|
2971
|
+
}
|
|
2972
|
+
};
|
|
2973
|
+
this.websocket.addEventListener("message", handleResponse);
|
|
2974
|
+
this.websocket.send(message);
|
|
2975
|
+
}
|
|
2976
|
+
/**
|
|
2977
|
+
* Flush all queued messages when connection becomes ready
|
|
2978
|
+
*/
|
|
2979
|
+
flushMessageQueue() {
|
|
2980
|
+
if (!this.websocket || this.websocket.readyState !== 1) {
|
|
2981
|
+
return;
|
|
2982
|
+
}
|
|
2983
|
+
const queue = [...this.messageQueue];
|
|
2984
|
+
this.messageQueue = [];
|
|
2985
|
+
queue.forEach((queuedMessage) => {
|
|
2986
|
+
try {
|
|
2987
|
+
const messageObj = JSON.parse(queuedMessage.message);
|
|
2988
|
+
if (messageObj.type === "publish") {
|
|
2989
|
+
this.sendQueuedMessage(queuedMessage);
|
|
2990
|
+
} else {
|
|
2991
|
+
this.websocket.send(queuedMessage.message);
|
|
2992
|
+
clearTimeout(queuedMessage.timeout);
|
|
2993
|
+
queuedMessage.resolve("sent");
|
|
2994
|
+
}
|
|
2995
|
+
} catch (error) {
|
|
2996
|
+
clearTimeout(queuedMessage.timeout);
|
|
2997
|
+
queuedMessage.reject(new BlinkRealtimeError("Invalid queued message format"));
|
|
2998
|
+
}
|
|
2999
|
+
});
|
|
3000
|
+
}
|
|
2879
3001
|
async connectWebSocket() {
|
|
2880
3002
|
if (this.websocket && this.websocket.readyState === 1) {
|
|
3003
|
+
this.isConnected = true;
|
|
2881
3004
|
return;
|
|
2882
3005
|
}
|
|
3006
|
+
if (this.isConnecting) {
|
|
3007
|
+
return new Promise((resolve, reject) => {
|
|
3008
|
+
const checkConnection = () => {
|
|
3009
|
+
if (this.isConnected) {
|
|
3010
|
+
resolve();
|
|
3011
|
+
} else if (!this.isConnecting) {
|
|
3012
|
+
reject(new BlinkRealtimeError("Connection failed"));
|
|
3013
|
+
} else {
|
|
3014
|
+
setTimeout(checkConnection, 100);
|
|
3015
|
+
}
|
|
3016
|
+
};
|
|
3017
|
+
checkConnection();
|
|
3018
|
+
});
|
|
3019
|
+
}
|
|
3020
|
+
this.isConnecting = true;
|
|
3021
|
+
this.isConnected = false;
|
|
2883
3022
|
return new Promise((resolve, reject) => {
|
|
2884
3023
|
try {
|
|
2885
3024
|
const httpClient = this.httpClient;
|
|
@@ -2890,12 +3029,16 @@ var BlinkRealtimeChannel = class {
|
|
|
2890
3029
|
const WSClass = getWebSocketClass();
|
|
2891
3030
|
this.websocket = new WSClass(wsUrl);
|
|
2892
3031
|
if (!this.websocket) {
|
|
3032
|
+
this.isConnecting = false;
|
|
2893
3033
|
reject(new BlinkRealtimeError("Failed to create WebSocket instance"));
|
|
2894
3034
|
return;
|
|
2895
3035
|
}
|
|
2896
3036
|
this.websocket.onopen = () => {
|
|
2897
3037
|
console.log(`\u{1F517} Connected to realtime for project ${this.projectId}`);
|
|
3038
|
+
this.isConnecting = false;
|
|
3039
|
+
this.isConnected = true;
|
|
2898
3040
|
this.reconnectAttempts = 0;
|
|
3041
|
+
this.flushMessageQueue();
|
|
2899
3042
|
resolve();
|
|
2900
3043
|
};
|
|
2901
3044
|
this.websocket.onmessage = (event) => {
|
|
@@ -2908,25 +3051,48 @@ var BlinkRealtimeChannel = class {
|
|
|
2908
3051
|
};
|
|
2909
3052
|
this.websocket.onclose = () => {
|
|
2910
3053
|
console.log(`\u{1F50C} Disconnected from realtime for project ${this.projectId}`);
|
|
3054
|
+
this.isConnecting = false;
|
|
3055
|
+
this.isConnected = false;
|
|
2911
3056
|
this.isSubscribed = false;
|
|
3057
|
+
this.rejectQueuedMessages(new BlinkRealtimeError("WebSocket connection closed"));
|
|
3058
|
+
if (this.pendingSubscription) {
|
|
3059
|
+
clearTimeout(this.pendingSubscription.timeout);
|
|
3060
|
+
this.pendingSubscription.reject(new BlinkRealtimeError("Connection closed during subscription"));
|
|
3061
|
+
this.pendingSubscription = null;
|
|
3062
|
+
}
|
|
2912
3063
|
this.scheduleReconnect();
|
|
2913
3064
|
};
|
|
2914
3065
|
this.websocket.onerror = (error) => {
|
|
2915
3066
|
console.error("WebSocket error:", error);
|
|
2916
3067
|
console.error("WebSocket URL was:", wsUrl);
|
|
2917
3068
|
console.error("WebSocket readyState:", this.websocket?.readyState);
|
|
3069
|
+
this.isConnecting = false;
|
|
3070
|
+
this.isConnected = false;
|
|
2918
3071
|
reject(new BlinkRealtimeError(`WebSocket connection failed to ${wsUrl}`));
|
|
2919
3072
|
};
|
|
2920
3073
|
setTimeout(() => {
|
|
2921
3074
|
if (this.websocket?.readyState !== 1) {
|
|
3075
|
+
this.isConnecting = false;
|
|
2922
3076
|
reject(new BlinkRealtimeError("WebSocket connection timeout"));
|
|
2923
3077
|
}
|
|
2924
|
-
},
|
|
3078
|
+
}, 1e4);
|
|
2925
3079
|
} catch (error) {
|
|
3080
|
+
this.isConnecting = false;
|
|
2926
3081
|
reject(new BlinkRealtimeError(`Failed to create WebSocket connection: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
2927
3082
|
}
|
|
2928
3083
|
});
|
|
2929
3084
|
}
|
|
3085
|
+
/**
|
|
3086
|
+
* Reject all queued messages with the given error
|
|
3087
|
+
*/
|
|
3088
|
+
rejectQueuedMessages(error) {
|
|
3089
|
+
const queue = [...this.messageQueue];
|
|
3090
|
+
this.messageQueue = [];
|
|
3091
|
+
queue.forEach((queuedMessage) => {
|
|
3092
|
+
clearTimeout(queuedMessage.timeout);
|
|
3093
|
+
queuedMessage.reject(error);
|
|
3094
|
+
});
|
|
3095
|
+
}
|
|
2930
3096
|
handleWebSocketMessage(message) {
|
|
2931
3097
|
switch (message.type) {
|
|
2932
3098
|
case "message":
|
|
@@ -2950,6 +3116,9 @@ var BlinkRealtimeChannel = class {
|
|
|
2950
3116
|
break;
|
|
2951
3117
|
case "subscribed":
|
|
2952
3118
|
console.log(`\u2705 Subscribed to channel: ${message.payload.channel}`);
|
|
3119
|
+
if (this.pendingSubscription && message.payload.channel === this.channelName) {
|
|
3120
|
+
this.pendingSubscription.resolve();
|
|
3121
|
+
}
|
|
2953
3122
|
break;
|
|
2954
3123
|
case "unsubscribed":
|
|
2955
3124
|
console.log(`\u274C Unsubscribed from channel: ${message.payload.channel}`);
|
|
@@ -2960,6 +3129,9 @@ var BlinkRealtimeChannel = class {
|
|
|
2960
3129
|
break;
|
|
2961
3130
|
case "error":
|
|
2962
3131
|
console.error("Realtime error:", message.payload.error);
|
|
3132
|
+
if (this.pendingSubscription && message.payload.channel === this.channelName) {
|
|
3133
|
+
this.pendingSubscription.reject(new BlinkRealtimeError(`Subscription error: ${message.payload.error}`));
|
|
3134
|
+
}
|
|
2963
3135
|
break;
|
|
2964
3136
|
default:
|
|
2965
3137
|
console.log("Unknown message type:", message.type);
|
|
@@ -2979,16 +3151,19 @@ var BlinkRealtimeChannel = class {
|
|
|
2979
3151
|
if (this.reconnectTimer) {
|
|
2980
3152
|
clearTimeout(this.reconnectTimer);
|
|
2981
3153
|
}
|
|
3154
|
+
if (!this.isSubscribed && !this.pendingSubscription) {
|
|
3155
|
+
return;
|
|
3156
|
+
}
|
|
2982
3157
|
this.reconnectAttempts++;
|
|
2983
3158
|
const baseDelay = Math.min(3e4, Math.pow(2, this.reconnectAttempts) * 1e3);
|
|
2984
3159
|
const jitter = Math.random() * 1e3;
|
|
2985
3160
|
const delay = baseDelay + jitter;
|
|
2986
3161
|
console.log(`\u{1F504} Scheduling reconnect attempt ${this.reconnectAttempts} in ${Math.round(delay)}ms`);
|
|
2987
3162
|
this.reconnectTimer = globalThis.setTimeout(async () => {
|
|
2988
|
-
if (this.isSubscribed) {
|
|
3163
|
+
if (this.isSubscribed || this.pendingSubscription) {
|
|
2989
3164
|
try {
|
|
2990
3165
|
await this.connectWebSocket();
|
|
2991
|
-
if (this.websocket) {
|
|
3166
|
+
if (this.isSubscribed && this.websocket) {
|
|
2992
3167
|
const subscribeMessage = {
|
|
2993
3168
|
type: "subscribe",
|
|
2994
3169
|
payload: {
|
|
@@ -3007,6 +3182,14 @@ var BlinkRealtimeChannel = class {
|
|
|
3007
3182
|
}
|
|
3008
3183
|
cleanup() {
|
|
3009
3184
|
this.isSubscribed = false;
|
|
3185
|
+
this.isConnected = false;
|
|
3186
|
+
this.isConnecting = false;
|
|
3187
|
+
if (this.pendingSubscription) {
|
|
3188
|
+
clearTimeout(this.pendingSubscription.timeout);
|
|
3189
|
+
this.pendingSubscription.reject(new BlinkRealtimeError("Channel cleanup"));
|
|
3190
|
+
this.pendingSubscription = null;
|
|
3191
|
+
}
|
|
3192
|
+
this.rejectQueuedMessages(new BlinkRealtimeError("Channel cleanup"));
|
|
3010
3193
|
if (this.heartbeatTimer) {
|
|
3011
3194
|
clearInterval(this.heartbeatTimer);
|
|
3012
3195
|
this.heartbeatTimer = null;
|
package/package.json
CHANGED