@blinkdotnew/sdk 0.14.7 → 0.14.9
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 +109 -42
- 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,22 @@ 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
|
+
import type { RealtimeChannel } from '@blinkdotnew/sdk'
|
|
1279
|
+
|
|
1280
|
+
// ❌ WRONG - Async function loses cleanup (causes "Subscription cancelled" errors)
|
|
1281
|
+
useEffect(() => {
|
|
1282
|
+
const initApp = async () => {
|
|
1283
|
+
const channel = blink.realtime.channel('room')
|
|
1284
|
+
await channel.subscribe({ userId: user.id })
|
|
1285
|
+
return () => channel.unsubscribe() // ❌ CLEANUP LOST!
|
|
1286
|
+
}
|
|
1287
|
+
initApp() // Returns Promise, not cleanup function
|
|
1288
|
+
}, [])
|
|
1289
|
+
```
|
|
1290
|
+
|
|
1273
1291
|
```typescript
|
|
1274
1292
|
// ❌ WRONG - Creates new connection on every user change
|
|
1275
1293
|
useEffect(() => {
|
|
@@ -1280,55 +1298,95 @@ useEffect(() => {
|
|
|
1280
1298
|
```
|
|
1281
1299
|
|
|
1282
1300
|
```typescript
|
|
1283
|
-
// ✅ CORRECT -
|
|
1284
|
-
|
|
1285
|
-
|
|
1301
|
+
// ✅ CORRECT - Proper async cleanup handling
|
|
1302
|
+
useEffect(() => {
|
|
1303
|
+
if (!user?.id) return
|
|
1304
|
+
|
|
1305
|
+
let channel: RealtimeChannel | null = null
|
|
1306
|
+
|
|
1307
|
+
const initApp = async () => {
|
|
1308
|
+
channel = blink.realtime.channel('room')
|
|
1309
|
+
await channel.subscribe({ userId: user.id })
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
initApp().catch(console.error)
|
|
1313
|
+
|
|
1314
|
+
// Cleanup runs when component unmounts
|
|
1315
|
+
return () => {
|
|
1316
|
+
channel?.unsubscribe()
|
|
1317
|
+
}
|
|
1318
|
+
}, [user?.id]) // ✅ Optional chaining in dependency too
|
|
1319
|
+
```
|
|
1286
1320
|
|
|
1287
|
-
|
|
1321
|
+
```typescript
|
|
1322
|
+
// ✅ ALTERNATIVE - Using state for cleanup
|
|
1323
|
+
const [channel, setChannel] = useState<RealtimeChannel | null>(null)
|
|
1288
1324
|
|
|
1289
1325
|
useEffect(() => {
|
|
1290
1326
|
if (!user?.id) return
|
|
1291
1327
|
|
|
1292
|
-
const
|
|
1293
|
-
|
|
1328
|
+
const initApp = async () => {
|
|
1329
|
+
const ch = blink.realtime.channel('room')
|
|
1330
|
+
await ch.subscribe({ userId: user.id })
|
|
1331
|
+
setChannel(ch)
|
|
1332
|
+
}
|
|
1333
|
+
initApp().catch(console.error)
|
|
1334
|
+
}, [user?.id])
|
|
1335
|
+
|
|
1336
|
+
useEffect(() => {
|
|
1337
|
+
return () => channel?.unsubscribe()
|
|
1338
|
+
}, [channel])
|
|
1339
|
+
```
|
|
1340
|
+
|
|
1341
|
+
```typescript
|
|
1342
|
+
// ✅ COMPLETE EXAMPLE - With proper loading states
|
|
1343
|
+
function MyRealtimeComponent() {
|
|
1344
|
+
const [user, setUser] = useState(null)
|
|
1345
|
+
const [messages, setMessages] = useState([])
|
|
1294
1346
|
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1347
|
+
// Auth state management
|
|
1348
|
+
useEffect(() => {
|
|
1349
|
+
const unsubscribe = blink.auth.onAuthStateChanged((state) => {
|
|
1350
|
+
setUser(state.user)
|
|
1351
|
+
})
|
|
1352
|
+
return unsubscribe
|
|
1353
|
+
}, [])
|
|
1354
|
+
|
|
1355
|
+
// Guard clause - prevent rendering if user not loaded
|
|
1356
|
+
if (!user) return <div>Loading...</div>
|
|
1357
|
+
|
|
1358
|
+
// Now safe to use user.id everywhere
|
|
1359
|
+
useEffect(() => {
|
|
1360
|
+
if (!user?.id) return
|
|
1361
|
+
|
|
1362
|
+
let channel: RealtimeChannel | null = null
|
|
1363
|
+
|
|
1364
|
+
const initApp = async () => {
|
|
1365
|
+
channel = blink.realtime.channel('room')
|
|
1366
|
+
await channel.subscribe({ userId: user.id })
|
|
1305
1367
|
|
|
1306
|
-
channel.onMessage((
|
|
1307
|
-
|
|
1308
|
-
// handle message
|
|
1368
|
+
channel.onMessage((message) => {
|
|
1369
|
+
setMessages(prev => [...prev, message])
|
|
1309
1370
|
})
|
|
1310
|
-
} catch (error) {
|
|
1311
|
-
console.error('Connection failed:', error)
|
|
1312
1371
|
}
|
|
1313
|
-
|
|
1372
|
+
|
|
1373
|
+
initApp().catch(console.error)
|
|
1374
|
+
|
|
1375
|
+
return () => {
|
|
1376
|
+
channel?.unsubscribe()
|
|
1377
|
+
}
|
|
1378
|
+
}, [user?.id])
|
|
1314
1379
|
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
isMounted = false
|
|
1318
|
-
setIsConnected(false)
|
|
1319
|
-
if (isSubscribed) channel.unsubscribe()
|
|
1320
|
-
}
|
|
1321
|
-
}, [user.id]) // ✅ Only user.id dependency
|
|
1322
|
-
|
|
1323
|
-
// Publish works automatically - no connection checks needed!
|
|
1324
|
-
await blink.realtime.publish('room', 'message', data)
|
|
1380
|
+
return <div>Welcome {user.email}! Messages: {messages.length}</div>
|
|
1381
|
+
}
|
|
1325
1382
|
```
|
|
1326
1383
|
|
|
1327
1384
|
**Rules:**
|
|
1328
|
-
1. **useEffect
|
|
1329
|
-
2. **
|
|
1330
|
-
3. **
|
|
1331
|
-
4. **
|
|
1385
|
+
1. **Never return cleanup from async functions** - useEffect cleanup must be synchronous
|
|
1386
|
+
2. **useEffect dependency**: `[user?.id]` not `[user]` to avoid reconnections
|
|
1387
|
+
3. **Store channel reference** outside async function for cleanup access
|
|
1388
|
+
4. **Add component-level guards** - Check `if (!user) return <Loading />` before rendering
|
|
1389
|
+
5. **Zero connection management**: SDK handles all connection states automatically
|
|
1332
1390
|
|
|
1333
1391
|
### React
|
|
1334
1392
|
|
|
@@ -1454,14 +1512,23 @@ function RealtimeChat() {
|
|
|
1454
1512
|
const [onlineUsers, setOnlineUsers] = useState([])
|
|
1455
1513
|
const [user] = useState({ id: 'user123', name: 'John Doe' }) // From auth
|
|
1456
1514
|
|
|
1515
|
+
// Guard clause - prevent rendering if user not loaded
|
|
1516
|
+
if (!user) return <div>Loading...</div>
|
|
1517
|
+
|
|
1518
|
+
const userRef = useRef(user)
|
|
1519
|
+
useEffect(() => { userRef.current = user }, [user])
|
|
1520
|
+
|
|
1457
1521
|
useEffect(() => {
|
|
1458
|
-
|
|
1522
|
+
if (!user?.id) return
|
|
1523
|
+
|
|
1524
|
+
let channel: RealtimeChannel | null = null
|
|
1459
1525
|
|
|
1460
1526
|
// Subscribe and listen for messages
|
|
1461
1527
|
const setupRealtime = async () => {
|
|
1528
|
+
channel = blink.realtime.channel('chat-room')
|
|
1462
1529
|
await channel.subscribe({
|
|
1463
|
-
userId:
|
|
1464
|
-
metadata: { displayName:
|
|
1530
|
+
userId: userRef.current.id,
|
|
1531
|
+
metadata: { displayName: userRef.current.name, avatar: '/avatar.png' }
|
|
1465
1532
|
})
|
|
1466
1533
|
|
|
1467
1534
|
// Listen for new messages
|
|
@@ -1498,13 +1565,13 @@ function RealtimeChat() {
|
|
|
1498
1565
|
})))
|
|
1499
1566
|
}
|
|
1500
1567
|
|
|
1501
|
-
setupRealtime()
|
|
1568
|
+
setupRealtime().catch(console.error)
|
|
1502
1569
|
|
|
1503
1570
|
// Cleanup on unmount
|
|
1504
1571
|
return () => {
|
|
1505
|
-
channel
|
|
1572
|
+
channel?.unsubscribe()
|
|
1506
1573
|
}
|
|
1507
|
-
}, [user
|
|
1574
|
+
}, [user?.id]) // ✅ Optional chaining in dependency
|
|
1508
1575
|
|
|
1509
1576
|
const sendMessage = async () => {
|
|
1510
1577
|
if (!newMessage.trim()) return
|
package/package.json
CHANGED