@blinkdotnew/sdk 0.14.5 → 0.14.6
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 +208 -60
- package/dist/index.mjs +208 -60
- 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
|
@@ -2751,40 +2751,73 @@ var BlinkRealtimeChannel = class {
|
|
|
2751
2751
|
presenceCallbacks = [];
|
|
2752
2752
|
websocket = null;
|
|
2753
2753
|
isSubscribed = false;
|
|
2754
|
+
isConnected = false;
|
|
2755
|
+
isConnecting = false;
|
|
2754
2756
|
reconnectTimer = null;
|
|
2755
2757
|
heartbeatTimer = null;
|
|
2756
2758
|
reconnectAttempts = 0;
|
|
2759
|
+
// Message queuing for when socket is not ready
|
|
2760
|
+
messageQueue = [];
|
|
2761
|
+
pendingSubscription = null;
|
|
2762
|
+
// Connection promise for awaiting readiness
|
|
2763
|
+
connectionPromise = null;
|
|
2764
|
+
/**
|
|
2765
|
+
* Check if channel is ready for publishing
|
|
2766
|
+
*/
|
|
2767
|
+
isReady() {
|
|
2768
|
+
return this.isConnected && this.isSubscribed;
|
|
2769
|
+
}
|
|
2757
2770
|
async subscribe(options = {}) {
|
|
2758
2771
|
if (this.isSubscribed) {
|
|
2759
2772
|
return;
|
|
2760
2773
|
}
|
|
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();
|
|
2774
|
+
await this.ensureConnected();
|
|
2775
|
+
return new Promise((resolve, reject) => {
|
|
2776
|
+
if (this.pendingSubscription) {
|
|
2777
|
+
clearTimeout(this.pendingSubscription.timeout);
|
|
2778
|
+
this.pendingSubscription.reject(new BlinkRealtimeError("Subscription cancelled by new subscription request"));
|
|
2775
2779
|
}
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2780
|
+
const timeout = setTimeout(() => {
|
|
2781
|
+
this.pendingSubscription = null;
|
|
2782
|
+
reject(new BlinkRealtimeError("Subscription timeout - no acknowledgment from server"));
|
|
2783
|
+
}, 1e4);
|
|
2784
|
+
this.pendingSubscription = {
|
|
2785
|
+
options,
|
|
2786
|
+
resolve: () => {
|
|
2787
|
+
clearTimeout(timeout);
|
|
2788
|
+
this.pendingSubscription = null;
|
|
2789
|
+
this.isSubscribed = true;
|
|
2790
|
+
this.startHeartbeat();
|
|
2791
|
+
resolve();
|
|
2792
|
+
},
|
|
2793
|
+
reject: (error) => {
|
|
2794
|
+
clearTimeout(timeout);
|
|
2795
|
+
this.pendingSubscription = null;
|
|
2796
|
+
reject(error);
|
|
2797
|
+
},
|
|
2798
|
+
timeout
|
|
2799
|
+
};
|
|
2800
|
+
const subscribeMessage = {
|
|
2801
|
+
type: "subscribe",
|
|
2802
|
+
payload: {
|
|
2803
|
+
channel: this.channelName,
|
|
2804
|
+
userId: options.userId,
|
|
2805
|
+
metadata: options.metadata
|
|
2806
|
+
}
|
|
2807
|
+
};
|
|
2808
|
+
this.websocket.send(JSON.stringify(subscribeMessage));
|
|
2809
|
+
});
|
|
2782
2810
|
}
|
|
2783
2811
|
async unsubscribe() {
|
|
2784
2812
|
if (!this.isSubscribed) {
|
|
2785
2813
|
return;
|
|
2786
2814
|
}
|
|
2787
|
-
if (this.
|
|
2815
|
+
if (this.pendingSubscription) {
|
|
2816
|
+
clearTimeout(this.pendingSubscription.timeout);
|
|
2817
|
+
this.pendingSubscription.reject(new BlinkRealtimeError("Subscription cancelled by unsubscribe"));
|
|
2818
|
+
this.pendingSubscription = null;
|
|
2819
|
+
}
|
|
2820
|
+
if (this.websocket && this.websocket.readyState === 1) {
|
|
2788
2821
|
const unsubscribeMessage = {
|
|
2789
2822
|
type: "unsubscribe",
|
|
2790
2823
|
payload: {
|
|
@@ -2796,42 +2829,18 @@ var BlinkRealtimeChannel = class {
|
|
|
2796
2829
|
this.cleanup();
|
|
2797
2830
|
}
|
|
2798
2831
|
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
|
-
});
|
|
2832
|
+
await this.ensureConnected();
|
|
2833
|
+
const publishMessage = {
|
|
2834
|
+
type: "publish",
|
|
2835
|
+
payload: {
|
|
2836
|
+
channel: this.channelName,
|
|
2837
|
+
type,
|
|
2838
|
+
data,
|
|
2839
|
+
userId: options.userId,
|
|
2840
|
+
metadata: options.metadata
|
|
2841
|
+
}
|
|
2842
|
+
};
|
|
2843
|
+
return this.sendMessage(JSON.stringify(publishMessage));
|
|
2835
2844
|
}
|
|
2836
2845
|
onMessage(callback) {
|
|
2837
2846
|
this.messageCallbacks.push(callback);
|
|
@@ -2878,10 +2887,105 @@ var BlinkRealtimeChannel = class {
|
|
|
2878
2887
|
);
|
|
2879
2888
|
}
|
|
2880
2889
|
}
|
|
2890
|
+
/**
|
|
2891
|
+
* Ensure WebSocket connection is established and ready
|
|
2892
|
+
*/
|
|
2893
|
+
async ensureConnected() {
|
|
2894
|
+
if (this.isConnected && this.websocket?.readyState === 1) {
|
|
2895
|
+
return;
|
|
2896
|
+
}
|
|
2897
|
+
if (this.connectionPromise) {
|
|
2898
|
+
return this.connectionPromise;
|
|
2899
|
+
}
|
|
2900
|
+
this.connectionPromise = this.connectWebSocket();
|
|
2901
|
+
try {
|
|
2902
|
+
await this.connectionPromise;
|
|
2903
|
+
} finally {
|
|
2904
|
+
this.connectionPromise = null;
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
/**
|
|
2908
|
+
* Send a message, queuing if socket not ready
|
|
2909
|
+
*/
|
|
2910
|
+
sendMessage(message) {
|
|
2911
|
+
return new Promise((resolve, reject) => {
|
|
2912
|
+
const timeout = setTimeout(() => {
|
|
2913
|
+
const index = this.messageQueue.findIndex((q) => q.resolve === resolve);
|
|
2914
|
+
if (index > -1) {
|
|
2915
|
+
this.messageQueue.splice(index, 1);
|
|
2916
|
+
}
|
|
2917
|
+
reject(new BlinkRealtimeError("Message send timeout - no response from server"));
|
|
2918
|
+
}, 1e4);
|
|
2919
|
+
const queuedMessage = {
|
|
2920
|
+
message,
|
|
2921
|
+
resolve,
|
|
2922
|
+
reject,
|
|
2923
|
+
timeout
|
|
2924
|
+
};
|
|
2925
|
+
if (this.websocket && this.websocket.readyState === 1) {
|
|
2926
|
+
this.sendQueuedMessage(queuedMessage);
|
|
2927
|
+
} else {
|
|
2928
|
+
this.messageQueue.push(queuedMessage);
|
|
2929
|
+
}
|
|
2930
|
+
});
|
|
2931
|
+
}
|
|
2932
|
+
/**
|
|
2933
|
+
* Send a queued message and set up response handling
|
|
2934
|
+
*/
|
|
2935
|
+
sendQueuedMessage(queuedMessage) {
|
|
2936
|
+
const { message, resolve, reject, timeout } = queuedMessage;
|
|
2937
|
+
const handleResponse = (event) => {
|
|
2938
|
+
try {
|
|
2939
|
+
const response = JSON.parse(event.data);
|
|
2940
|
+
if (response.type === "published" && response.payload.channel === this.channelName) {
|
|
2941
|
+
clearTimeout(timeout);
|
|
2942
|
+
this.websocket.removeEventListener("message", handleResponse);
|
|
2943
|
+
resolve(response.payload.messageId);
|
|
2944
|
+
} else if (response.type === "error") {
|
|
2945
|
+
clearTimeout(timeout);
|
|
2946
|
+
this.websocket.removeEventListener("message", handleResponse);
|
|
2947
|
+
reject(new BlinkRealtimeError(`Server error: ${response.payload.error}`));
|
|
2948
|
+
}
|
|
2949
|
+
} catch (err) {
|
|
2950
|
+
}
|
|
2951
|
+
};
|
|
2952
|
+
this.websocket.addEventListener("message", handleResponse);
|
|
2953
|
+
this.websocket.send(message);
|
|
2954
|
+
}
|
|
2955
|
+
/**
|
|
2956
|
+
* Flush all queued messages when connection becomes ready
|
|
2957
|
+
*/
|
|
2958
|
+
flushMessageQueue() {
|
|
2959
|
+
if (!this.websocket || this.websocket.readyState !== 1) {
|
|
2960
|
+
return;
|
|
2961
|
+
}
|
|
2962
|
+
const queue = [...this.messageQueue];
|
|
2963
|
+
this.messageQueue = [];
|
|
2964
|
+
queue.forEach((queuedMessage) => {
|
|
2965
|
+
this.sendQueuedMessage(queuedMessage);
|
|
2966
|
+
});
|
|
2967
|
+
}
|
|
2881
2968
|
async connectWebSocket() {
|
|
2882
2969
|
if (this.websocket && this.websocket.readyState === 1) {
|
|
2970
|
+
this.isConnected = true;
|
|
2883
2971
|
return;
|
|
2884
2972
|
}
|
|
2973
|
+
if (this.isConnecting) {
|
|
2974
|
+
return new Promise((resolve, reject) => {
|
|
2975
|
+
const checkConnection = () => {
|
|
2976
|
+
if (this.isConnected) {
|
|
2977
|
+
resolve();
|
|
2978
|
+
} else if (!this.isConnecting) {
|
|
2979
|
+
reject(new BlinkRealtimeError("Connection failed"));
|
|
2980
|
+
} else {
|
|
2981
|
+
setTimeout(checkConnection, 100);
|
|
2982
|
+
}
|
|
2983
|
+
};
|
|
2984
|
+
checkConnection();
|
|
2985
|
+
});
|
|
2986
|
+
}
|
|
2987
|
+
this.isConnecting = true;
|
|
2988
|
+
this.isConnected = false;
|
|
2885
2989
|
return new Promise((resolve, reject) => {
|
|
2886
2990
|
try {
|
|
2887
2991
|
const httpClient = this.httpClient;
|
|
@@ -2892,12 +2996,16 @@ var BlinkRealtimeChannel = class {
|
|
|
2892
2996
|
const WSClass = getWebSocketClass();
|
|
2893
2997
|
this.websocket = new WSClass(wsUrl);
|
|
2894
2998
|
if (!this.websocket) {
|
|
2999
|
+
this.isConnecting = false;
|
|
2895
3000
|
reject(new BlinkRealtimeError("Failed to create WebSocket instance"));
|
|
2896
3001
|
return;
|
|
2897
3002
|
}
|
|
2898
3003
|
this.websocket.onopen = () => {
|
|
2899
3004
|
console.log(`\u{1F517} Connected to realtime for project ${this.projectId}`);
|
|
3005
|
+
this.isConnecting = false;
|
|
3006
|
+
this.isConnected = true;
|
|
2900
3007
|
this.reconnectAttempts = 0;
|
|
3008
|
+
this.flushMessageQueue();
|
|
2901
3009
|
resolve();
|
|
2902
3010
|
};
|
|
2903
3011
|
this.websocket.onmessage = (event) => {
|
|
@@ -2910,25 +3018,48 @@ var BlinkRealtimeChannel = class {
|
|
|
2910
3018
|
};
|
|
2911
3019
|
this.websocket.onclose = () => {
|
|
2912
3020
|
console.log(`\u{1F50C} Disconnected from realtime for project ${this.projectId}`);
|
|
3021
|
+
this.isConnecting = false;
|
|
3022
|
+
this.isConnected = false;
|
|
2913
3023
|
this.isSubscribed = false;
|
|
3024
|
+
this.rejectQueuedMessages(new BlinkRealtimeError("WebSocket connection closed"));
|
|
3025
|
+
if (this.pendingSubscription) {
|
|
3026
|
+
clearTimeout(this.pendingSubscription.timeout);
|
|
3027
|
+
this.pendingSubscription.reject(new BlinkRealtimeError("Connection closed during subscription"));
|
|
3028
|
+
this.pendingSubscription = null;
|
|
3029
|
+
}
|
|
2914
3030
|
this.scheduleReconnect();
|
|
2915
3031
|
};
|
|
2916
3032
|
this.websocket.onerror = (error) => {
|
|
2917
3033
|
console.error("WebSocket error:", error);
|
|
2918
3034
|
console.error("WebSocket URL was:", wsUrl);
|
|
2919
3035
|
console.error("WebSocket readyState:", this.websocket?.readyState);
|
|
3036
|
+
this.isConnecting = false;
|
|
3037
|
+
this.isConnected = false;
|
|
2920
3038
|
reject(new BlinkRealtimeError(`WebSocket connection failed to ${wsUrl}`));
|
|
2921
3039
|
};
|
|
2922
3040
|
setTimeout(() => {
|
|
2923
3041
|
if (this.websocket?.readyState !== 1) {
|
|
3042
|
+
this.isConnecting = false;
|
|
2924
3043
|
reject(new BlinkRealtimeError("WebSocket connection timeout"));
|
|
2925
3044
|
}
|
|
2926
|
-
},
|
|
3045
|
+
}, 1e4);
|
|
2927
3046
|
} catch (error) {
|
|
3047
|
+
this.isConnecting = false;
|
|
2928
3048
|
reject(new BlinkRealtimeError(`Failed to create WebSocket connection: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
2929
3049
|
}
|
|
2930
3050
|
});
|
|
2931
3051
|
}
|
|
3052
|
+
/**
|
|
3053
|
+
* Reject all queued messages with the given error
|
|
3054
|
+
*/
|
|
3055
|
+
rejectQueuedMessages(error) {
|
|
3056
|
+
const queue = [...this.messageQueue];
|
|
3057
|
+
this.messageQueue = [];
|
|
3058
|
+
queue.forEach((queuedMessage) => {
|
|
3059
|
+
clearTimeout(queuedMessage.timeout);
|
|
3060
|
+
queuedMessage.reject(error);
|
|
3061
|
+
});
|
|
3062
|
+
}
|
|
2932
3063
|
handleWebSocketMessage(message) {
|
|
2933
3064
|
switch (message.type) {
|
|
2934
3065
|
case "message":
|
|
@@ -2952,6 +3083,9 @@ var BlinkRealtimeChannel = class {
|
|
|
2952
3083
|
break;
|
|
2953
3084
|
case "subscribed":
|
|
2954
3085
|
console.log(`\u2705 Subscribed to channel: ${message.payload.channel}`);
|
|
3086
|
+
if (this.pendingSubscription && message.payload.channel === this.channelName) {
|
|
3087
|
+
this.pendingSubscription.resolve();
|
|
3088
|
+
}
|
|
2955
3089
|
break;
|
|
2956
3090
|
case "unsubscribed":
|
|
2957
3091
|
console.log(`\u274C Unsubscribed from channel: ${message.payload.channel}`);
|
|
@@ -2962,6 +3096,9 @@ var BlinkRealtimeChannel = class {
|
|
|
2962
3096
|
break;
|
|
2963
3097
|
case "error":
|
|
2964
3098
|
console.error("Realtime error:", message.payload.error);
|
|
3099
|
+
if (this.pendingSubscription && message.payload.channel === this.channelName) {
|
|
3100
|
+
this.pendingSubscription.reject(new BlinkRealtimeError(`Subscription error: ${message.payload.error}`));
|
|
3101
|
+
}
|
|
2965
3102
|
break;
|
|
2966
3103
|
default:
|
|
2967
3104
|
console.log("Unknown message type:", message.type);
|
|
@@ -2981,16 +3118,19 @@ var BlinkRealtimeChannel = class {
|
|
|
2981
3118
|
if (this.reconnectTimer) {
|
|
2982
3119
|
clearTimeout(this.reconnectTimer);
|
|
2983
3120
|
}
|
|
3121
|
+
if (!this.isSubscribed && !this.pendingSubscription) {
|
|
3122
|
+
return;
|
|
3123
|
+
}
|
|
2984
3124
|
this.reconnectAttempts++;
|
|
2985
3125
|
const baseDelay = Math.min(3e4, Math.pow(2, this.reconnectAttempts) * 1e3);
|
|
2986
3126
|
const jitter = Math.random() * 1e3;
|
|
2987
3127
|
const delay = baseDelay + jitter;
|
|
2988
3128
|
console.log(`\u{1F504} Scheduling reconnect attempt ${this.reconnectAttempts} in ${Math.round(delay)}ms`);
|
|
2989
3129
|
this.reconnectTimer = globalThis.setTimeout(async () => {
|
|
2990
|
-
if (this.isSubscribed) {
|
|
3130
|
+
if (this.isSubscribed || this.pendingSubscription) {
|
|
2991
3131
|
try {
|
|
2992
3132
|
await this.connectWebSocket();
|
|
2993
|
-
if (this.websocket) {
|
|
3133
|
+
if (this.isSubscribed && this.websocket) {
|
|
2994
3134
|
const subscribeMessage = {
|
|
2995
3135
|
type: "subscribe",
|
|
2996
3136
|
payload: {
|
|
@@ -3009,6 +3149,14 @@ var BlinkRealtimeChannel = class {
|
|
|
3009
3149
|
}
|
|
3010
3150
|
cleanup() {
|
|
3011
3151
|
this.isSubscribed = false;
|
|
3152
|
+
this.isConnected = false;
|
|
3153
|
+
this.isConnecting = false;
|
|
3154
|
+
if (this.pendingSubscription) {
|
|
3155
|
+
clearTimeout(this.pendingSubscription.timeout);
|
|
3156
|
+
this.pendingSubscription.reject(new BlinkRealtimeError("Channel cleanup"));
|
|
3157
|
+
this.pendingSubscription = null;
|
|
3158
|
+
}
|
|
3159
|
+
this.rejectQueuedMessages(new BlinkRealtimeError("Channel cleanup"));
|
|
3012
3160
|
if (this.heartbeatTimer) {
|
|
3013
3161
|
clearInterval(this.heartbeatTimer);
|
|
3014
3162
|
this.heartbeatTimer = null;
|
package/dist/index.mjs
CHANGED
|
@@ -2749,40 +2749,73 @@ var BlinkRealtimeChannel = class {
|
|
|
2749
2749
|
presenceCallbacks = [];
|
|
2750
2750
|
websocket = null;
|
|
2751
2751
|
isSubscribed = false;
|
|
2752
|
+
isConnected = false;
|
|
2753
|
+
isConnecting = false;
|
|
2752
2754
|
reconnectTimer = null;
|
|
2753
2755
|
heartbeatTimer = null;
|
|
2754
2756
|
reconnectAttempts = 0;
|
|
2757
|
+
// Message queuing for when socket is not ready
|
|
2758
|
+
messageQueue = [];
|
|
2759
|
+
pendingSubscription = null;
|
|
2760
|
+
// Connection promise for awaiting readiness
|
|
2761
|
+
connectionPromise = null;
|
|
2762
|
+
/**
|
|
2763
|
+
* Check if channel is ready for publishing
|
|
2764
|
+
*/
|
|
2765
|
+
isReady() {
|
|
2766
|
+
return this.isConnected && this.isSubscribed;
|
|
2767
|
+
}
|
|
2755
2768
|
async subscribe(options = {}) {
|
|
2756
2769
|
if (this.isSubscribed) {
|
|
2757
2770
|
return;
|
|
2758
2771
|
}
|
|
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();
|
|
2772
|
+
await this.ensureConnected();
|
|
2773
|
+
return new Promise((resolve, reject) => {
|
|
2774
|
+
if (this.pendingSubscription) {
|
|
2775
|
+
clearTimeout(this.pendingSubscription.timeout);
|
|
2776
|
+
this.pendingSubscription.reject(new BlinkRealtimeError("Subscription cancelled by new subscription request"));
|
|
2773
2777
|
}
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2778
|
+
const timeout = setTimeout(() => {
|
|
2779
|
+
this.pendingSubscription = null;
|
|
2780
|
+
reject(new BlinkRealtimeError("Subscription timeout - no acknowledgment from server"));
|
|
2781
|
+
}, 1e4);
|
|
2782
|
+
this.pendingSubscription = {
|
|
2783
|
+
options,
|
|
2784
|
+
resolve: () => {
|
|
2785
|
+
clearTimeout(timeout);
|
|
2786
|
+
this.pendingSubscription = null;
|
|
2787
|
+
this.isSubscribed = true;
|
|
2788
|
+
this.startHeartbeat();
|
|
2789
|
+
resolve();
|
|
2790
|
+
},
|
|
2791
|
+
reject: (error) => {
|
|
2792
|
+
clearTimeout(timeout);
|
|
2793
|
+
this.pendingSubscription = null;
|
|
2794
|
+
reject(error);
|
|
2795
|
+
},
|
|
2796
|
+
timeout
|
|
2797
|
+
};
|
|
2798
|
+
const subscribeMessage = {
|
|
2799
|
+
type: "subscribe",
|
|
2800
|
+
payload: {
|
|
2801
|
+
channel: this.channelName,
|
|
2802
|
+
userId: options.userId,
|
|
2803
|
+
metadata: options.metadata
|
|
2804
|
+
}
|
|
2805
|
+
};
|
|
2806
|
+
this.websocket.send(JSON.stringify(subscribeMessage));
|
|
2807
|
+
});
|
|
2780
2808
|
}
|
|
2781
2809
|
async unsubscribe() {
|
|
2782
2810
|
if (!this.isSubscribed) {
|
|
2783
2811
|
return;
|
|
2784
2812
|
}
|
|
2785
|
-
if (this.
|
|
2813
|
+
if (this.pendingSubscription) {
|
|
2814
|
+
clearTimeout(this.pendingSubscription.timeout);
|
|
2815
|
+
this.pendingSubscription.reject(new BlinkRealtimeError("Subscription cancelled by unsubscribe"));
|
|
2816
|
+
this.pendingSubscription = null;
|
|
2817
|
+
}
|
|
2818
|
+
if (this.websocket && this.websocket.readyState === 1) {
|
|
2786
2819
|
const unsubscribeMessage = {
|
|
2787
2820
|
type: "unsubscribe",
|
|
2788
2821
|
payload: {
|
|
@@ -2794,42 +2827,18 @@ var BlinkRealtimeChannel = class {
|
|
|
2794
2827
|
this.cleanup();
|
|
2795
2828
|
}
|
|
2796
2829
|
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
|
-
});
|
|
2830
|
+
await this.ensureConnected();
|
|
2831
|
+
const publishMessage = {
|
|
2832
|
+
type: "publish",
|
|
2833
|
+
payload: {
|
|
2834
|
+
channel: this.channelName,
|
|
2835
|
+
type,
|
|
2836
|
+
data,
|
|
2837
|
+
userId: options.userId,
|
|
2838
|
+
metadata: options.metadata
|
|
2839
|
+
}
|
|
2840
|
+
};
|
|
2841
|
+
return this.sendMessage(JSON.stringify(publishMessage));
|
|
2833
2842
|
}
|
|
2834
2843
|
onMessage(callback) {
|
|
2835
2844
|
this.messageCallbacks.push(callback);
|
|
@@ -2876,10 +2885,105 @@ var BlinkRealtimeChannel = class {
|
|
|
2876
2885
|
);
|
|
2877
2886
|
}
|
|
2878
2887
|
}
|
|
2888
|
+
/**
|
|
2889
|
+
* Ensure WebSocket connection is established and ready
|
|
2890
|
+
*/
|
|
2891
|
+
async ensureConnected() {
|
|
2892
|
+
if (this.isConnected && this.websocket?.readyState === 1) {
|
|
2893
|
+
return;
|
|
2894
|
+
}
|
|
2895
|
+
if (this.connectionPromise) {
|
|
2896
|
+
return this.connectionPromise;
|
|
2897
|
+
}
|
|
2898
|
+
this.connectionPromise = this.connectWebSocket();
|
|
2899
|
+
try {
|
|
2900
|
+
await this.connectionPromise;
|
|
2901
|
+
} finally {
|
|
2902
|
+
this.connectionPromise = null;
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
/**
|
|
2906
|
+
* Send a message, queuing if socket not ready
|
|
2907
|
+
*/
|
|
2908
|
+
sendMessage(message) {
|
|
2909
|
+
return new Promise((resolve, reject) => {
|
|
2910
|
+
const timeout = setTimeout(() => {
|
|
2911
|
+
const index = this.messageQueue.findIndex((q) => q.resolve === resolve);
|
|
2912
|
+
if (index > -1) {
|
|
2913
|
+
this.messageQueue.splice(index, 1);
|
|
2914
|
+
}
|
|
2915
|
+
reject(new BlinkRealtimeError("Message send timeout - no response from server"));
|
|
2916
|
+
}, 1e4);
|
|
2917
|
+
const queuedMessage = {
|
|
2918
|
+
message,
|
|
2919
|
+
resolve,
|
|
2920
|
+
reject,
|
|
2921
|
+
timeout
|
|
2922
|
+
};
|
|
2923
|
+
if (this.websocket && this.websocket.readyState === 1) {
|
|
2924
|
+
this.sendQueuedMessage(queuedMessage);
|
|
2925
|
+
} else {
|
|
2926
|
+
this.messageQueue.push(queuedMessage);
|
|
2927
|
+
}
|
|
2928
|
+
});
|
|
2929
|
+
}
|
|
2930
|
+
/**
|
|
2931
|
+
* Send a queued message and set up response handling
|
|
2932
|
+
*/
|
|
2933
|
+
sendQueuedMessage(queuedMessage) {
|
|
2934
|
+
const { message, resolve, reject, timeout } = queuedMessage;
|
|
2935
|
+
const handleResponse = (event) => {
|
|
2936
|
+
try {
|
|
2937
|
+
const response = JSON.parse(event.data);
|
|
2938
|
+
if (response.type === "published" && response.payload.channel === this.channelName) {
|
|
2939
|
+
clearTimeout(timeout);
|
|
2940
|
+
this.websocket.removeEventListener("message", handleResponse);
|
|
2941
|
+
resolve(response.payload.messageId);
|
|
2942
|
+
} else if (response.type === "error") {
|
|
2943
|
+
clearTimeout(timeout);
|
|
2944
|
+
this.websocket.removeEventListener("message", handleResponse);
|
|
2945
|
+
reject(new BlinkRealtimeError(`Server error: ${response.payload.error}`));
|
|
2946
|
+
}
|
|
2947
|
+
} catch (err) {
|
|
2948
|
+
}
|
|
2949
|
+
};
|
|
2950
|
+
this.websocket.addEventListener("message", handleResponse);
|
|
2951
|
+
this.websocket.send(message);
|
|
2952
|
+
}
|
|
2953
|
+
/**
|
|
2954
|
+
* Flush all queued messages when connection becomes ready
|
|
2955
|
+
*/
|
|
2956
|
+
flushMessageQueue() {
|
|
2957
|
+
if (!this.websocket || this.websocket.readyState !== 1) {
|
|
2958
|
+
return;
|
|
2959
|
+
}
|
|
2960
|
+
const queue = [...this.messageQueue];
|
|
2961
|
+
this.messageQueue = [];
|
|
2962
|
+
queue.forEach((queuedMessage) => {
|
|
2963
|
+
this.sendQueuedMessage(queuedMessage);
|
|
2964
|
+
});
|
|
2965
|
+
}
|
|
2879
2966
|
async connectWebSocket() {
|
|
2880
2967
|
if (this.websocket && this.websocket.readyState === 1) {
|
|
2968
|
+
this.isConnected = true;
|
|
2881
2969
|
return;
|
|
2882
2970
|
}
|
|
2971
|
+
if (this.isConnecting) {
|
|
2972
|
+
return new Promise((resolve, reject) => {
|
|
2973
|
+
const checkConnection = () => {
|
|
2974
|
+
if (this.isConnected) {
|
|
2975
|
+
resolve();
|
|
2976
|
+
} else if (!this.isConnecting) {
|
|
2977
|
+
reject(new BlinkRealtimeError("Connection failed"));
|
|
2978
|
+
} else {
|
|
2979
|
+
setTimeout(checkConnection, 100);
|
|
2980
|
+
}
|
|
2981
|
+
};
|
|
2982
|
+
checkConnection();
|
|
2983
|
+
});
|
|
2984
|
+
}
|
|
2985
|
+
this.isConnecting = true;
|
|
2986
|
+
this.isConnected = false;
|
|
2883
2987
|
return new Promise((resolve, reject) => {
|
|
2884
2988
|
try {
|
|
2885
2989
|
const httpClient = this.httpClient;
|
|
@@ -2890,12 +2994,16 @@ var BlinkRealtimeChannel = class {
|
|
|
2890
2994
|
const WSClass = getWebSocketClass();
|
|
2891
2995
|
this.websocket = new WSClass(wsUrl);
|
|
2892
2996
|
if (!this.websocket) {
|
|
2997
|
+
this.isConnecting = false;
|
|
2893
2998
|
reject(new BlinkRealtimeError("Failed to create WebSocket instance"));
|
|
2894
2999
|
return;
|
|
2895
3000
|
}
|
|
2896
3001
|
this.websocket.onopen = () => {
|
|
2897
3002
|
console.log(`\u{1F517} Connected to realtime for project ${this.projectId}`);
|
|
3003
|
+
this.isConnecting = false;
|
|
3004
|
+
this.isConnected = true;
|
|
2898
3005
|
this.reconnectAttempts = 0;
|
|
3006
|
+
this.flushMessageQueue();
|
|
2899
3007
|
resolve();
|
|
2900
3008
|
};
|
|
2901
3009
|
this.websocket.onmessage = (event) => {
|
|
@@ -2908,25 +3016,48 @@ var BlinkRealtimeChannel = class {
|
|
|
2908
3016
|
};
|
|
2909
3017
|
this.websocket.onclose = () => {
|
|
2910
3018
|
console.log(`\u{1F50C} Disconnected from realtime for project ${this.projectId}`);
|
|
3019
|
+
this.isConnecting = false;
|
|
3020
|
+
this.isConnected = false;
|
|
2911
3021
|
this.isSubscribed = false;
|
|
3022
|
+
this.rejectQueuedMessages(new BlinkRealtimeError("WebSocket connection closed"));
|
|
3023
|
+
if (this.pendingSubscription) {
|
|
3024
|
+
clearTimeout(this.pendingSubscription.timeout);
|
|
3025
|
+
this.pendingSubscription.reject(new BlinkRealtimeError("Connection closed during subscription"));
|
|
3026
|
+
this.pendingSubscription = null;
|
|
3027
|
+
}
|
|
2912
3028
|
this.scheduleReconnect();
|
|
2913
3029
|
};
|
|
2914
3030
|
this.websocket.onerror = (error) => {
|
|
2915
3031
|
console.error("WebSocket error:", error);
|
|
2916
3032
|
console.error("WebSocket URL was:", wsUrl);
|
|
2917
3033
|
console.error("WebSocket readyState:", this.websocket?.readyState);
|
|
3034
|
+
this.isConnecting = false;
|
|
3035
|
+
this.isConnected = false;
|
|
2918
3036
|
reject(new BlinkRealtimeError(`WebSocket connection failed to ${wsUrl}`));
|
|
2919
3037
|
};
|
|
2920
3038
|
setTimeout(() => {
|
|
2921
3039
|
if (this.websocket?.readyState !== 1) {
|
|
3040
|
+
this.isConnecting = false;
|
|
2922
3041
|
reject(new BlinkRealtimeError("WebSocket connection timeout"));
|
|
2923
3042
|
}
|
|
2924
|
-
},
|
|
3043
|
+
}, 1e4);
|
|
2925
3044
|
} catch (error) {
|
|
3045
|
+
this.isConnecting = false;
|
|
2926
3046
|
reject(new BlinkRealtimeError(`Failed to create WebSocket connection: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
2927
3047
|
}
|
|
2928
3048
|
});
|
|
2929
3049
|
}
|
|
3050
|
+
/**
|
|
3051
|
+
* Reject all queued messages with the given error
|
|
3052
|
+
*/
|
|
3053
|
+
rejectQueuedMessages(error) {
|
|
3054
|
+
const queue = [...this.messageQueue];
|
|
3055
|
+
this.messageQueue = [];
|
|
3056
|
+
queue.forEach((queuedMessage) => {
|
|
3057
|
+
clearTimeout(queuedMessage.timeout);
|
|
3058
|
+
queuedMessage.reject(error);
|
|
3059
|
+
});
|
|
3060
|
+
}
|
|
2930
3061
|
handleWebSocketMessage(message) {
|
|
2931
3062
|
switch (message.type) {
|
|
2932
3063
|
case "message":
|
|
@@ -2950,6 +3081,9 @@ var BlinkRealtimeChannel = class {
|
|
|
2950
3081
|
break;
|
|
2951
3082
|
case "subscribed":
|
|
2952
3083
|
console.log(`\u2705 Subscribed to channel: ${message.payload.channel}`);
|
|
3084
|
+
if (this.pendingSubscription && message.payload.channel === this.channelName) {
|
|
3085
|
+
this.pendingSubscription.resolve();
|
|
3086
|
+
}
|
|
2953
3087
|
break;
|
|
2954
3088
|
case "unsubscribed":
|
|
2955
3089
|
console.log(`\u274C Unsubscribed from channel: ${message.payload.channel}`);
|
|
@@ -2960,6 +3094,9 @@ var BlinkRealtimeChannel = class {
|
|
|
2960
3094
|
break;
|
|
2961
3095
|
case "error":
|
|
2962
3096
|
console.error("Realtime error:", message.payload.error);
|
|
3097
|
+
if (this.pendingSubscription && message.payload.channel === this.channelName) {
|
|
3098
|
+
this.pendingSubscription.reject(new BlinkRealtimeError(`Subscription error: ${message.payload.error}`));
|
|
3099
|
+
}
|
|
2963
3100
|
break;
|
|
2964
3101
|
default:
|
|
2965
3102
|
console.log("Unknown message type:", message.type);
|
|
@@ -2979,16 +3116,19 @@ var BlinkRealtimeChannel = class {
|
|
|
2979
3116
|
if (this.reconnectTimer) {
|
|
2980
3117
|
clearTimeout(this.reconnectTimer);
|
|
2981
3118
|
}
|
|
3119
|
+
if (!this.isSubscribed && !this.pendingSubscription) {
|
|
3120
|
+
return;
|
|
3121
|
+
}
|
|
2982
3122
|
this.reconnectAttempts++;
|
|
2983
3123
|
const baseDelay = Math.min(3e4, Math.pow(2, this.reconnectAttempts) * 1e3);
|
|
2984
3124
|
const jitter = Math.random() * 1e3;
|
|
2985
3125
|
const delay = baseDelay + jitter;
|
|
2986
3126
|
console.log(`\u{1F504} Scheduling reconnect attempt ${this.reconnectAttempts} in ${Math.round(delay)}ms`);
|
|
2987
3127
|
this.reconnectTimer = globalThis.setTimeout(async () => {
|
|
2988
|
-
if (this.isSubscribed) {
|
|
3128
|
+
if (this.isSubscribed || this.pendingSubscription) {
|
|
2989
3129
|
try {
|
|
2990
3130
|
await this.connectWebSocket();
|
|
2991
|
-
if (this.websocket) {
|
|
3131
|
+
if (this.isSubscribed && this.websocket) {
|
|
2992
3132
|
const subscribeMessage = {
|
|
2993
3133
|
type: "subscribe",
|
|
2994
3134
|
payload: {
|
|
@@ -3007,6 +3147,14 @@ var BlinkRealtimeChannel = class {
|
|
|
3007
3147
|
}
|
|
3008
3148
|
cleanup() {
|
|
3009
3149
|
this.isSubscribed = false;
|
|
3150
|
+
this.isConnected = false;
|
|
3151
|
+
this.isConnecting = false;
|
|
3152
|
+
if (this.pendingSubscription) {
|
|
3153
|
+
clearTimeout(this.pendingSubscription.timeout);
|
|
3154
|
+
this.pendingSubscription.reject(new BlinkRealtimeError("Channel cleanup"));
|
|
3155
|
+
this.pendingSubscription = null;
|
|
3156
|
+
}
|
|
3157
|
+
this.rejectQueuedMessages(new BlinkRealtimeError("Channel cleanup"));
|
|
3010
3158
|
if (this.heartbeatTimer) {
|
|
3011
3159
|
clearInterval(this.heartbeatTimer);
|
|
3012
3160
|
this.heartbeatTimer = null;
|
package/package.json
CHANGED