@aurabase/realtime 0.4.1 → 0.5.1
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 +91 -4
- package/dist/index.cjs +356 -131
- package/dist/index.d.cts +197 -100
- package/dist/index.d.ts +197 -100
- package/dist/index.js +357 -132
- package/package.json +13 -12
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ npm i @aurabase/realtime
|
|
|
23
23
|
```ts
|
|
24
24
|
import { createClient } from '@aurabase/aurabase-js'
|
|
25
25
|
|
|
26
|
-
const aura = createClient('
|
|
26
|
+
const aura = createClient('https://mon-projet.aurabase.cloud', 'aura_anon_xxx')
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
### Connexion
|
|
@@ -162,21 +162,108 @@ room
|
|
|
162
162
|
})
|
|
163
163
|
.subscribe()
|
|
164
164
|
|
|
165
|
-
//
|
|
165
|
+
// Résout à l'ACK serveur si broadcast.ack est activé ET le canal abonné (voir ci-dessous).
|
|
166
166
|
const status = await room.send({ type: 'broadcast', event: 'cursor-pos', payload: { x: 100, y: 200 } })
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
+
##### Statuts rendus par `send()`
|
|
170
|
+
|
|
171
|
+
| Statut | Signification | Le message est-il parti ? |
|
|
172
|
+
| --- | --- | --- |
|
|
173
|
+
| `'ok'` | Sans `ack` : remis au transport. Avec `ack` : le serveur a répondu `broadcast_ack`. | oui |
|
|
174
|
+
| `'timeout'` | `ack` demandé sur un canal abonné, aucun accusé reçu en 5 s : l'accusé s'est **perdu** (lien coupé, serveur saturé). | oui |
|
|
175
|
+
| `'not_connected'` | Aucun transport lié, ou socket non-OPEN — le message a été **jeté**. | **non** |
|
|
176
|
+
| `'requires_subscription'` | Le serveur n'a **pas d'abonnement** pour ce canal : jamais souscrit, souscription **refusée** (RLS, `private_only`, quota) ou **révoquée** depuis (voir ci-dessous). | **non** |
|
|
177
|
+
| `'error'` | L'envoi a levé (sérialisation, transport en erreur). | non |
|
|
178
|
+
|
|
179
|
+
Avant la remédiation C18/N24, ces trois derniers cas rendaient tous `'error'` (et le
|
|
180
|
+
message jeté sur une socket fermée rendait même `'ok'`) : l'appelant ne pouvait ni les
|
|
181
|
+
distinguer, ni savoir si son message était parti.
|
|
182
|
+
|
|
183
|
+
##### Toute écriture exige un canal ABONNÉ
|
|
184
|
+
|
|
185
|
+
`.subscribe()` d'abord, `.send()` / `.track()` ensuite. Le serveur refuse un broadcast émis
|
|
186
|
+
sur un canal auquel la connexion n'est pas abonnée
|
|
187
|
+
(`Non abonné au canal '…' — Subscribe requis avant Broadcast`), et le SDK ne l'émet donc
|
|
188
|
+
même pas :
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
const room = aura.realtime.channel('room-1', { broadcast: { ack: true } })
|
|
192
|
+
|
|
193
|
+
await room.send({ type: 'broadcast', event: 'x', payload: {} })
|
|
194
|
+
// → 'requires_subscription' (immédiat) : rien n'est parti.
|
|
195
|
+
|
|
196
|
+
room.subscribe()
|
|
197
|
+
await room.send({ type: 'broadcast', event: 'x', payload: {} }) // → 'ok'
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Le même statut est rendu quand le SERVEUR **refuse** l'abonnement (canal privé sans droit
|
|
201
|
+
RLS, projet `private_only`, quota de canaux) ou le **révoque** ensuite (ré-évaluation RLS au
|
|
202
|
+
refresh de token) : le canal a alors reçu `CHANNEL_ERROR`, et toute écriture ultérieure rend
|
|
203
|
+
`'requires_subscription'` immédiatement — elle n'attend pas les 5 s d'un accusé que le
|
|
204
|
+
serveur n'émettra jamais. Écrivez donc depuis le callback de statut, comme chez Supabase :
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
room.subscribe(async (statut) => {
|
|
208
|
+
if (statut !== 'SUBSCRIBED') return // CHANNEL_ERROR : le serveur a refusé ce canal
|
|
209
|
+
await room.send({ type: 'broadcast', event: 'x', payload: {} }) // → 'ok'
|
|
210
|
+
})
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Ce n'est pas une limitation arbitraire, c'est la condition d'un contrôle d'accès qui tienne :
|
|
214
|
+
les policies RLS d'un canal privé sont calculées **à l'abonnement** et mises en cache dans la
|
|
215
|
+
subscription de la connexion. Sans abonnement, le serveur n'a aucune policy à consulter — et
|
|
216
|
+
laisser passer reviendrait à n'appliquer le contrôle qu'aux clients qui s'abonnent (c'était
|
|
217
|
+
le défaut N28 : un client jamais abonné diffusait sur un canal privé). C'est aussi le modèle
|
|
218
|
+
de `@supabase/realtime-js` : phoenix exige le `join` avant tout `push`, et `send()` bascule
|
|
219
|
+
sur l'endpoint HTTP quand le canal n'est pas rejoint. Le pendant Aurabase de cet endpoint est
|
|
220
|
+
`httpBroadcast()`, ci-dessous — lui aussi soumis à la RLS sur les projets `private_only`.
|
|
221
|
+
|
|
222
|
+
##### `private_only` — l'autorité sur la privauté est au SERVEUR
|
|
223
|
+
|
|
224
|
+
`{ private: true }` est une DEMANDE du client : elle déclenche le contrôle RLS pour *cette*
|
|
225
|
+
connexion. Elle ne rend pas le canal privé pour les autres — un client qui s'abonne au même
|
|
226
|
+
canal sans `private` reste, par défaut, un abonné public. Pour que la privauté soit imposée
|
|
227
|
+
à tous, activez le réglage de projet (table `realtime_config` du schéma `_platform`, hors de
|
|
228
|
+
portée des rôles `anon`/`authenticated`) :
|
|
229
|
+
|
|
230
|
+
```sql
|
|
231
|
+
INSERT INTO "project_<id>_platform".realtime_config (key, value)
|
|
232
|
+
VALUES ('private_only', 'true')
|
|
233
|
+
ON CONFLICT (key) DO UPDATE SET value = excluded.value;
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Tout abonnement public est alors refusé (`Canal public refusé : ce projet est configuré en
|
|
237
|
+
private_only`), et les chemins REST (`httpBroadcast`, roster de présence) ainsi que le flux
|
|
238
|
+
SSE passent par la même policy RLS. Même modèle que le réglage `private_only` de Supabase
|
|
239
|
+
Realtime (`PrivateOnly`).
|
|
240
|
+
|
|
169
241
|
#### Broadcast HTTP (sans WebSocket)
|
|
170
242
|
|
|
171
243
|
Émet un broadcast via REST, sans connexion WebSocket ouverte — utile côté serveur/edge.
|
|
172
244
|
|
|
173
245
|
```ts
|
|
174
|
-
// POST /v1/realtime/
|
|
246
|
+
// POST /v1/realtime/broadcast — pas besoin de connect().
|
|
175
247
|
const { error } = await aura.realtime.httpBroadcast('room-1', 'cursor-pos', { x: 10, y: 20 })
|
|
176
248
|
if (error) console.error(error) // 4xx / rate-limit surfacés proprement
|
|
177
249
|
```
|
|
178
250
|
|
|
179
|
-
Le
|
|
251
|
+
**Le canal doit porter le projet.** Le serveur exige `channel` préfixé par
|
|
252
|
+
`"<project_id>:"` et répond sinon `403 « Le canal n'appartient pas à ce projet »`
|
|
253
|
+
(`handlers/broadcast.rs`).
|
|
254
|
+
|
|
255
|
+
- **Avec une session** : le préfixe est ajouté ici, lu dans le claim `project_id` du jeton.
|
|
256
|
+
Un canal déjà qualifié n'est pas préfixé deux fois.
|
|
257
|
+
- **Sans session — le cas serveur/edge** : le SDK n'a aucun identifiant de projet (l'URL est
|
|
258
|
+
la seule désignation du tenant côté client), et il ne peut donc rien préfixer.
|
|
259
|
+
`httpBroadcast` **refuse** alors localement, sans appel réseau
|
|
260
|
+
(`error.code === 'realtime_channel_non_qualifie'`), plutôt que d'aller chercher un `403`
|
|
261
|
+
dont le message ne nomme pas la forme attendue. Qualifiez le canal vous-même :
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
// Serveur / edge : clé service_role, aucune session utilisateur.
|
|
265
|
+
await aura.realtime.httpBroadcast(`${PROJECT_ID}:room-1`, 'cursor-pos', { x: 10, y: 20 })
|
|
266
|
+
```
|
|
180
267
|
|
|
181
268
|
### Canaux privés (`setAuth`)
|
|
182
269
|
|