@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.
Files changed (2) hide show
  1. package/README.md +49 -40
  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,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 - Single stable connection
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
- if (!user?.id) return
1291
-
1292
- const channel = blink.realtime.channel('room')
1293
- let isMounted = true, isSubscribed = false
1301
+ let channel = null
1294
1302
 
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)
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
- setup()
1308
+ initApp().catch(console.error)
1309
+
1310
+ // Cleanup runs when component unmounts
1316
1311
  return () => {
1317
- isMounted = false
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
- // Publish works automatically - no connection checks needed!
1324
- await blink.realtime.publish('room', 'message', data)
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 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
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
- const channel = blink.realtime.channel('chat-room')
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.unsubscribe()
1514
+ channel?.unsubscribe()
1506
1515
  }
1507
1516
  }, [user.id, user.name])
1508
1517
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blinkdotnew/sdk",
3
- "version": "0.14.7",
3
+ "version": "0.14.8",
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",