@blinkdotnew/sdk 0.14.6 → 0.14.8
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 +49 -40
- package/dist/index.js +39 -4
- package/dist/index.mjs +39 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -958,6 +958,8 @@ blink.analytics.clearAttribution()
|
|
|
958
958
|
**🎉 Zero-Boilerplate Connection Management!**
|
|
959
959
|
All connection states, queuing, and reconnection are handled automatically. No more "CONNECTING state" errors!
|
|
960
960
|
|
|
961
|
+
**⚠️ React Users**: See the [React + Realtime Connections](#react--realtime-connections) section below for proper async cleanup patterns to avoid "Subscription cancelled" errors.
|
|
962
|
+
|
|
961
963
|
```typescript
|
|
962
964
|
// 🔥 Real-time Messaging & Presence (NEW!)
|
|
963
965
|
// Perfect for chat apps, live collaboration, multiplayer games, and live updates
|
|
@@ -1270,6 +1272,20 @@ All `{{secret_name}}` placeholders are replaced with encrypted values from your
|
|
|
1270
1272
|
|
|
1271
1273
|
**⚠️ Critical: Avoid Multiple WebSocket Connections**
|
|
1272
1274
|
|
|
1275
|
+
The most common mistake is using async functions in useEffect that lose the cleanup function:
|
|
1276
|
+
|
|
1277
|
+
```typescript
|
|
1278
|
+
// ❌ WRONG - Async function loses cleanup (causes "Subscription cancelled" errors)
|
|
1279
|
+
useEffect(() => {
|
|
1280
|
+
const initApp = async () => {
|
|
1281
|
+
const channel = blink.realtime.channel('room')
|
|
1282
|
+
await channel.subscribe({ userId: user.id })
|
|
1283
|
+
return () => channel.unsubscribe() // ❌ CLEANUP LOST!
|
|
1284
|
+
}
|
|
1285
|
+
initApp() // Returns Promise, not cleanup function
|
|
1286
|
+
}, [])
|
|
1287
|
+
```
|
|
1288
|
+
|
|
1273
1289
|
```typescript
|
|
1274
1290
|
// ❌ WRONG - Creates new connection on every user change
|
|
1275
1291
|
useEffect(() => {
|
|
@@ -1280,54 +1296,46 @@ useEffect(() => {
|
|
|
1280
1296
|
```
|
|
1281
1297
|
|
|
1282
1298
|
```typescript
|
|
1283
|
-
// ✅ CORRECT -
|
|
1284
|
-
const userRef = useRef(user)
|
|
1285
|
-
const [isConnected, setIsConnected] = useState(false)
|
|
1286
|
-
|
|
1287
|
-
useEffect(() => { userRef.current = user }, [user])
|
|
1288
|
-
|
|
1299
|
+
// ✅ CORRECT - Proper async cleanup handling
|
|
1289
1300
|
useEffect(() => {
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
const channel = blink.realtime.channel('room')
|
|
1293
|
-
let isMounted = true, isSubscribed = false
|
|
1301
|
+
let channel = null
|
|
1294
1302
|
|
|
1295
|
-
const
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
await channel.subscribe({
|
|
1299
|
-
userId: userRef.current.id,
|
|
1300
|
-
metadata: { name: userRef.current.name }
|
|
1301
|
-
})
|
|
1302
|
-
if (!isMounted) return
|
|
1303
|
-
isSubscribed = true
|
|
1304
|
-
setIsConnected(true)
|
|
1305
|
-
|
|
1306
|
-
channel.onMessage((msg) => {
|
|
1307
|
-
if (!isMounted) return
|
|
1308
|
-
// handle message
|
|
1309
|
-
})
|
|
1310
|
-
} catch (error) {
|
|
1311
|
-
console.error('Connection failed:', error)
|
|
1312
|
-
}
|
|
1303
|
+
const initApp = async () => {
|
|
1304
|
+
channel = blink.realtime.channel('room')
|
|
1305
|
+
await channel.subscribe({ userId: user.id })
|
|
1313
1306
|
}
|
|
1314
1307
|
|
|
1315
|
-
|
|
1308
|
+
initApp().catch(console.error)
|
|
1309
|
+
|
|
1310
|
+
// Cleanup runs when component unmounts
|
|
1316
1311
|
return () => {
|
|
1317
|
-
|
|
1318
|
-
setIsConnected(false)
|
|
1319
|
-
if (isSubscribed) channel.unsubscribe()
|
|
1312
|
+
channel?.unsubscribe()
|
|
1320
1313
|
}
|
|
1321
1314
|
}, [user.id]) // ✅ Only user.id dependency
|
|
1315
|
+
```
|
|
1322
1316
|
|
|
1323
|
-
|
|
1324
|
-
|
|
1317
|
+
```typescript
|
|
1318
|
+
// ✅ ALTERNATIVE - Using state for cleanup
|
|
1319
|
+
const [channel, setChannel] = useState(null)
|
|
1320
|
+
|
|
1321
|
+
useEffect(() => {
|
|
1322
|
+
const initApp = async () => {
|
|
1323
|
+
const ch = blink.realtime.channel('room')
|
|
1324
|
+
await ch.subscribe({ userId: user.id })
|
|
1325
|
+
setChannel(ch)
|
|
1326
|
+
}
|
|
1327
|
+
initApp().catch(console.error)
|
|
1328
|
+
}, [user.id])
|
|
1329
|
+
|
|
1330
|
+
useEffect(() => {
|
|
1331
|
+
return () => channel?.unsubscribe()
|
|
1332
|
+
}, [channel])
|
|
1325
1333
|
```
|
|
1326
1334
|
|
|
1327
1335
|
**Rules:**
|
|
1328
|
-
1. **useEffect
|
|
1329
|
-
2. **
|
|
1330
|
-
3. **
|
|
1336
|
+
1. **Never return cleanup from async functions** - useEffect cleanup must be synchronous
|
|
1337
|
+
2. **useEffect dependency**: `[user.id]` not `[user]` to avoid reconnections
|
|
1338
|
+
3. **Store channel reference** outside async function for cleanup access
|
|
1331
1339
|
4. **Zero connection management**: SDK handles all connection states automatically
|
|
1332
1340
|
|
|
1333
1341
|
### React
|
|
@@ -1455,10 +1463,11 @@ function RealtimeChat() {
|
|
|
1455
1463
|
const [user] = useState({ id: 'user123', name: 'John Doe' }) // From auth
|
|
1456
1464
|
|
|
1457
1465
|
useEffect(() => {
|
|
1458
|
-
|
|
1466
|
+
let channel = null
|
|
1459
1467
|
|
|
1460
1468
|
// Subscribe and listen for messages
|
|
1461
1469
|
const setupRealtime = async () => {
|
|
1470
|
+
channel = blink.realtime.channel('chat-room')
|
|
1462
1471
|
await channel.subscribe({
|
|
1463
1472
|
userId: user.id,
|
|
1464
1473
|
metadata: { displayName: user.name, avatar: '/avatar.png' }
|
|
@@ -1498,11 +1507,11 @@ function RealtimeChat() {
|
|
|
1498
1507
|
})))
|
|
1499
1508
|
}
|
|
1500
1509
|
|
|
1501
|
-
setupRealtime()
|
|
1510
|
+
setupRealtime().catch(console.error)
|
|
1502
1511
|
|
|
1503
1512
|
// Cleanup on unmount
|
|
1504
1513
|
return () => {
|
|
1505
|
-
channel
|
|
1514
|
+
channel?.unsubscribe()
|
|
1506
1515
|
}
|
|
1507
1516
|
}, [user.id, user.name])
|
|
1508
1517
|
|
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
|
};
|
|
@@ -2805,7 +2811,11 @@ var BlinkRealtimeChannel = class {
|
|
|
2805
2811
|
metadata: options.metadata
|
|
2806
2812
|
}
|
|
2807
2813
|
};
|
|
2808
|
-
this.
|
|
2814
|
+
this.sendMessage(JSON.stringify(subscribeMessage)).catch((error) => {
|
|
2815
|
+
if (this.pendingSubscription) {
|
|
2816
|
+
this.pendingSubscription.reject(error);
|
|
2817
|
+
}
|
|
2818
|
+
});
|
|
2809
2819
|
});
|
|
2810
2820
|
}
|
|
2811
2821
|
async unsubscribe() {
|
|
@@ -2909,6 +2919,13 @@ var BlinkRealtimeChannel = class {
|
|
|
2909
2919
|
*/
|
|
2910
2920
|
sendMessage(message) {
|
|
2911
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
|
+
}
|
|
2912
2929
|
const timeout = setTimeout(() => {
|
|
2913
2930
|
const index = this.messageQueue.findIndex((q) => q.resolve === resolve);
|
|
2914
2931
|
if (index > -1) {
|
|
@@ -2923,7 +2940,13 @@ var BlinkRealtimeChannel = class {
|
|
|
2923
2940
|
timeout
|
|
2924
2941
|
};
|
|
2925
2942
|
if (this.websocket && this.websocket.readyState === 1) {
|
|
2926
|
-
|
|
2943
|
+
if (messageObj.type === "publish") {
|
|
2944
|
+
this.sendQueuedMessage(queuedMessage);
|
|
2945
|
+
} else {
|
|
2946
|
+
this.websocket.send(message);
|
|
2947
|
+
clearTimeout(timeout);
|
|
2948
|
+
resolve("sent");
|
|
2949
|
+
}
|
|
2927
2950
|
} else {
|
|
2928
2951
|
this.messageQueue.push(queuedMessage);
|
|
2929
2952
|
}
|
|
@@ -2962,7 +2985,19 @@ var BlinkRealtimeChannel = class {
|
|
|
2962
2985
|
const queue = [...this.messageQueue];
|
|
2963
2986
|
this.messageQueue = [];
|
|
2964
2987
|
queue.forEach((queuedMessage) => {
|
|
2965
|
-
|
|
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
|
+
}
|
|
2966
3001
|
});
|
|
2967
3002
|
}
|
|
2968
3003
|
async connectWebSocket() {
|
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
|
};
|
|
@@ -2803,7 +2809,11 @@ var BlinkRealtimeChannel = class {
|
|
|
2803
2809
|
metadata: options.metadata
|
|
2804
2810
|
}
|
|
2805
2811
|
};
|
|
2806
|
-
this.
|
|
2812
|
+
this.sendMessage(JSON.stringify(subscribeMessage)).catch((error) => {
|
|
2813
|
+
if (this.pendingSubscription) {
|
|
2814
|
+
this.pendingSubscription.reject(error);
|
|
2815
|
+
}
|
|
2816
|
+
});
|
|
2807
2817
|
});
|
|
2808
2818
|
}
|
|
2809
2819
|
async unsubscribe() {
|
|
@@ -2907,6 +2917,13 @@ var BlinkRealtimeChannel = class {
|
|
|
2907
2917
|
*/
|
|
2908
2918
|
sendMessage(message) {
|
|
2909
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
|
+
}
|
|
2910
2927
|
const timeout = setTimeout(() => {
|
|
2911
2928
|
const index = this.messageQueue.findIndex((q) => q.resolve === resolve);
|
|
2912
2929
|
if (index > -1) {
|
|
@@ -2921,7 +2938,13 @@ var BlinkRealtimeChannel = class {
|
|
|
2921
2938
|
timeout
|
|
2922
2939
|
};
|
|
2923
2940
|
if (this.websocket && this.websocket.readyState === 1) {
|
|
2924
|
-
|
|
2941
|
+
if (messageObj.type === "publish") {
|
|
2942
|
+
this.sendQueuedMessage(queuedMessage);
|
|
2943
|
+
} else {
|
|
2944
|
+
this.websocket.send(message);
|
|
2945
|
+
clearTimeout(timeout);
|
|
2946
|
+
resolve("sent");
|
|
2947
|
+
}
|
|
2925
2948
|
} else {
|
|
2926
2949
|
this.messageQueue.push(queuedMessage);
|
|
2927
2950
|
}
|
|
@@ -2960,7 +2983,19 @@ var BlinkRealtimeChannel = class {
|
|
|
2960
2983
|
const queue = [...this.messageQueue];
|
|
2961
2984
|
this.messageQueue = [];
|
|
2962
2985
|
queue.forEach((queuedMessage) => {
|
|
2963
|
-
|
|
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
|
+
}
|
|
2964
2999
|
});
|
|
2965
3000
|
}
|
|
2966
3001
|
async connectWebSocket() {
|
package/package.json
CHANGED