hotpipe 0.1.0 → 0.2.0
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 +390 -0
- package/dist/client/connection.d.ts +11 -8
- package/dist/client/connection.d.ts.map +1 -1
- package/dist/client/connection.js +54 -28
- package/dist/client/connection.js.map +1 -1
- package/dist/client/index.d.ts +12 -15
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +23 -56
- package/dist/client/index.js.map +1 -1
- package/dist/server/index.d.ts +41 -31
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +92 -51
- package/dist/server/index.js.map +1 -1
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +4 -4
package/README.md
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
# hotpipe
|
|
2
|
+
|
|
3
|
+
Type-safe real-time events for React and Next.js. Define your events with Zod, subscribe with React hooks, publish from client or server. Clients connect directly to the hotpipe API over WebSocket — your Next.js server is never in the hot path.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install hotpipe
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Peer dependencies: `react` (>=18), `zod` (>=3).
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
### 1. Create the route handler
|
|
16
|
+
|
|
17
|
+
Hotpipe uses your existing auth — check the session however you normally do. The `authorize` function runs on every connection attempt. Return `null` to deny access, or return the user's ID and pipe permissions to grant it. We'll cover pipe permissions in detail after the usage examples.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
// app/api/realtime/[...all]/route.ts
|
|
21
|
+
import { createPipeHandler } from 'hotpipe/server';
|
|
22
|
+
|
|
23
|
+
export const { POST } = createPipeHandler({
|
|
24
|
+
secret: process.env.HOTPIPE_SECRET!,
|
|
25
|
+
authorize: async (req) => {
|
|
26
|
+
const { session } = await getAuth(req); // your auth
|
|
27
|
+
if (!session) return null;
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
userId: session.userId,
|
|
31
|
+
pipes: {}, // we'll configure these in the permissions section
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. Define your events
|
|
38
|
+
|
|
39
|
+
This is your single source of truth for event types across client and server.
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
// lib/realtime-events.ts
|
|
43
|
+
import { z } from 'zod';
|
|
44
|
+
|
|
45
|
+
export const realtimeEvents = {
|
|
46
|
+
'message.created': z.object({
|
|
47
|
+
id: z.string(),
|
|
48
|
+
text: z.string(),
|
|
49
|
+
userId: z.string(),
|
|
50
|
+
createdAt: z.number(),
|
|
51
|
+
}),
|
|
52
|
+
'message.deleted': z.object({
|
|
53
|
+
id: z.string(),
|
|
54
|
+
}),
|
|
55
|
+
'typing.start': z.object({
|
|
56
|
+
userId: z.string(),
|
|
57
|
+
}),
|
|
58
|
+
} as const;
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 3. Create the client
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
// lib/realtime-client.ts
|
|
65
|
+
import { createPipeClient } from 'hotpipe/client';
|
|
66
|
+
|
|
67
|
+
import { realtimeEvents } from './realtime-events';
|
|
68
|
+
|
|
69
|
+
export const { PipeProvider, usePipe } = createPipeClient({
|
|
70
|
+
events: realtimeEvents,
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
This assumes your auth uses cookies (the default for most frameworks like Better Auth, NextAuth, and Clerk). If you're using bearer tokens instead, pass an `authHeaders` function:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
export const { PipeProvider, usePipe } = createPipeClient({
|
|
78
|
+
events: realtimeEvents,
|
|
79
|
+
authHeaders: async () => ({
|
|
80
|
+
Authorization: `Bearer ${getAccessToken()}`,
|
|
81
|
+
}),
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 4. Add the provider
|
|
86
|
+
|
|
87
|
+
Wrap your app with `PipeProvider`. This connects everything — your frontend, your backend, and the hotpipe API.
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
// app/layout.tsx
|
|
91
|
+
import { PipeProvider } from '@/lib/realtime-client';
|
|
92
|
+
|
|
93
|
+
export default function Layout({ children }: { children: React.ReactNode }) {
|
|
94
|
+
return <PipeProvider>{children}</PipeProvider>;
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 5. Create the server publisher
|
|
99
|
+
|
|
100
|
+
You can also publish events from server actions, route handlers, and even background jobs. No limits.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
// lib/realtime-server.ts
|
|
104
|
+
import { createPipePublisher } from 'hotpipe/server';
|
|
105
|
+
|
|
106
|
+
import { realtimeEvents } from './realtime-events';
|
|
107
|
+
|
|
108
|
+
export const realtime = createPipePublisher({
|
|
109
|
+
secret: process.env.HOTPIPE_SECRET!,
|
|
110
|
+
events: realtimeEvents,
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Usage
|
|
115
|
+
|
|
116
|
+
Let's build a team chat. Users type messages, and everyone on the team sees them instantly.
|
|
117
|
+
|
|
118
|
+
### Listening for new messages
|
|
119
|
+
|
|
120
|
+
When someone sends a message, every connected user should see it appear. We exported a `usePipe` hook in step 3. This is where you'll use it.
|
|
121
|
+
|
|
122
|
+
The first argument to `usePipe` is the pipe name — it's just a string, and you can name it anything you want. We're calling this one `"team-chat"` because it's a shared space where everyone on the team sees every message. The second argument is an object of event handlers. When a `message.created` event comes in, our handler runs. The data is fully typed based on the schema you defined in step 2.
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
import { usePipe } from '@/lib/realtime-client';
|
|
126
|
+
|
|
127
|
+
function Messages() {
|
|
128
|
+
const [messages, setMessages] = useState([]);
|
|
129
|
+
|
|
130
|
+
usePipe('team-chat', {
|
|
131
|
+
'message.created': (data) => {
|
|
132
|
+
setMessages((prev) => [...prev, data]);
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<ul>
|
|
138
|
+
{messages.map((msg) => (
|
|
139
|
+
<li key={msg.id}>{msg.text}</li>
|
|
140
|
+
))}
|
|
141
|
+
</ul>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
A few other pipe names to get your wheels turning:
|
|
147
|
+
|
|
148
|
+
- `"game-lobby-abc"` — real-time multiplayer game state
|
|
149
|
+
- `"player-xyz"` — events targeting a specific player
|
|
150
|
+
- `"org-acme"` — updates scoped to a whole organization
|
|
151
|
+
- `"doc-789"` — collaborative editing on a shared document
|
|
152
|
+
- `"auction-42"` — live bid updates on a specific item
|
|
153
|
+
|
|
154
|
+
The name is up to you. Whatever makes sense for your app.
|
|
155
|
+
|
|
156
|
+
### Sending a message from the browser
|
|
157
|
+
|
|
158
|
+
The same `usePipe` hook also gives you a `publish` function. When a user hits send, the event goes out to everyone listening on that pipe.
|
|
159
|
+
|
|
160
|
+
Hotpipe doesn't store anything — it's purely real-time delivery. You save data however you normally do (server actions, API calls, etc.) and publish the event alongside it.
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
function MessageInput() {
|
|
164
|
+
const [text, setText] = useState('');
|
|
165
|
+
const { publish } = usePipe('team-chat');
|
|
166
|
+
|
|
167
|
+
async function send() {
|
|
168
|
+
// save to your database first
|
|
169
|
+
const message = await createMessage({ text, userId: currentUser.id });
|
|
170
|
+
|
|
171
|
+
// then broadcast it to everyone in real time
|
|
172
|
+
publish('message.created', {
|
|
173
|
+
id: message.id,
|
|
174
|
+
text: message.text,
|
|
175
|
+
userId: message.userId,
|
|
176
|
+
createdAt: message.createdAt,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
setText('');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<form
|
|
184
|
+
onSubmit={(e) => {
|
|
185
|
+
e.preventDefault();
|
|
186
|
+
send();
|
|
187
|
+
}}
|
|
188
|
+
>
|
|
189
|
+
<input value={text} onChange={(e) => setText(e.target.value)} />
|
|
190
|
+
<button type="submit">Send</button>
|
|
191
|
+
</form>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Sending events from the server
|
|
197
|
+
|
|
198
|
+
Same idea on the server. Maybe you're already saving data in a server action or handling a webhook — just publish the event after. Everyone listening on that pipe gets it instantly.
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
import { realtime } from '@/lib/realtime-server';
|
|
202
|
+
|
|
203
|
+
// save to your database
|
|
204
|
+
const message = await db.messages.create({ text, userId });
|
|
205
|
+
|
|
206
|
+
// broadcast to connected users
|
|
207
|
+
const teamChat = realtime.pipe('team-chat');
|
|
208
|
+
|
|
209
|
+
await teamChat.publish('message.created', {
|
|
210
|
+
id: message.id,
|
|
211
|
+
text: message.text,
|
|
212
|
+
userId: message.userId,
|
|
213
|
+
createdAt: message.createdAt,
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Connection status
|
|
218
|
+
|
|
219
|
+
You can check whether the user is connected to the hotpipe API at any time.
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
const { status } = usePipe('team-chat');
|
|
223
|
+
// 'connecting' | 'connected' | 'disconnected' | 'reconnecting'
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Hotpipe automatically reconnects if the connection drops.
|
|
227
|
+
|
|
228
|
+
## Pipes and permissions
|
|
229
|
+
|
|
230
|
+
Now that you've seen how pipes work in practice, let's go back to the `authorize` function from step 1 and talk about access control.
|
|
231
|
+
|
|
232
|
+
Every user who connects gets a set of pipe permissions. You define these in the object returned by `authorize`. Each pipe can grant two abilities: `subscribe` (receive events) and `publish` (send events).
|
|
233
|
+
|
|
234
|
+
### Team chat — everyone can read and write
|
|
235
|
+
|
|
236
|
+
In the chat example above, every team member should be able to send and receive messages. Give everyone both permissions on the `team-chat` pipe:
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
authorize: async (req) => {
|
|
240
|
+
const { session } = await getAuth(req);
|
|
241
|
+
if (!session) return null;
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
userId: session.userId,
|
|
245
|
+
pipes: {
|
|
246
|
+
'team-chat': { subscribe: true, publish: true },
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
},
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### User-specific pipes — scoped to a single user
|
|
253
|
+
|
|
254
|
+
Sometimes you need to send events to a specific user — a notification, a status update, something only they should see. Use a dynamic pipe name with their user ID:
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
return {
|
|
258
|
+
userId: session.userId,
|
|
259
|
+
pipes: {
|
|
260
|
+
[`user-${session.userId}`]: { subscribe: true },
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Now your server can publish to `user-abc123` and only that user receives it. Other users can't subscribe to someone else's pipe because it's not in their permissions.
|
|
266
|
+
|
|
267
|
+
### Read-only pipes — broadcast without client publish
|
|
268
|
+
|
|
269
|
+
For things like live dashboards, announcements, or system alerts, you might want users to receive events but not send them. Only grant `subscribe`:
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
return {
|
|
273
|
+
userId: session.userId,
|
|
274
|
+
pipes: {
|
|
275
|
+
announcements: { subscribe: true },
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
The server can publish to `announcements` anytime. Clients can listen but can't push events to this pipe.
|
|
281
|
+
|
|
282
|
+
### Combining pipes — a real-world example
|
|
283
|
+
|
|
284
|
+
Most apps need a mix. Here's what a team collaboration app might look like:
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
authorize: async (req) => {
|
|
288
|
+
const { session } = await getAuth(req);
|
|
289
|
+
if (!session) return null;
|
|
290
|
+
|
|
291
|
+
const teamId = await getTeamId(session.userId);
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
userId: session.userId,
|
|
295
|
+
pipes: {
|
|
296
|
+
[`team-${teamId}`]: { subscribe: true, publish: true },
|
|
297
|
+
[`user-${session.userId}`]: { subscribe: true },
|
|
298
|
+
'announcements': { subscribe: true },
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
},
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Three pipes, three different access patterns:
|
|
305
|
+
|
|
306
|
+
- **`team-abc`** — everyone on the team can send and receive (chat, typing indicators, live cursors)
|
|
307
|
+
- **`user-xyz`** — only this user receives (notifications, direct messages from the server)
|
|
308
|
+
- **`announcements`** — all users receive, only the server publishes (maintenance alerts, feature launches)
|
|
309
|
+
|
|
310
|
+
If a user tries to subscribe or publish to a pipe that isn't in their permissions, the API ignores the request.
|
|
311
|
+
|
|
312
|
+
## Environment variables
|
|
313
|
+
|
|
314
|
+
| Variable | Where | Description |
|
|
315
|
+
| ---------------- | ------ | ----------------------------------------------------- |
|
|
316
|
+
| `HOTPIPE_SECRET` | Server | Shared secret for auth tokens and server-side publish |
|
|
317
|
+
|
|
318
|
+
## How it works
|
|
319
|
+
|
|
320
|
+
1. User loads your app
|
|
321
|
+
2. `PipeProvider` calls your handler to get an auth token
|
|
322
|
+
3. Your handler checks the session and signs a JWT with pipe permissions
|
|
323
|
+
4. The client opens a WebSocket directly to the hotpipe API
|
|
324
|
+
5. Events flow between clients and server through the hotpipe API
|
|
325
|
+
6. Your Next.js server is out of the loop — no long-lived connections, no Vercel cost impact
|
|
326
|
+
|
|
327
|
+
The hotpipe API is stateless. It handles connection management and event fan-out. Your database is the source of truth — the API doesn't persist anything.
|
|
328
|
+
|
|
329
|
+
## API reference
|
|
330
|
+
|
|
331
|
+
### `hotpipe/client`
|
|
332
|
+
|
|
333
|
+
#### `createPipeClient(config)`
|
|
334
|
+
|
|
335
|
+
Returns `{ PipeProvider, usePipe }`.
|
|
336
|
+
|
|
337
|
+
| Option | Type | Required | Description |
|
|
338
|
+
| ------------- | --------------------------------------- | -------- | ----------------------------------------------------------- |
|
|
339
|
+
| `events` | `Record<string, ZodType>` | Yes | Zod schemas for your events |
|
|
340
|
+
| `basePath` | `string` | No | Where your handler is mounted (defaults to `/api/realtime`) |
|
|
341
|
+
| `authHeaders` | `() => Promise<Record<string, string>>` | No | Custom headers for the auth request (e.g., bearer tokens) |
|
|
342
|
+
|
|
343
|
+
#### `PipeProvider`
|
|
344
|
+
|
|
345
|
+
Wrap your app (or a subtree) to establish the WebSocket connection to the hotpipe API. Connects on mount, disconnects on unmount.
|
|
346
|
+
|
|
347
|
+
#### `usePipe(pipe, handlers?)`
|
|
348
|
+
|
|
349
|
+
Subscribe to events on a pipe.
|
|
350
|
+
|
|
351
|
+
| Argument | Type | Description |
|
|
352
|
+
| ---------- | ------------------------------------------ | ---------------------------------------------------- |
|
|
353
|
+
| `pipe` | `string` | The pipe name to subscribe to |
|
|
354
|
+
| `handlers` | `{ [event]: (data) => void }` _(optional)_ | Event handlers, keyed by event name from your schema |
|
|
355
|
+
|
|
356
|
+
Returns `{ status, publish }`:
|
|
357
|
+
|
|
358
|
+
- **`status`** — Reactive connection state: `'connecting'`, `'connected'`, `'disconnected'`, or `'reconnecting'`. Updates automatically and triggers a re-render on change.
|
|
359
|
+
- **`publish(event, data)`** — Send a typed event to everyone on this pipe. Data is validated against your Zod schema before sending. Returns `true` if the event was sent or queued for delivery, `false` if the queue is full. Throws if the data fails validation. If the connection is temporarily down, events are buffered and flushed automatically when the connection comes back (up to 100 events, within 30 seconds).
|
|
360
|
+
|
|
361
|
+
The hook automatically subscribes when the component mounts and unsubscribes when it unmounts. If multiple components subscribe to the same pipe, hotpipe ref-counts them — the subscription stays active until the last component unmounts.
|
|
362
|
+
|
|
363
|
+
### `hotpipe/server`
|
|
364
|
+
|
|
365
|
+
#### `createPipeHandler(config)`
|
|
366
|
+
|
|
367
|
+
Returns `{ POST }` for your catch-all route handler.
|
|
368
|
+
|
|
369
|
+
| Option | Type | Required | Description |
|
|
370
|
+
| ------------- | ----------------------------------------------- | -------- | ----------------------------------------- |
|
|
371
|
+
| `secret` | `string` | Yes | Shared signing secret |
|
|
372
|
+
| `authorize` | `(req: Request) => Promise<AuthResult \| null>` | Yes | Your auth logic |
|
|
373
|
+
| `tokenExpiry` | `number` | No | Token lifetime in seconds (default: 3600) |
|
|
374
|
+
|
|
375
|
+
#### `createPipePublisher(config)`
|
|
376
|
+
|
|
377
|
+
Returns `{ pipe }`.
|
|
378
|
+
|
|
379
|
+
| Option | Type | Required | Description |
|
|
380
|
+
| -------- | ------------------------- | -------- | --------------------------- |
|
|
381
|
+
| `secret` | `string` | Yes | Your hotpipe secret |
|
|
382
|
+
| `events` | `Record<string, ZodType>` | Yes | Zod schemas for your events |
|
|
383
|
+
|
|
384
|
+
#### `publisher.pipe(name)`
|
|
385
|
+
|
|
386
|
+
Returns `{ publish }` for server-side event publishing. Unlike the client-side `publish`, this is async — it makes an HTTP request to the hotpipe API and will throw if the request fails.
|
|
387
|
+
|
|
388
|
+
## License
|
|
389
|
+
|
|
390
|
+
MIT
|
|
@@ -1,31 +1,34 @@
|
|
|
1
1
|
export type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'reconnecting';
|
|
2
|
-
export type
|
|
2
|
+
export type PipeListener = (event: string, data: unknown) => void;
|
|
3
3
|
interface ConnectionConfig {
|
|
4
4
|
brokerUrl: string;
|
|
5
5
|
authEndpoint: string;
|
|
6
|
+
authHeaders?: () => Promise<Record<string, string>>;
|
|
6
7
|
}
|
|
7
8
|
export declare class ConnectionManager {
|
|
8
9
|
private config;
|
|
9
10
|
private ws;
|
|
10
11
|
private status;
|
|
11
12
|
private statusListeners;
|
|
12
|
-
private
|
|
13
|
-
private
|
|
13
|
+
private pipeListeners;
|
|
14
|
+
private pipeRefs;
|
|
14
15
|
private reconnectAttempts;
|
|
15
16
|
private maxReconnectDelay;
|
|
16
17
|
private pingInterval;
|
|
17
18
|
private reconnectTimer;
|
|
18
19
|
private intentionalClose;
|
|
20
|
+
private eventQueue;
|
|
19
21
|
constructor(config: ConnectionConfig);
|
|
20
22
|
connect(): Promise<void>;
|
|
21
23
|
disconnect(): void;
|
|
22
|
-
subscribe(
|
|
23
|
-
unsubscribe(
|
|
24
|
-
publish(
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
subscribe(pipe: string): void;
|
|
25
|
+
unsubscribe(pipe: string): void;
|
|
26
|
+
publish(pipe: string, event: string, data: unknown): boolean;
|
|
27
|
+
addPipeListener(pipe: string, listener: PipeListener): void;
|
|
28
|
+
removePipeListener(pipe: string, listener: PipeListener): void;
|
|
27
29
|
getStatus(): ConnectionStatus;
|
|
28
30
|
onStatusChange(listener: (status: ConnectionStatus) => void): () => void;
|
|
31
|
+
private flushQueue;
|
|
29
32
|
private dispatch;
|
|
30
33
|
private setStatus;
|
|
31
34
|
private wsSend;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/client/connection.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/client/connection.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,gBAAgB,GAAG,cAAc,GAAG,YAAY,GAAG,WAAW,GAAG,cAAc,CAAC;AAE5F,MAAM,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;AAElE,UAAU,gBAAgB;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACrD;AAYD,qBAAa,iBAAiB;IAahB,OAAO,CAAC,MAAM;IAZ1B,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,MAAM,CAAoC;IAClD,OAAO,CAAC,eAAe,CAAiD;IACxE,OAAO,CAAC,aAAa,CAAwC;IAC7D,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,iBAAiB,CAAU;IACnC,OAAO,CAAC,YAAY,CAA+C;IACnE,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,UAAU,CAAqB;gBAEnB,MAAM,EAAE,gBAAgB;IAEtC,OAAO;IA2Eb,UAAU;IAeV,SAAS,CAAC,IAAI,EAAE,MAAM;IAStB,WAAW,CAAC,IAAI,EAAE,MAAM;IAcxB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO;IAkB5D,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY;IAQpD,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY;IAQvD,SAAS,IAAI,gBAAgB;IAI7B,cAAc,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,GAAG,MAAM,IAAI;IAKxE,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,SAAS;IAQjB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,OAAO;IAOf,OAAO,CAAC,iBAAiB;CAW1B"}
|
|
@@ -1,15 +1,19 @@
|
|
|
1
|
+
import { SDK_VERSION } from '../version';
|
|
2
|
+
const MAX_QUEUE_SIZE = 100;
|
|
3
|
+
const QUEUE_TTL = 30_000;
|
|
1
4
|
export class ConnectionManager {
|
|
2
5
|
config;
|
|
3
6
|
ws = null;
|
|
4
7
|
status = 'disconnected';
|
|
5
8
|
statusListeners = new Set();
|
|
6
|
-
|
|
7
|
-
|
|
9
|
+
pipeListeners = new Map();
|
|
10
|
+
pipeRefs = new Map();
|
|
8
11
|
reconnectAttempts = 0;
|
|
9
12
|
maxReconnectDelay = 30_000;
|
|
10
13
|
pingInterval = null;
|
|
11
14
|
reconnectTimer = null;
|
|
12
15
|
intentionalClose = false;
|
|
16
|
+
eventQueue = [];
|
|
13
17
|
constructor(config) {
|
|
14
18
|
this.config = config;
|
|
15
19
|
}
|
|
@@ -19,23 +23,27 @@ export class ConnectionManager {
|
|
|
19
23
|
this.intentionalClose = false;
|
|
20
24
|
this.setStatus(this.reconnectAttempts > 0 ? 'reconnecting' : 'connecting');
|
|
21
25
|
try {
|
|
26
|
+
const headers = this.config.authHeaders ? await this.config.authHeaders() : undefined;
|
|
22
27
|
const res = await fetch(this.config.authEndpoint, {
|
|
23
28
|
method: 'POST',
|
|
24
29
|
credentials: 'include',
|
|
30
|
+
headers,
|
|
25
31
|
});
|
|
26
32
|
if (!res.ok) {
|
|
27
33
|
throw new Error(`Auth failed: ${res.status}`);
|
|
28
34
|
}
|
|
29
35
|
const { token } = await res.json();
|
|
30
|
-
const wsUrl = `${this.config.brokerUrl}/ws?token=${encodeURIComponent(token)}`;
|
|
36
|
+
const wsUrl = `${this.config.brokerUrl}/ws?token=${encodeURIComponent(token)}&v=${SDK_VERSION}`;
|
|
31
37
|
this.ws = new WebSocket(wsUrl);
|
|
32
38
|
this.ws.onopen = () => {
|
|
33
39
|
this.setStatus('connected');
|
|
34
40
|
this.reconnectAttempts = 0;
|
|
35
|
-
// Resubscribe to all active
|
|
36
|
-
for (const
|
|
37
|
-
this.wsSend({ type: 'subscribe',
|
|
41
|
+
// Resubscribe to all active pipes
|
|
42
|
+
for (const pipe of this.pipeRefs.keys()) {
|
|
43
|
+
this.wsSend({ type: 'subscribe', pipe });
|
|
38
44
|
}
|
|
45
|
+
// Flush queued events
|
|
46
|
+
this.flushQueue();
|
|
39
47
|
// Keepalive ping every 30s
|
|
40
48
|
this.pingInterval = setInterval(() => {
|
|
41
49
|
this.wsSend({ type: 'ping' });
|
|
@@ -45,7 +53,7 @@ export class ConnectionManager {
|
|
|
45
53
|
try {
|
|
46
54
|
const msg = JSON.parse(event.data);
|
|
47
55
|
if (msg.type === 'event') {
|
|
48
|
-
this.dispatch(msg.
|
|
56
|
+
this.dispatch(msg.pipe, msg.event, msg.data);
|
|
49
57
|
}
|
|
50
58
|
}
|
|
51
59
|
catch {
|
|
@@ -79,42 +87,52 @@ export class ConnectionManager {
|
|
|
79
87
|
}
|
|
80
88
|
this.ws?.close();
|
|
81
89
|
this.ws = null;
|
|
90
|
+
this.eventQueue = [];
|
|
82
91
|
this.setStatus('disconnected');
|
|
83
92
|
}
|
|
84
|
-
subscribe(
|
|
85
|
-
const count = this.
|
|
86
|
-
this.
|
|
93
|
+
subscribe(pipe) {
|
|
94
|
+
const count = this.pipeRefs.get(pipe) || 0;
|
|
95
|
+
this.pipeRefs.set(pipe, count + 1);
|
|
87
96
|
if (count === 0 && this.ws?.readyState === WebSocket.OPEN) {
|
|
88
|
-
this.wsSend({ type: 'subscribe',
|
|
97
|
+
this.wsSend({ type: 'subscribe', pipe });
|
|
89
98
|
}
|
|
90
99
|
}
|
|
91
|
-
unsubscribe(
|
|
92
|
-
const count = this.
|
|
100
|
+
unsubscribe(pipe) {
|
|
101
|
+
const count = this.pipeRefs.get(pipe) || 0;
|
|
93
102
|
if (count <= 1) {
|
|
94
|
-
this.
|
|
103
|
+
this.pipeRefs.delete(pipe);
|
|
95
104
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
96
|
-
this.wsSend({ type: 'unsubscribe',
|
|
105
|
+
this.wsSend({ type: 'unsubscribe', pipe });
|
|
97
106
|
}
|
|
98
107
|
}
|
|
99
108
|
else {
|
|
100
|
-
this.
|
|
109
|
+
this.pipeRefs.set(pipe, count - 1);
|
|
101
110
|
}
|
|
102
111
|
}
|
|
103
|
-
publish(
|
|
112
|
+
publish(pipe, event, data) {
|
|
104
113
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
105
|
-
this.wsSend({ type: 'publish',
|
|
114
|
+
this.wsSend({ type: 'publish', pipe, event, data });
|
|
115
|
+
return true;
|
|
106
116
|
}
|
|
117
|
+
if (this.intentionalClose) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
if (this.eventQueue.length >= MAX_QUEUE_SIZE) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
this.eventQueue.push({ pipe, event, data, timestamp: Date.now() });
|
|
124
|
+
return true;
|
|
107
125
|
}
|
|
108
|
-
|
|
109
|
-
if (!this.
|
|
110
|
-
this.
|
|
126
|
+
addPipeListener(pipe, listener) {
|
|
127
|
+
if (!this.pipeListeners.has(pipe)) {
|
|
128
|
+
this.pipeListeners.set(pipe, new Set());
|
|
111
129
|
}
|
|
112
|
-
this.
|
|
130
|
+
this.pipeListeners.get(pipe).add(listener);
|
|
113
131
|
}
|
|
114
|
-
|
|
115
|
-
this.
|
|
116
|
-
if (this.
|
|
117
|
-
this.
|
|
132
|
+
removePipeListener(pipe, listener) {
|
|
133
|
+
this.pipeListeners.get(pipe)?.delete(listener);
|
|
134
|
+
if (this.pipeListeners.get(pipe)?.size === 0) {
|
|
135
|
+
this.pipeListeners.delete(pipe);
|
|
118
136
|
}
|
|
119
137
|
}
|
|
120
138
|
getStatus() {
|
|
@@ -124,8 +142,16 @@ export class ConnectionManager {
|
|
|
124
142
|
this.statusListeners.add(listener);
|
|
125
143
|
return () => this.statusListeners.delete(listener);
|
|
126
144
|
}
|
|
127
|
-
|
|
128
|
-
const
|
|
145
|
+
flushQueue() {
|
|
146
|
+
const now = Date.now();
|
|
147
|
+
const pending = this.eventQueue.filter((e) => now - e.timestamp < QUEUE_TTL);
|
|
148
|
+
this.eventQueue = [];
|
|
149
|
+
for (const { pipe, event, data } of pending) {
|
|
150
|
+
this.wsSend({ type: 'publish', pipe, event, data });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
dispatch(pipe, event, data) {
|
|
154
|
+
const listeners = this.pipeListeners.get(pipe);
|
|
129
155
|
if (!listeners)
|
|
130
156
|
return;
|
|
131
157
|
for (const listener of listeners) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/client/connection.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/client/connection.ts"],"names":[],"mappings":"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"}
|
package/dist/client/index.d.ts
CHANGED
|
@@ -13,9 +13,9 @@ type EventName<T extends EventMap> = keyof T & string;
|
|
|
13
13
|
*/
|
|
14
14
|
type EventData<T extends EventMap, E extends EventName<T>> = z.infer<T[E]>;
|
|
15
15
|
/**
|
|
16
|
-
* Handler map for
|
|
16
|
+
* Handler map for usePipe — a partial record of event handlers.
|
|
17
17
|
*/
|
|
18
|
-
type
|
|
18
|
+
type PipeHandlers<T extends EventMap> = {
|
|
19
19
|
[E in EventName<T>]?: (data: EventData<T, E>) => void;
|
|
20
20
|
};
|
|
21
21
|
/**
|
|
@@ -23,29 +23,26 @@ type ChannelHandlers<T extends EventMap> = {
|
|
|
23
23
|
*
|
|
24
24
|
* Usage:
|
|
25
25
|
* ```ts
|
|
26
|
-
* const {
|
|
26
|
+
* const { PipeProvider, usePipe } = createPipeClient({
|
|
27
27
|
* events: realtimeEvents,
|
|
28
|
-
* auth: { endpoint: '/api/realtime/auth' },
|
|
29
28
|
* });
|
|
30
29
|
* ```
|
|
31
30
|
*/
|
|
32
|
-
export declare function
|
|
31
|
+
export declare function createPipeClient<T extends EventMap>(config: {
|
|
33
32
|
events: T;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
/** Override the broker URL. Defaults to `wss://api.hotpipe.dev`. */
|
|
33
|
+
/** Override the base path where your handler is mounted. Defaults to `/api/realtime`. */
|
|
34
|
+
basePath?: string;
|
|
35
|
+
/** @internal Override the broker URL for local development. */
|
|
38
36
|
brokerUrl?: string;
|
|
37
|
+
/** Provide custom headers for the auth request (e.g., a Bearer token). */
|
|
38
|
+
authHeaders?: () => Promise<Record<string, string>>;
|
|
39
39
|
}): {
|
|
40
|
-
|
|
40
|
+
PipeProvider: ({ children }: {
|
|
41
41
|
children: React.ReactNode;
|
|
42
42
|
}) => import("react/jsx-runtime").JSX.Element;
|
|
43
|
-
|
|
44
|
-
status: ConnectionStatus;
|
|
45
|
-
publish: <E extends EventName<T>>(event: E, data: EventData<T, E>) => void;
|
|
46
|
-
};
|
|
47
|
-
useEvent: <E extends EventName<T>>(channel: string, event: E, handler: (data: EventData<T, E>) => void) => {
|
|
43
|
+
usePipe: (pipe: string, handlers?: Partial<PipeHandlers<T>>) => {
|
|
48
44
|
status: ConnectionStatus;
|
|
45
|
+
publish: <E extends EventName<T>>(event: E, data: EventData<T, E>) => boolean;
|
|
49
46
|
};
|
|
50
47
|
};
|
|
51
48
|
export type { ConnectionStatus, EventMap };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAExE;;GAEG;AACH,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;AAE1C;;GAEG;AACH,KAAK,SAAS,CAAC,CAAC,SAAS,QAAQ,IAAI,MAAM,CAAC,GAAG,MAAM,CAAC;AAEtD;;GAEG;AACH,KAAK,SAAS,CAAC,CAAC,SAAS,QAAQ,EAAE,CAAC,SAAS,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE3E;;GAEG;AACH,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAExE;;GAEG;AACH,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;AAE1C;;GAEG;AACH,KAAK,SAAS,CAAC,CAAC,SAAS,QAAQ,IAAI,MAAM,CAAC,GAAG,MAAM,CAAC;AAEtD;;GAEG;AACH,KAAK,SAAS,CAAC,CAAC,SAAS,QAAQ,EAAE,CAAC,SAAS,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE3E;;GAEG;AACH,KAAK,YAAY,CAAC,CAAC,SAAS,QAAQ,IAAI;KACrC,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI;CACtD,CAAC;AAKF;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,QAAQ,EAAE,MAAM,EAAE;IAC3D,MAAM,EAAE,CAAC,CAAC;IACV,yFAAyF;IACzF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACrD;iCAiBqC;QAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;KAAE;oBAgCzD,MAAM,aACD,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,KAClC;QACD,MAAM,EAAE,gBAAgB,CAAC;QACzB,OAAO,EAAE,CAAC,CAAC,SAAS,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,OAAO,CAAC;KAC/E;EAoDF;AAED,YAAY,EAAE,gBAAgB,EAAE,QAAQ,EAAE,CAAC"}
|
package/dist/client/index.js
CHANGED
|
@@ -3,23 +3,23 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { ConnectionManager } from './connection';
|
|
5
5
|
const DEFAULT_BROKER_URL = 'wss://api.hotpipe.dev';
|
|
6
|
+
const DEFAULT_BASE_PATH = '/api/realtime';
|
|
6
7
|
/**
|
|
7
8
|
* Creates a typed real-time client bound to your event schemas.
|
|
8
9
|
*
|
|
9
10
|
* Usage:
|
|
10
11
|
* ```ts
|
|
11
|
-
* const {
|
|
12
|
+
* const { PipeProvider, usePipe } = createPipeClient({
|
|
12
13
|
* events: realtimeEvents,
|
|
13
|
-
* auth: { endpoint: '/api/realtime/auth' },
|
|
14
14
|
* });
|
|
15
15
|
* ```
|
|
16
16
|
*/
|
|
17
|
-
export function
|
|
18
|
-
const
|
|
17
|
+
export function createPipeClient(config) {
|
|
18
|
+
const PipeContext = createContext(null);
|
|
19
19
|
function useManager() {
|
|
20
|
-
const manager = useContext(
|
|
20
|
+
const manager = useContext(PipeContext);
|
|
21
21
|
if (!manager) {
|
|
22
|
-
throw new Error('hotpipe:
|
|
22
|
+
throw new Error('hotpipe: usePipe must be used within <PipeProvider>');
|
|
23
23
|
}
|
|
24
24
|
return manager;
|
|
25
25
|
}
|
|
@@ -27,31 +27,32 @@ export function createRealtimeClient(config) {
|
|
|
27
27
|
* Wrap your app (or a subtree) with this provider to establish the
|
|
28
28
|
* WebSocket connection to the broker.
|
|
29
29
|
*/
|
|
30
|
-
function
|
|
30
|
+
function PipeProvider({ children }) {
|
|
31
31
|
const managerRef = useRef(null);
|
|
32
32
|
if (!managerRef.current) {
|
|
33
33
|
managerRef.current = new ConnectionManager({
|
|
34
34
|
brokerUrl: config.brokerUrl ?? DEFAULT_BROKER_URL,
|
|
35
|
-
authEndpoint: config.auth
|
|
35
|
+
authEndpoint: `${config.basePath ?? DEFAULT_BASE_PATH}/auth`,
|
|
36
|
+
authHeaders: config.authHeaders,
|
|
36
37
|
});
|
|
37
38
|
}
|
|
38
39
|
useEffect(() => {
|
|
39
40
|
managerRef.current?.connect();
|
|
40
41
|
return () => managerRef.current?.disconnect();
|
|
41
42
|
}, []);
|
|
42
|
-
return (_jsx(
|
|
43
|
+
return (_jsx(PipeContext.Provider, { value: managerRef.current, children: children }));
|
|
43
44
|
}
|
|
44
45
|
/**
|
|
45
|
-
* Subscribe to events on a
|
|
46
|
+
* Subscribe to events on a pipe. Returns connection status and a
|
|
46
47
|
* typed publish function.
|
|
47
48
|
*
|
|
48
49
|
* ```tsx
|
|
49
|
-
* const { status, publish } =
|
|
50
|
+
* const { status, publish } = usePipe('global', {
|
|
50
51
|
* 'message.created': (data) => addMessage(data),
|
|
51
52
|
* });
|
|
52
53
|
* ```
|
|
53
54
|
*/
|
|
54
|
-
function
|
|
55
|
+
function usePipe(pipe, handlers) {
|
|
55
56
|
const manager = useManager();
|
|
56
57
|
const handlersRef = useRef(handlers);
|
|
57
58
|
handlersRef.current = handlers;
|
|
@@ -61,64 +62,30 @@ export function createRealtimeClient(config) {
|
|
|
61
62
|
}, [manager]);
|
|
62
63
|
useEffect(() => {
|
|
63
64
|
const listener = (event, data) => {
|
|
64
|
-
const handler = handlersRef.current[event];
|
|
65
|
+
const handler = handlersRef.current?.[event];
|
|
65
66
|
if (handler) {
|
|
66
67
|
handler(data);
|
|
67
68
|
}
|
|
68
69
|
};
|
|
69
|
-
manager.
|
|
70
|
-
manager.subscribe(
|
|
70
|
+
manager.addPipeListener(pipe, listener);
|
|
71
|
+
manager.subscribe(pipe);
|
|
71
72
|
return () => {
|
|
72
|
-
manager.unsubscribe(
|
|
73
|
-
manager.
|
|
73
|
+
manager.unsubscribe(pipe);
|
|
74
|
+
manager.removePipeListener(pipe, listener);
|
|
74
75
|
};
|
|
75
|
-
}, [
|
|
76
|
+
}, [pipe, manager]);
|
|
76
77
|
const publish = useCallback((event, data) => {
|
|
77
|
-
// Validate against schema before sending
|
|
78
78
|
const schema = config.events[event];
|
|
79
79
|
if (schema) {
|
|
80
80
|
const result = schema.safeParse(data);
|
|
81
81
|
if (!result.success) {
|
|
82
|
-
|
|
83
|
-
console.error(`[hotpipe] Invalid data for "${event}":`, result.error.format());
|
|
84
|
-
}
|
|
85
|
-
return;
|
|
82
|
+
throw new Error(`[hotpipe] Invalid data for "${event}": ${JSON.stringify(result.error.format())}`);
|
|
86
83
|
}
|
|
87
84
|
}
|
|
88
|
-
manager.publish(
|
|
89
|
-
}, [
|
|
85
|
+
return manager.publish(pipe, event, data);
|
|
86
|
+
}, [pipe, manager]);
|
|
90
87
|
return { status, publish };
|
|
91
88
|
}
|
|
92
|
-
|
|
93
|
-
* Subscribe to a single event type on a channel.
|
|
94
|
-
*
|
|
95
|
-
* ```tsx
|
|
96
|
-
* useEvent('general', 'message.created', (data) => addMessage(data));
|
|
97
|
-
* ```
|
|
98
|
-
*/
|
|
99
|
-
function useEvent(channel, event, handler) {
|
|
100
|
-
const manager = useManager();
|
|
101
|
-
const handlerRef = useRef(handler);
|
|
102
|
-
handlerRef.current = handler;
|
|
103
|
-
const [status, setStatus] = useState(manager.getStatus());
|
|
104
|
-
useEffect(() => {
|
|
105
|
-
return manager.onStatusChange(setStatus);
|
|
106
|
-
}, [manager]);
|
|
107
|
-
useEffect(() => {
|
|
108
|
-
const listener = (evt, data) => {
|
|
109
|
-
if (evt === event) {
|
|
110
|
-
handlerRef.current(data);
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
manager.addChannelListener(channel, listener);
|
|
114
|
-
manager.subscribe(channel);
|
|
115
|
-
return () => {
|
|
116
|
-
manager.unsubscribe(channel);
|
|
117
|
-
manager.removeChannelListener(channel, listener);
|
|
118
|
-
};
|
|
119
|
-
}, [channel, event, manager]);
|
|
120
|
-
return { status };
|
|
121
|
-
}
|
|
122
|
-
return { RealtimeProvider, useChannel, useEvent };
|
|
89
|
+
return { PipeProvider, usePipe };
|
|
123
90
|
}
|
|
124
91
|
//# sourceMappingURL=index.js.map
|
package/dist/client/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAG5F,OAAO,EAAE,iBAAiB,EAAyB,MAAM,cAAc,CAAC;AAwBxE,MAAM,kBAAkB,GAAG,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.tsx"],"names":[],"mappings":"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"}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,71 +1,81 @@
|
|
|
1
1
|
import type { z } from 'zod';
|
|
2
2
|
type EventMap = Record<string, z.ZodType>;
|
|
3
|
+
/**
|
|
4
|
+
* Pipe permissions — which operations a user can perform.
|
|
5
|
+
*/
|
|
6
|
+
interface PipePermissions {
|
|
7
|
+
subscribe?: boolean;
|
|
8
|
+
publish?: boolean;
|
|
9
|
+
}
|
|
3
10
|
/**
|
|
4
11
|
* The shape returned by your authorize function.
|
|
5
12
|
*/
|
|
6
13
|
interface AuthResult {
|
|
7
14
|
userId: string;
|
|
8
|
-
|
|
15
|
+
pipes: Record<string, PipePermissions>;
|
|
9
16
|
}
|
|
10
17
|
/**
|
|
11
18
|
* Creates a typed server-side publisher for emitting events to the broker
|
|
12
19
|
* from API routes, server actions, webhooks, cron jobs, etc.
|
|
13
20
|
*
|
|
14
21
|
* ```ts
|
|
15
|
-
* const realtime =
|
|
16
|
-
*
|
|
22
|
+
* const realtime = createPipePublisher({
|
|
23
|
+
* secret: process.env.HOTPIPE_SECRET!,
|
|
17
24
|
* events: realtimeEvents,
|
|
18
25
|
* });
|
|
19
26
|
*
|
|
20
|
-
*
|
|
27
|
+
* const teamChat = realtime.pipe('team-chat');
|
|
28
|
+
* await teamChat.publish('message.created', { ... });
|
|
21
29
|
* ```
|
|
22
30
|
*/
|
|
23
|
-
export declare function
|
|
24
|
-
|
|
31
|
+
export declare function createPipePublisher<T extends EventMap>(config: {
|
|
32
|
+
secret: string;
|
|
25
33
|
events: T;
|
|
26
|
-
/** Override the broker URL
|
|
34
|
+
/** @internal Override the broker URL for local development. */
|
|
27
35
|
brokerUrl?: string;
|
|
28
36
|
}): {
|
|
29
37
|
/**
|
|
30
|
-
*
|
|
38
|
+
* Get a reference to a pipe. Returns a `{ publish }` object with
|
|
39
|
+
* the same signature as the client-side `usePipe` publish function.
|
|
40
|
+
*
|
|
41
|
+
* ```ts
|
|
42
|
+
* const teamChat = realtime.pipe('team-chat');
|
|
43
|
+
* await teamChat.publish('message.created', { ... });
|
|
44
|
+
* ```
|
|
31
45
|
*/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
*/
|
|
36
|
-
publishBatch(events: Array<{
|
|
37
|
-
channel: string;
|
|
38
|
-
event: keyof T & string;
|
|
39
|
-
data: unknown;
|
|
40
|
-
}>): Promise<void>;
|
|
46
|
+
pipe(pipe: string): {
|
|
47
|
+
publish<E extends keyof T & string>(event: E, data: z.infer<T[E]>): Promise<void>;
|
|
48
|
+
};
|
|
41
49
|
};
|
|
42
50
|
/**
|
|
43
|
-
* Creates a Next.js-compatible route handler for
|
|
44
|
-
*
|
|
45
|
-
*
|
|
51
|
+
* Creates a Next.js-compatible catch-all route handler for hotpipe.
|
|
52
|
+
* Mount this at `app/api/realtime/[...all]/route.ts` and the SDK
|
|
53
|
+
* handles the rest.
|
|
46
54
|
*
|
|
47
55
|
* ```ts
|
|
48
|
-
* // app/api/realtime/
|
|
49
|
-
* export const POST =
|
|
50
|
-
* secret: process.env.
|
|
56
|
+
* // app/api/realtime/[...all]/route.ts
|
|
57
|
+
* export const { POST } = createPipeHandler({
|
|
58
|
+
* secret: process.env.HOTPIPE_SECRET!,
|
|
51
59
|
* authorize: async (req) => {
|
|
52
|
-
* const session = await
|
|
60
|
+
* const { session } = await getAuth(req);
|
|
53
61
|
* if (!session) return null;
|
|
54
62
|
* return {
|
|
55
|
-
* userId: session.
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* [`user-${session.
|
|
63
|
+
* userId: session.userId,
|
|
64
|
+
* pipes: {
|
|
65
|
+
* global: { subscribe: true, publish: true },
|
|
66
|
+
* [`user-${session.userId}`]: { subscribe: true },
|
|
59
67
|
* },
|
|
60
68
|
* };
|
|
61
69
|
* },
|
|
62
70
|
* });
|
|
63
71
|
* ```
|
|
64
72
|
*/
|
|
65
|
-
export declare function
|
|
73
|
+
export declare function createPipeHandler(handlerConfig: {
|
|
66
74
|
secret: string;
|
|
67
75
|
authorize: (req: Request) => Promise<AuthResult | null>;
|
|
68
76
|
tokenExpiry?: number;
|
|
69
|
-
}):
|
|
70
|
-
|
|
77
|
+
}): {
|
|
78
|
+
POST: (req: Request) => Promise<Response>;
|
|
79
|
+
};
|
|
80
|
+
export type { AuthResult, EventMap, PipePermissions };
|
|
71
81
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;AAE1C;;GAEG;AACH,UAAU,eAAe;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,UAAU,UAAU;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CACxC;AAwBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,QAAQ,EAAE,MAAM,EAAE;IAC9D,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,CAAC,CAAC;IACV,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;IAwCG;;;;;;;;OAQG;eACQ,MAAM;gBAEC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,SAC/B,CAAC,QACF,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAClB,OAAO,CAAC,IAAI,CAAC;;EAMvB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,iBAAiB,CAAC,aAAa,EAAE;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;gBAuC6B,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;EAWxD;AAED,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC"}
|
package/dist/server/index.js
CHANGED
|
@@ -1,92 +1,125 @@
|
|
|
1
1
|
import { SignJWT } from 'jose';
|
|
2
|
+
import { SDK_VERSION } from '../version';
|
|
2
3
|
const DEFAULT_BROKER_URL = 'https://api.hotpipe.dev';
|
|
4
|
+
/**
|
|
5
|
+
* Derives a publish-specific auth token from the shared secret via HMAC.
|
|
6
|
+
* This ensures the raw secret is never sent over the wire as a Bearer token.
|
|
7
|
+
*/
|
|
8
|
+
async function derivePublishToken(secret) {
|
|
9
|
+
const encoder = new TextEncoder();
|
|
10
|
+
const key = await crypto.subtle.importKey('raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
11
|
+
const signature = await crypto.subtle.sign('HMAC', key, encoder.encode('hotpipe-publish-v1'));
|
|
12
|
+
return Array.from(new Uint8Array(signature))
|
|
13
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
14
|
+
.join('');
|
|
15
|
+
}
|
|
3
16
|
/**
|
|
4
17
|
* Creates a typed server-side publisher for emitting events to the broker
|
|
5
18
|
* from API routes, server actions, webhooks, cron jobs, etc.
|
|
6
19
|
*
|
|
7
20
|
* ```ts
|
|
8
|
-
* const realtime =
|
|
9
|
-
*
|
|
21
|
+
* const realtime = createPipePublisher({
|
|
22
|
+
* secret: process.env.HOTPIPE_SECRET!,
|
|
10
23
|
* events: realtimeEvents,
|
|
11
24
|
* });
|
|
12
25
|
*
|
|
13
|
-
*
|
|
26
|
+
* const teamChat = realtime.pipe('team-chat');
|
|
27
|
+
* await teamChat.publish('message.created', { ... });
|
|
14
28
|
* ```
|
|
15
29
|
*/
|
|
16
|
-
export function
|
|
30
|
+
export function createPipePublisher(config) {
|
|
17
31
|
const brokerUrl = config.brokerUrl ?? DEFAULT_BROKER_URL;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
32
|
+
let cachedHeaders = null;
|
|
33
|
+
async function getHeaders() {
|
|
34
|
+
if (!cachedHeaders) {
|
|
35
|
+
const publishToken = await derivePublishToken(config.secret);
|
|
36
|
+
cachedHeaders = {
|
|
37
|
+
'Content-Type': 'application/json',
|
|
38
|
+
Authorization: `Bearer ${publishToken}`,
|
|
39
|
+
'X-Hotpipe-SDK': SDK_VERSION,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return cachedHeaders;
|
|
43
|
+
}
|
|
44
|
+
async function publishToBroker(pipe, event, data) {
|
|
45
|
+
const schema = config.events[event];
|
|
46
|
+
if (schema) {
|
|
47
|
+
schema.parse(data);
|
|
48
|
+
}
|
|
49
|
+
const res = await fetch(`${brokerUrl}/publish`, {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: await getHeaders(),
|
|
52
|
+
body: JSON.stringify({ pipe, event, data }),
|
|
53
|
+
});
|
|
54
|
+
if (!res.ok) {
|
|
55
|
+
throw new Error(`hotpipe publish failed: ${res.status} ${await res.text()}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
22
58
|
return {
|
|
23
59
|
/**
|
|
24
|
-
*
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const res = await fetch(`${brokerUrl}/publish`, {
|
|
32
|
-
method: 'POST',
|
|
33
|
-
headers,
|
|
34
|
-
body: JSON.stringify({ channel, event, data }),
|
|
35
|
-
});
|
|
36
|
-
if (!res.ok) {
|
|
37
|
-
throw new Error(`hotpipe publish failed: ${res.status} ${await res.text()}`);
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
/**
|
|
41
|
-
* Publish multiple events in a single HTTP request.
|
|
60
|
+
* Get a reference to a pipe. Returns a `{ publish }` object with
|
|
61
|
+
* the same signature as the client-side `usePipe` publish function.
|
|
62
|
+
*
|
|
63
|
+
* ```ts
|
|
64
|
+
* const teamChat = realtime.pipe('team-chat');
|
|
65
|
+
* await teamChat.publish('message.created', { ... });
|
|
66
|
+
* ```
|
|
42
67
|
*/
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
if (!res.ok) {
|
|
50
|
-
throw new Error(`hotpipe batch publish failed: ${res.status} ${await res.text()}`);
|
|
51
|
-
}
|
|
68
|
+
pipe(pipe) {
|
|
69
|
+
return {
|
|
70
|
+
async publish(event, data) {
|
|
71
|
+
return publishToBroker(pipe, event, data);
|
|
72
|
+
},
|
|
73
|
+
};
|
|
52
74
|
},
|
|
53
75
|
};
|
|
54
76
|
}
|
|
55
77
|
/**
|
|
56
|
-
* Creates a Next.js-compatible route handler for
|
|
57
|
-
*
|
|
58
|
-
*
|
|
78
|
+
* Creates a Next.js-compatible catch-all route handler for hotpipe.
|
|
79
|
+
* Mount this at `app/api/realtime/[...all]/route.ts` and the SDK
|
|
80
|
+
* handles the rest.
|
|
59
81
|
*
|
|
60
82
|
* ```ts
|
|
61
|
-
* // app/api/realtime/
|
|
62
|
-
* export const POST =
|
|
63
|
-
* secret: process.env.
|
|
83
|
+
* // app/api/realtime/[...all]/route.ts
|
|
84
|
+
* export const { POST } = createPipeHandler({
|
|
85
|
+
* secret: process.env.HOTPIPE_SECRET!,
|
|
64
86
|
* authorize: async (req) => {
|
|
65
|
-
* const session = await
|
|
87
|
+
* const { session } = await getAuth(req);
|
|
66
88
|
* if (!session) return null;
|
|
67
89
|
* return {
|
|
68
|
-
* userId: session.
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* [`user-${session.
|
|
90
|
+
* userId: session.userId,
|
|
91
|
+
* pipes: {
|
|
92
|
+
* global: { subscribe: true, publish: true },
|
|
93
|
+
* [`user-${session.userId}`]: { subscribe: true },
|
|
72
94
|
* },
|
|
73
95
|
* };
|
|
74
96
|
* },
|
|
75
97
|
* });
|
|
76
98
|
* ```
|
|
77
99
|
*/
|
|
78
|
-
export function
|
|
100
|
+
export function createPipeHandler(handlerConfig) {
|
|
79
101
|
const encodedSecret = new TextEncoder().encode(handlerConfig.secret);
|
|
80
102
|
const expiry = handlerConfig.tokenExpiry || 3600;
|
|
81
|
-
|
|
103
|
+
async function handleAuth(req) {
|
|
82
104
|
try {
|
|
83
105
|
const result = await handlerConfig.authorize(req);
|
|
84
106
|
if (!result) {
|
|
85
107
|
return Response.json({ error: 'Unauthorized' }, { status: 401 });
|
|
86
108
|
}
|
|
109
|
+
// Transform { subscribe?: boolean, publish?: boolean } to string arrays
|
|
110
|
+
// for the broker wire format
|
|
111
|
+
const permissions = {};
|
|
112
|
+
for (const [pipe, perms] of Object.entries(result.pipes)) {
|
|
113
|
+
const arr = [];
|
|
114
|
+
if (perms.subscribe)
|
|
115
|
+
arr.push('subscribe');
|
|
116
|
+
if (perms.publish)
|
|
117
|
+
arr.push('publish');
|
|
118
|
+
permissions[pipe] = arr;
|
|
119
|
+
}
|
|
87
120
|
const token = await new SignJWT({
|
|
88
|
-
|
|
89
|
-
permissions
|
|
121
|
+
pipes: Object.keys(result.pipes),
|
|
122
|
+
permissions,
|
|
90
123
|
})
|
|
91
124
|
.setSubject(result.userId)
|
|
92
125
|
.setIssuedAt()
|
|
@@ -98,6 +131,14 @@ export function createAuthHandler(handlerConfig) {
|
|
|
98
131
|
catch {
|
|
99
132
|
return Response.json({ error: 'Internal error' }, { status: 500 });
|
|
100
133
|
}
|
|
101
|
-
}
|
|
134
|
+
}
|
|
135
|
+
async function handler(req) {
|
|
136
|
+
const url = new URL(req.url);
|
|
137
|
+
if (url.pathname.endsWith('/auth')) {
|
|
138
|
+
return handleAuth(req);
|
|
139
|
+
}
|
|
140
|
+
return Response.json({ error: 'Not found' }, { status: 404 });
|
|
141
|
+
}
|
|
142
|
+
return { POST: handler };
|
|
102
143
|
}
|
|
103
144
|
//# sourceMappingURL=index.js.map
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,UAAU,CAAC"}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hotpipe",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Type-safe real-time event broker SDK for React and Next.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"reset": "npm run clean && rm -rf node_modules"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"jose": "^
|
|
43
|
+
"jose": "^6.1.3"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"react": ">=18",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@pepper/tsconfig": "1.0.0",
|
|
51
|
-
"@types/react": ">=
|
|
52
|
-
"typescript": "^5.
|
|
51
|
+
"@types/react": ">=19",
|
|
52
|
+
"typescript": "^5.9.3"
|
|
53
53
|
}
|
|
54
54
|
}
|