@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.
Files changed (2) hide show
  1. package/README.md +109 -42
  2. 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 - Single stable connection
1284
- const userRef = useRef(user)
1285
- const [isConnected, setIsConnected] = useState(false)
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
- useEffect(() => { userRef.current = user }, [user])
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 channel = blink.realtime.channel('room')
1293
- let isMounted = true, isSubscribed = false
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
- const setup = async () => {
1296
- if (!isMounted) return
1297
- try {
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)
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((msg) => {
1307
- if (!isMounted) return
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
- setup()
1316
- return () => {
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 dependency**: `[user.id]` not `[user]`
1329
- 2. **Use useRef**: Store user data to avoid re-connections
1330
- 3. **Add isMounted**: Prevent operations on unmounted components
1331
- 4. **Zero connection management**: SDK handles all connection states automatically
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
- const channel = blink.realtime.channel('chat-room')
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: user.id,
1464
- metadata: { displayName: user.name, avatar: '/avatar.png' }
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.unsubscribe()
1572
+ channel?.unsubscribe()
1506
1573
  }
1507
- }, [user.id, user.name])
1574
+ }, [user?.id]) // ✅ Optional chaining in dependency
1508
1575
 
1509
1576
  const sendMessage = async () => {
1510
1577
  if (!newMessage.trim()) return
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blinkdotnew/sdk",
3
- "version": "0.14.7",
3
+ "version": "0.14.9",
4
4
  "description": "Blink TypeScript SDK for client-side applications - Zero-boilerplate CRUD + auth + AI + analytics + notifications for modern SaaS/AI apps",
5
5
  "keywords": [
6
6
  "blink",