@blinkdotnew/sdk 0.14.7 → 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/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/package.json
CHANGED