notif-agent 1.0.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/LICENSE +21 -0
- package/README.md +265 -0
- package/dist/index.d.mts +298 -0
- package/dist/index.d.ts +298 -0
- package/dist/index.js +737 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +729 -0
- package/dist/index.mjs.map +1 -0
- package/dist/server.d.mts +126 -0
- package/dist/server.d.ts +126 -0
- package/dist/server.js +333 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +331 -0
- package/dist/server.mjs.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# notif-agent 🚀
|
|
2
|
+
|
|
3
|
+
A lightweight, Pusher-like real-time notification client and server library powered by **Server-Sent Events (SSE)**.
|
|
4
|
+
|
|
5
|
+
Enjoy the developer experience and channel pub/sub API of Pusher or WebSockets with zero external cloud dependencies, built-in reconnection, automatic heartbeats, and seamless HTTP/2 multiplexing.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/notif-agent)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## ✨ Features
|
|
13
|
+
|
|
14
|
+
- 🎯 **Pusher-Compatible API**: Familiar `subscribe(channel)`, `channel.bind(event)`, `server.trigger()`, and `connection.bind()` semantics.
|
|
15
|
+
- ⚡ **Lightweight SSE Architecture**: Runs entirely over HTTP/HTTPS — no complex WebSocket proxies or port forwarding needed.
|
|
16
|
+
- 🔒 **Full Authentication Support**:
|
|
17
|
+
- Connection-level auth (Bearer tokens, cookies, custom headers).
|
|
18
|
+
- Pusher-style dynamic channel authorization (`authEndpoint` for `private-*` channels).
|
|
19
|
+
- 🔄 **Smart Reconnections**: Exponential backoff reconnection with state transitions (`connecting`, `connected`, `unavailable`, `disconnected`).
|
|
20
|
+
- 💓 **Heartbeat Keep-Alive**: Prevents proxy timeouts (e.g. Nginx, Cloudflare) with automatic ping monitoring.
|
|
21
|
+
- 🌐 **Universal Runtime Support**:
|
|
22
|
+
- **Server**: Node.js HTTP, Express, Fastify, Next.js App Router, Edge runtime, Cloudflare Workers, Hono.
|
|
23
|
+
- **Client**: Modern Browsers, React, Vue, Svelte, Node.js (v18+).
|
|
24
|
+
- 📦 **Zero Dependencies**: Pure TypeScript, dual ESM & CommonJS builds with full type definitions.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 📦 Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install notif-agent
|
|
32
|
+
# or
|
|
33
|
+
yarn add notif-agent
|
|
34
|
+
# or
|
|
35
|
+
pnpm add notif-agent
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 🚀 Quickstart
|
|
41
|
+
|
|
42
|
+
### 1. Server-side (Node.js / Express)
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import http from 'http';
|
|
46
|
+
import { NotifServer } from 'notif-agent/server';
|
|
47
|
+
|
|
48
|
+
const server = new NotifServer({
|
|
49
|
+
heartbeatInterval: 20000, // 20s heartbeat
|
|
50
|
+
debug: true,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const httpServer = http.createServer((req, res) => {
|
|
54
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
55
|
+
|
|
56
|
+
// SSE Stream Endpoint
|
|
57
|
+
if (url.pathname === '/sse') {
|
|
58
|
+
server.handleRequest(req, res);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// HTTP API to broadcast events
|
|
63
|
+
if (url.pathname === '/api/trigger' && req.method === 'POST') {
|
|
64
|
+
let body = '';
|
|
65
|
+
req.on('data', chunk => (body += chunk));
|
|
66
|
+
req.on('end', () => {
|
|
67
|
+
const { channel, event, data } = JSON.parse(body);
|
|
68
|
+
|
|
69
|
+
// Trigger event to all subscribers of this channel (like Pusher)
|
|
70
|
+
server.trigger(channel, event, data);
|
|
71
|
+
|
|
72
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
73
|
+
res.end(JSON.stringify({ success: true }));
|
|
74
|
+
});
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
httpServer.listen(3000);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
#### Next.js App Router (`app/api/sse/route.ts`)
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { NotifServer } from 'notif-agent/server';
|
|
86
|
+
|
|
87
|
+
const server = new NotifServer();
|
|
88
|
+
|
|
89
|
+
export async function GET(request: Request) {
|
|
90
|
+
return server.createWebResponse(request);
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
### 2. Client-side (Browser / React / Vue)
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import NotifAgent from 'notif-agent';
|
|
100
|
+
|
|
101
|
+
// Initialize client
|
|
102
|
+
const agent = new NotifAgent('http://localhost:3000/sse', {
|
|
103
|
+
reconnectBaseDelay: 1000,
|
|
104
|
+
heartbeatTimeout: 30000,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Monitor connection state
|
|
108
|
+
agent.connection.bind('state_change', ({ current }) => {
|
|
109
|
+
console.log('Connection state:', current);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Subscribe to a public channel
|
|
113
|
+
const ordersChannel = agent.subscribe('orders');
|
|
114
|
+
|
|
115
|
+
// Listen to specific events
|
|
116
|
+
ordersChannel.bind('order:created', (order, metadata) => {
|
|
117
|
+
console.log('New Order received:', order);
|
|
118
|
+
console.log('Channel:', metadata.channel);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Catch-all global listener
|
|
122
|
+
agent.bind_global((eventName, data, metadata) => {
|
|
123
|
+
console.log('[Global]', eventName, data, metadata);
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 🔐 Authentication & Private Channels
|
|
130
|
+
|
|
131
|
+
`notif-agent` supports both **Handshake Authentication** (Bearer tokens/Cookies) and **Pusher-Style Channel Authorization** (`authEndpoint`).
|
|
132
|
+
|
|
133
|
+
### Option 1: Connection Handshake Auth (Bearer Token / Cookie)
|
|
134
|
+
|
|
135
|
+
Pass dynamic headers or cookie credentials when connecting:
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
const agent = new NotifAgent('http://localhost:3000/sse', {
|
|
139
|
+
headers: () => ({
|
|
140
|
+
Authorization: `Bearer ${localStorage.getItem('jwt_token')}`,
|
|
141
|
+
}),
|
|
142
|
+
withCredentials: true, // Send session cookies
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
On the backend, authorize the user and restrict allowed channels:
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
app.get('/sse', (req, res) => {
|
|
150
|
+
const token = req.headers.authorization?.replace('Bearer ', '');
|
|
151
|
+
const user = verifyToken(token); // Authenticate
|
|
152
|
+
|
|
153
|
+
server.handleRequest(req, res, {
|
|
154
|
+
userId: user.id,
|
|
155
|
+
channels: ['public-news', `private-user.${user.id}`], // restrict client to these channels
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
### Option 2: Pusher-Style `authEndpoint` for `private-*` Channels
|
|
163
|
+
|
|
164
|
+
Specify an `authEndpoint` on the client:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
const agent = new NotifAgent('/sse', {
|
|
168
|
+
authEndpoint: '/api/notif/auth',
|
|
169
|
+
auth: {
|
|
170
|
+
headers: {
|
|
171
|
+
Authorization: `Bearer ${token}`,
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// When subscribing to a private channel:
|
|
177
|
+
const privateChannel = agent.subscribe('private-orders.123');
|
|
178
|
+
|
|
179
|
+
privateChannel.bind('subscription_succeeded', () => {
|
|
180
|
+
console.log('Authorized & subscribed to private channel!');
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
privateChannel.bind('subscription_error', (err) => {
|
|
184
|
+
console.error('Subscription failed:', err);
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
On your backend, verify the user and call `server.subscribeClient()`:
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
app.post('/api/notif/auth', (req, res) => {
|
|
192
|
+
const { clientId, channel } = req.body;
|
|
193
|
+
const user = authenticate(req);
|
|
194
|
+
|
|
195
|
+
// Authorize channel access
|
|
196
|
+
if (channel === `private-orders.${user.id}`) {
|
|
197
|
+
server.subscribeClient(clientId, channel);
|
|
198
|
+
return res.json({ success: true, channel });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return res.status(403).json({ error: 'Forbidden' });
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## 📖 API Reference
|
|
208
|
+
|
|
209
|
+
### `NotifAgent` (Client)
|
|
210
|
+
|
|
211
|
+
| Method / Property | Description |
|
|
212
|
+
|---|---|
|
|
213
|
+
| `new NotifAgent(url, options?)` | Instantiate client and connect to SSE endpoint |
|
|
214
|
+
| `agent.subscribe(channelName)` | Returns a [`Channel`](#channel) object |
|
|
215
|
+
| `agent.unsubscribe(channelName)` | Unsubscribes and cleans up channel listeners |
|
|
216
|
+
| `agent.channel(channelName)` | Returns active channel by name |
|
|
217
|
+
| `agent.bind_global(callback)` | Catch-all listener for any incoming event |
|
|
218
|
+
| `agent.unbind_global(callback?)` | Remove global listener(s) |
|
|
219
|
+
| `agent.connect()` / `disconnect()` | Manual connection control |
|
|
220
|
+
| `agent.clientId` / `agent.socket_id` | Unique connection ID assigned by server handshake |
|
|
221
|
+
| `agent.connection.bind(state, cb)` | Listen to `'connecting'`, `'connected'`, `'unavailable'`, `'state_change'` |
|
|
222
|
+
|
|
223
|
+
### `Channel` (Client)
|
|
224
|
+
|
|
225
|
+
| Method | Description |
|
|
226
|
+
|---|---|
|
|
227
|
+
| `channel.bind(event, callback)` | Attach listener: `(data, metadata) => void` |
|
|
228
|
+
| `channel.unbind(event?, callback?)` | Remove listener(s) |
|
|
229
|
+
| `channel.bind_global(callback)` | Catch-all listener for events on this channel |
|
|
230
|
+
| `channel.subscribed` | Boolean indicating subscription status |
|
|
231
|
+
|
|
232
|
+
### `NotifServer` (Server)
|
|
233
|
+
|
|
234
|
+
| Method | Description |
|
|
235
|
+
|---|---|
|
|
236
|
+
| `server.trigger(channel, event, data, options?)` | Broadcast event to channel(s) |
|
|
237
|
+
| `server.broadcast(...)` | Alias for `trigger()` |
|
|
238
|
+
| `server.sendToUser(userId, event, data)` | Deliver event to all sessions of a specific user |
|
|
239
|
+
| `server.sendToClient(clientId, event, data)` | Deliver event directly to a specific client |
|
|
240
|
+
| `server.subscribeClient(clientId, channel)` | Dynamically grant client access to a channel |
|
|
241
|
+
| `server.unsubscribeClient(clientId, channel)`| Remove client from a channel |
|
|
242
|
+
| `server.handleRequest(req, res, options?)` | Node.js / Express / Fastify request handler |
|
|
243
|
+
| `server.createWebResponse(req, options?)` | Web API Standard Response stream (Next.js / Edge) |
|
|
244
|
+
| `server.getClientCount()` | Total active SSE connections |
|
|
245
|
+
| `server.getSubscribersCount(channel)` | Total subscribers listening to channel |
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## 🧪 Running Tests
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
npm test
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## 🛠️ Build
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
npm run build
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## 📄 License
|
|
264
|
+
|
|
265
|
+
MIT © 2026
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connection states matching the Pusher-like lifecycle.
|
|
3
|
+
*/
|
|
4
|
+
type ConnectionState = 'initialized' | 'connecting' | 'connected' | 'unavailable' | 'failed' | 'disconnected';
|
|
5
|
+
interface StateChangePayload {
|
|
6
|
+
previous: ConnectionState;
|
|
7
|
+
current: ConnectionState;
|
|
8
|
+
}
|
|
9
|
+
interface EventMetadata {
|
|
10
|
+
channel?: string;
|
|
11
|
+
event: string;
|
|
12
|
+
id?: string;
|
|
13
|
+
timestamp?: number;
|
|
14
|
+
}
|
|
15
|
+
type EventCallback<T = any> = (data: T, metadata?: EventMetadata) => void;
|
|
16
|
+
type GlobalEventCallback = (eventName: string, data: any, metadata?: EventMetadata) => void;
|
|
17
|
+
type TransportType = 'auto' | 'eventsource' | 'fetch';
|
|
18
|
+
interface NotifAgentOptions {
|
|
19
|
+
/**
|
|
20
|
+
* The SSE endpoint URL.
|
|
21
|
+
*/
|
|
22
|
+
url?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Custom HTTP headers (forces `fetch` transport if specified in browser).
|
|
25
|
+
* Can be an object or a sync/async function returning an object.
|
|
26
|
+
*/
|
|
27
|
+
headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
|
|
28
|
+
/**
|
|
29
|
+
* Query parameters to append to the SSE URL.
|
|
30
|
+
*/
|
|
31
|
+
queryParams?: Record<string, string | number | boolean>;
|
|
32
|
+
/**
|
|
33
|
+
* Whether to send cookies with cross-origin requests.
|
|
34
|
+
* Defaults to false.
|
|
35
|
+
*/
|
|
36
|
+
withCredentials?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* SSE Transport mechanism.
|
|
39
|
+
* - 'auto': uses Fetch stream if custom headers or non-browser, otherwise EventSource if available.
|
|
40
|
+
* - 'eventsource': uses standard EventSource.
|
|
41
|
+
* - 'fetch': uses fetch API with ReadableStream.
|
|
42
|
+
* Defaults to 'auto'.
|
|
43
|
+
*/
|
|
44
|
+
transport?: TransportType;
|
|
45
|
+
/**
|
|
46
|
+
* Initial delay in ms before reconnection attempts.
|
|
47
|
+
* Defaults to 1000ms.
|
|
48
|
+
*/
|
|
49
|
+
reconnectBaseDelay?: number;
|
|
50
|
+
/**
|
|
51
|
+
* Maximum reconnect delay in ms.
|
|
52
|
+
* Defaults to 30000ms.
|
|
53
|
+
*/
|
|
54
|
+
reconnectMaxDelay?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Maximum number of reconnect attempts before transitioning to 'failed'.
|
|
57
|
+
* Defaults to Infinity.
|
|
58
|
+
*/
|
|
59
|
+
maxReconnectAttempts?: number;
|
|
60
|
+
/**
|
|
61
|
+
* Heartbeat timeout in ms. If no data/ping is received within this period,
|
|
62
|
+
* the connection is presumed dead and reconnected.
|
|
63
|
+
* Defaults to 45000ms (45s).
|
|
64
|
+
*/
|
|
65
|
+
heartbeatTimeout?: number;
|
|
66
|
+
/**
|
|
67
|
+
* Whether to automatically connect upon instantiation.
|
|
68
|
+
* Defaults to true.
|
|
69
|
+
*/
|
|
70
|
+
autoConnect?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Enable verbose console logging for debugging.
|
|
73
|
+
* Defaults to false.
|
|
74
|
+
*/
|
|
75
|
+
debug?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Endpoint URL for channel authorization (Pusher-compatible authEndpoint).
|
|
78
|
+
* Automatically invoked when subscribing to private or presence channels (e.g. `private-*`).
|
|
79
|
+
*/
|
|
80
|
+
authEndpoint?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Custom authentication headers and parameters sent to `authEndpoint`.
|
|
83
|
+
*/
|
|
84
|
+
auth?: {
|
|
85
|
+
headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
|
|
86
|
+
params?: Record<string, any>;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
type ChannelSubscriptionState = 'unsubscribed' | 'subscribing' | 'subscribed' | 'error';
|
|
90
|
+
interface SSEMessagePayload<T = any> {
|
|
91
|
+
channel?: string;
|
|
92
|
+
event: string;
|
|
93
|
+
data: T;
|
|
94
|
+
id?: string;
|
|
95
|
+
timestamp?: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
declare class Channel {
|
|
99
|
+
readonly name: string;
|
|
100
|
+
state: ChannelSubscriptionState;
|
|
101
|
+
private emitter;
|
|
102
|
+
constructor(name: string);
|
|
103
|
+
/**
|
|
104
|
+
* Whether the channel is currently successfully subscribed.
|
|
105
|
+
*/
|
|
106
|
+
get subscribed(): boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Bind an event listener to this channel.
|
|
109
|
+
*
|
|
110
|
+
* Example:
|
|
111
|
+
* ```ts
|
|
112
|
+
* channel.bind('new-comment', (data) => {
|
|
113
|
+
* console.log(data);
|
|
114
|
+
* });
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
bind<T = any>(event: string, callback: EventCallback<T>): this;
|
|
118
|
+
/**
|
|
119
|
+
* Unbind an event listener from this channel.
|
|
120
|
+
*/
|
|
121
|
+
unbind<T = any>(event?: string, callback?: EventCallback<T>): this;
|
|
122
|
+
/**
|
|
123
|
+
* Bind a global listener to all events received on this channel.
|
|
124
|
+
*
|
|
125
|
+
* Example:
|
|
126
|
+
* ```ts
|
|
127
|
+
* channel.bind_global((eventName, data) => {
|
|
128
|
+
* console.log(`Event ${eventName}:`, data);
|
|
129
|
+
* });
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
bind_global(callback: GlobalEventCallback): this;
|
|
133
|
+
/**
|
|
134
|
+
* Unbind a global listener from this channel.
|
|
135
|
+
*/
|
|
136
|
+
unbind_global(callback?: GlobalEventCallback): this;
|
|
137
|
+
/**
|
|
138
|
+
* Internal method to dispatch an event onto this channel.
|
|
139
|
+
*/
|
|
140
|
+
handleEvent<T = any>(event: string, data: T, metadata?: EventMetadata): void;
|
|
141
|
+
/**
|
|
142
|
+
* Internal method to update subscription status and fire Pusher-compatible subscription events.
|
|
143
|
+
*/
|
|
144
|
+
setSubscribed(data?: any): void;
|
|
145
|
+
/**
|
|
146
|
+
* Internal method to set subscription error.
|
|
147
|
+
*/
|
|
148
|
+
setSubscriptionError(error: any): void;
|
|
149
|
+
/**
|
|
150
|
+
* Internal method called when channel is unsubscribed.
|
|
151
|
+
*/
|
|
152
|
+
setUnsubscribed(): void;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
declare class ConnectionManager {
|
|
156
|
+
state: ConnectionState;
|
|
157
|
+
clientId: string | null;
|
|
158
|
+
/**
|
|
159
|
+
* Pusher-compatible alias for clientId.
|
|
160
|
+
*/
|
|
161
|
+
get socket_id(): string | null;
|
|
162
|
+
private emitter;
|
|
163
|
+
private options;
|
|
164
|
+
private eventSource;
|
|
165
|
+
private abortController;
|
|
166
|
+
private reconnectAttempts;
|
|
167
|
+
private reconnectTimer;
|
|
168
|
+
private heartbeatTimer;
|
|
169
|
+
private isManuallyClosed;
|
|
170
|
+
constructor(url: string, options?: NotifAgentOptions);
|
|
171
|
+
bind<T = any>(event: string, callback: (data: T) => void): this;
|
|
172
|
+
unbind<T = any>(event?: string, callback?: (data: T) => void): this;
|
|
173
|
+
connect(): void;
|
|
174
|
+
disconnect(): void;
|
|
175
|
+
private transition;
|
|
176
|
+
private establishConnection;
|
|
177
|
+
private connectViaEventSource;
|
|
178
|
+
private connectViaFetch;
|
|
179
|
+
private processSSEFrame;
|
|
180
|
+
private parseAndDispatchMessage;
|
|
181
|
+
private handleConnectionSuccess;
|
|
182
|
+
private handleConnectionFailure;
|
|
183
|
+
private scheduleReconnect;
|
|
184
|
+
private resetHeartbeat;
|
|
185
|
+
private clearHeartbeat;
|
|
186
|
+
private clearTimers;
|
|
187
|
+
private closeActiveTransport;
|
|
188
|
+
private buildUrl;
|
|
189
|
+
private resolveHeaders;
|
|
190
|
+
private log;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
declare class NotifAgent {
|
|
194
|
+
readonly connection: ConnectionManager;
|
|
195
|
+
private options;
|
|
196
|
+
private channels;
|
|
197
|
+
private globalEmitter;
|
|
198
|
+
/**
|
|
199
|
+
* Unique client connection ID provided by NotifServer handshake.
|
|
200
|
+
*/
|
|
201
|
+
get clientId(): string | null;
|
|
202
|
+
/**
|
|
203
|
+
* Pusher-compatible alias for clientId.
|
|
204
|
+
*/
|
|
205
|
+
get socket_id(): string | null;
|
|
206
|
+
constructor(urlOrOptions: string | NotifAgentOptions, maybeOptions?: NotifAgentOptions);
|
|
207
|
+
/**
|
|
208
|
+
* Subscribe to a channel.
|
|
209
|
+
*
|
|
210
|
+
* Example:
|
|
211
|
+
* ```ts
|
|
212
|
+
* const channel = agent.subscribe('orders');
|
|
213
|
+
* channel.bind('created', (order) => { ... });
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
subscribe(channelName: string): Channel;
|
|
217
|
+
/**
|
|
218
|
+
* Internal authorization helper for private/presence channels.
|
|
219
|
+
*/
|
|
220
|
+
private authorizeChannel;
|
|
221
|
+
/**
|
|
222
|
+
* Unsubscribe from a channel.
|
|
223
|
+
*/
|
|
224
|
+
unsubscribe(channelName: string): void;
|
|
225
|
+
/**
|
|
226
|
+
* Get an existing subscribed channel by name, if any.
|
|
227
|
+
*/
|
|
228
|
+
channel(channelName: string): Channel | undefined;
|
|
229
|
+
/**
|
|
230
|
+
* Return all currently subscribed channels.
|
|
231
|
+
*/
|
|
232
|
+
allChannels(): Channel[];
|
|
233
|
+
/**
|
|
234
|
+
* Bind to an event across all channels or global events.
|
|
235
|
+
*
|
|
236
|
+
* Example:
|
|
237
|
+
* ```ts
|
|
238
|
+
* agent.bind('system-alert', (data) => console.log(data));
|
|
239
|
+
* ```
|
|
240
|
+
*/
|
|
241
|
+
bind<T = any>(event: string, callback: EventCallback<T>): this;
|
|
242
|
+
/**
|
|
243
|
+
* Unbind an event listener.
|
|
244
|
+
*/
|
|
245
|
+
unbind<T = any>(event?: string, callback?: EventCallback<T>): this;
|
|
246
|
+
/**
|
|
247
|
+
* Bind to all events regardless of name.
|
|
248
|
+
*/
|
|
249
|
+
bind_global(callback: GlobalEventCallback): this;
|
|
250
|
+
/**
|
|
251
|
+
* Unbind a global event listener.
|
|
252
|
+
*/
|
|
253
|
+
unbind_global(callback?: GlobalEventCallback): this;
|
|
254
|
+
/**
|
|
255
|
+
* Connect to the SSE endpoint.
|
|
256
|
+
*/
|
|
257
|
+
connect(): void;
|
|
258
|
+
/**
|
|
259
|
+
* Disconnect from the SSE endpoint.
|
|
260
|
+
*/
|
|
261
|
+
disconnect(): void;
|
|
262
|
+
/**
|
|
263
|
+
* Route incoming SSE message to global emitters and specific channels.
|
|
264
|
+
*/
|
|
265
|
+
private routeMessage;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
declare class EventEmitter {
|
|
269
|
+
private listeners;
|
|
270
|
+
private globalListeners;
|
|
271
|
+
/**
|
|
272
|
+
* Bind a callback to a specific event name.
|
|
273
|
+
*/
|
|
274
|
+
bind<T = any>(event: string, callback: EventCallback<T>): this;
|
|
275
|
+
/**
|
|
276
|
+
* Unbind a callback from a specific event name, or all callbacks if callback is omitted.
|
|
277
|
+
* If both event and callback are omitted, all listeners are removed.
|
|
278
|
+
*/
|
|
279
|
+
unbind<T = any>(event?: string, callback?: EventCallback<T>): this;
|
|
280
|
+
/**
|
|
281
|
+
* Bind a callback to ALL incoming events on this emitter.
|
|
282
|
+
*/
|
|
283
|
+
bind_global(callback: GlobalEventCallback): this;
|
|
284
|
+
/**
|
|
285
|
+
* Unbind a global callback, or all global callbacks if callback is omitted.
|
|
286
|
+
*/
|
|
287
|
+
unbind_global(callback?: GlobalEventCallback): this;
|
|
288
|
+
/**
|
|
289
|
+
* Emit an event to registered listeners and global listeners.
|
|
290
|
+
*/
|
|
291
|
+
emit<T = any>(event: string, data: T, metadata?: EventMetadata): void;
|
|
292
|
+
/**
|
|
293
|
+
* Clear all listeners.
|
|
294
|
+
*/
|
|
295
|
+
clear(): void;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export { Channel, type ChannelSubscriptionState, ConnectionManager, type ConnectionState, type EventCallback, EventEmitter, type EventMetadata, type GlobalEventCallback, NotifAgent, type NotifAgentOptions, type SSEMessagePayload, type StateChangePayload, type TransportType, NotifAgent as default };
|