@hapi/nes 13.0.1 → 14.0.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/lib/client.d.ts +375 -0
- package/lib/client.js +8 -1
- package/lib/index.d.ts +450 -0
- package/lib/listener.js +5 -0
- package/lib/socket.js +3 -3
- package/package.json +16 -13
package/lib/client.d.ts
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import type { ClientRequestArgs } from "http";
|
|
2
|
+
import type { ClientOptions } from "ws";
|
|
3
|
+
|
|
4
|
+
// Same as exported type in @hapi/hapi v20
|
|
5
|
+
type HTTP_METHODS = 'ACL' | 'BIND' | 'CHECKOUT' | 'CONNECT' | 'COPY' | 'DELETE' | 'GET' | 'HEAD' | 'LINK' | 'LOCK' |
|
|
6
|
+
'M-SEARCH' | 'MERGE' | 'MKACTIVITY' | 'MKCALENDAR' | 'MKCOL' | 'MOVE' | 'NOTIFY' | 'OPTIONS' | 'PATCH' | 'POST' |
|
|
7
|
+
'PROPFIND' | 'PROPPATCH' | 'PURGE' | 'PUT' | 'REBIND' | 'REPORT' | 'SEARCH' | 'SOURCE' | 'SUBSCRIBE' | 'TRACE' |
|
|
8
|
+
'UNBIND' | 'UNLINK' | 'UNLOCK' | 'UNSUBSCRIBE';
|
|
9
|
+
|
|
10
|
+
type ErrorType = (
|
|
11
|
+
'timeout' |
|
|
12
|
+
'disconnect' |
|
|
13
|
+
'server' |
|
|
14
|
+
'protocol' |
|
|
15
|
+
'ws' |
|
|
16
|
+
'user'
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
type ErrorCodes = {
|
|
20
|
+
1000: 'Normal closure',
|
|
21
|
+
1001: 'Going away',
|
|
22
|
+
1002: 'Protocol error',
|
|
23
|
+
1003: 'Unsupported data',
|
|
24
|
+
1004: 'Reserved',
|
|
25
|
+
1005: 'No status received',
|
|
26
|
+
1006: 'Abnormal closure',
|
|
27
|
+
1007: 'Invalid frame payload data',
|
|
28
|
+
1008: 'Policy violation',
|
|
29
|
+
1009: 'Message too big',
|
|
30
|
+
1010: 'Mandatory extension',
|
|
31
|
+
1011: 'Internal server error',
|
|
32
|
+
1015: 'TLS handshake'
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type NesLog = {
|
|
36
|
+
|
|
37
|
+
readonly code: keyof ErrorCodes;
|
|
38
|
+
readonly explanation: ErrorCodes[keyof ErrorCodes] | 'Unknown';
|
|
39
|
+
readonly reason: string;
|
|
40
|
+
readonly wasClean: boolean;
|
|
41
|
+
readonly willReconnect: boolean;
|
|
42
|
+
readonly wasRequested: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface NesError extends Error {
|
|
46
|
+
|
|
47
|
+
type: ErrorType;
|
|
48
|
+
isNes: true;
|
|
49
|
+
statusCode?: number;
|
|
50
|
+
data?: any;
|
|
51
|
+
headers?: Record<string, string>;
|
|
52
|
+
path?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ClientConnectOptions {
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* sets the credentials used to authenticate.
|
|
59
|
+
* when the server is configured for
|
|
60
|
+
*
|
|
61
|
+
* - `'token'` type authentication, the value
|
|
62
|
+
* is the token response received from the
|
|
63
|
+
* authentication endpoint (called manually by
|
|
64
|
+
* the application). When the server is
|
|
65
|
+
* configured for `'direct'` type
|
|
66
|
+
* authentication, the value is the credentials
|
|
67
|
+
* expected by the server for the specified
|
|
68
|
+
* authentication strategy used which typically
|
|
69
|
+
* means an object with headers
|
|
70
|
+
* (e.g. `{ headers: { authorization: 'Basic am9objpzZWNyZXQ=' } }`).
|
|
71
|
+
*/
|
|
72
|
+
auth?: string | {
|
|
73
|
+
headers?: Record<string, string>;
|
|
74
|
+
payload?: Record<string, string>;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A boolean that indicates whether the client
|
|
79
|
+
* should try to reconnect. Defaults to `true`.
|
|
80
|
+
*/
|
|
81
|
+
reconnect?: boolean;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Time in milliseconds to wait between each
|
|
85
|
+
* reconnection attempt. The delay time is
|
|
86
|
+
* cumulative, meaning that if the value is set
|
|
87
|
+
* to `1000` (1 second), the first wait will be
|
|
88
|
+
* 1 seconds, then 2 seconds, 3 seconds, until
|
|
89
|
+
* the `maxDelay` value is reached and then
|
|
90
|
+
* `maxDelay` is used.
|
|
91
|
+
*/
|
|
92
|
+
delay?: number;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The maximum delay time in milliseconds
|
|
96
|
+
* between reconnections.
|
|
97
|
+
*/
|
|
98
|
+
maxDelay?: number;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* number of reconnection attempts. Defaults to
|
|
102
|
+
* `Infinity` (unlimited).
|
|
103
|
+
*/
|
|
104
|
+
retries?: number;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* socket connection timeout in milliseconds.
|
|
108
|
+
* Defaults to the WebSocket implementation
|
|
109
|
+
* timeout default.
|
|
110
|
+
*/
|
|
111
|
+
timeout?: number;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
type NesReqRes<R> = {
|
|
115
|
+
payload: R;
|
|
116
|
+
statusCode: number;
|
|
117
|
+
headers: Record<string, string>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface ClientRequestOptions {
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The requested endpoint path or route id.
|
|
124
|
+
*/
|
|
125
|
+
path: string;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The requested HTTP method (can also be any
|
|
129
|
+
* method string supported by the server).
|
|
130
|
+
* Defaults to `'GET'`.
|
|
131
|
+
*/
|
|
132
|
+
method?: Omit<HTTP_METHODS | Lowercase<HTTP_METHODS>, 'HEAD' | 'head'>;
|
|
133
|
+
/**
|
|
134
|
+
* An object where each key is a request header
|
|
135
|
+
* and the value the header content. Cannot
|
|
136
|
+
* include an Authorization header. Defaults to
|
|
137
|
+
* no headers.
|
|
138
|
+
*/
|
|
139
|
+
headers?: Record<string, string>;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The request payload sent to the server.
|
|
143
|
+
*/
|
|
144
|
+
payload?: any;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface NesSubHandler {
|
|
148
|
+
|
|
149
|
+
(
|
|
150
|
+
message: unknown,
|
|
151
|
+
flags: {
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Set to `true` when the message is the
|
|
155
|
+
* last update from the server due to a
|
|
156
|
+
* subscription revocation.
|
|
157
|
+
*/
|
|
158
|
+
revoked?: boolean;
|
|
159
|
+
|
|
160
|
+
}
|
|
161
|
+
): void;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Creates a new client object
|
|
166
|
+
*
|
|
167
|
+
* https://github.com/hapijs/nes/blob/master/API.md#client-5
|
|
168
|
+
*/
|
|
169
|
+
export class Client {
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* https://github.com/hapijs/nes/blob/master/API.md#new-clienturl-options
|
|
173
|
+
* @param url
|
|
174
|
+
* @param options
|
|
175
|
+
*/
|
|
176
|
+
constructor(
|
|
177
|
+
url: `ws://${string}` | `wss://${string}`,
|
|
178
|
+
options?: {
|
|
179
|
+
ws?: ClientOptions | ClientRequestArgs;
|
|
180
|
+
timeout?: number | boolean;
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The unique socket identifier assigned by the
|
|
185
|
+
* server. The value is set after the
|
|
186
|
+
* connection is established.
|
|
187
|
+
*/
|
|
188
|
+
readonly id: string | null;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* A property set by the developer to handle
|
|
192
|
+
* errors. Invoked whenever an error happens
|
|
193
|
+
* that cannot be associated with a pending
|
|
194
|
+
* request.
|
|
195
|
+
*
|
|
196
|
+
* https://github.com/hapijs/nes/blob/master/API.md#clientonerror
|
|
197
|
+
*/
|
|
198
|
+
onError(err: NesError): void;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* A property set by the developer used to set
|
|
202
|
+
* a handler for connection events (initial
|
|
203
|
+
* connection and subsequent reconnections)
|
|
204
|
+
*
|
|
205
|
+
* https://github.com/hapijs/nes/blob/master/API.md#clientonconnect
|
|
206
|
+
*/
|
|
207
|
+
onConnect(): void;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* A property set by the developer used to set
|
|
211
|
+
* a handler for disconnection events
|
|
212
|
+
*
|
|
213
|
+
* https://github.com/hapijs/nes/blob/master/API.md#clientondisconnect
|
|
214
|
+
*
|
|
215
|
+
* @param willReconnect A boolean indicating if
|
|
216
|
+
* the client will automatically attempt to
|
|
217
|
+
* reconnect
|
|
218
|
+
* @param log A log object containing
|
|
219
|
+
* information about the disconnection
|
|
220
|
+
*/
|
|
221
|
+
onDisconnect(willReconnect: boolean, log: NesLog): void;
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* A property set by the developer used to set
|
|
225
|
+
* a handler for heartbeat timeout events
|
|
226
|
+
*
|
|
227
|
+
* https://github.com/hapijs/nes/blob/master/API.md#clientonheartbeattimeout
|
|
228
|
+
*
|
|
229
|
+
* @param willReconnect A boolean indicating if
|
|
230
|
+
* the client will automatically attempt to
|
|
231
|
+
* reconnect
|
|
232
|
+
*/
|
|
233
|
+
onHeartbeatTimeout(willReconnect: boolean): void;
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* A property set by the developer used to set
|
|
237
|
+
* a custom message handler. Invoked whenever
|
|
238
|
+
* the server calls `server.broadcast()` or
|
|
239
|
+
* `socket.send()`.
|
|
240
|
+
*
|
|
241
|
+
* https://github.com/hapijs/nes/blob/master/API.md#clientonupdate
|
|
242
|
+
*
|
|
243
|
+
* @param message
|
|
244
|
+
*/
|
|
245
|
+
|
|
246
|
+
onUpdate(message: unknown): void;
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Connects the client to the server
|
|
251
|
+
*
|
|
252
|
+
* https://github.com/hapijs/nes/blob/master/API.md#await-clientconnectoptions
|
|
253
|
+
*/
|
|
254
|
+
connect(options?: ClientConnectOptions): Promise<void>;
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Disconnects the client from the server and
|
|
258
|
+
* stops future reconnects.
|
|
259
|
+
*
|
|
260
|
+
* https://github.com/hapijs/nes/blob/master/API.md#await-clientdisconnect
|
|
261
|
+
*/
|
|
262
|
+
disconnect(): Promise<void>;
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Sends an endpoint request to the server.
|
|
266
|
+
* This overload will perform a `GET` request by
|
|
267
|
+
* default.
|
|
268
|
+
*
|
|
269
|
+
* Rejects with `Error` if the request failed.
|
|
270
|
+
*
|
|
271
|
+
* https://github.com/hapijs/nes/blob/master/API.md#await-clientrequestoptions
|
|
272
|
+
*
|
|
273
|
+
* @param path The endpoint path
|
|
274
|
+
*/
|
|
275
|
+
request <R = any>(path: string): Promise<NesReqRes<R>>;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Sends an endpoint request to the server.
|
|
279
|
+
*
|
|
280
|
+
* Rejects with `Error` if the request failed.
|
|
281
|
+
*
|
|
282
|
+
* https://github.com/hapijs/nes/blob/master/API.md#await-clientrequestoptions
|
|
283
|
+
*
|
|
284
|
+
* @param options The request options
|
|
285
|
+
*/
|
|
286
|
+
request <R = any>(options: ClientRequestOptions): Promise<NesReqRes<R>>;
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Sends a custom message to the server which
|
|
291
|
+
* is received by the server `onMessage` handler
|
|
292
|
+
*
|
|
293
|
+
* https://github.com/hapijs/nes/blob/master/API.md#await-clientmessagemessage
|
|
294
|
+
*
|
|
295
|
+
* @param message The message sent to the
|
|
296
|
+
* server. Can be any type which can be safely
|
|
297
|
+
* converted to string using `JSON.stringify()`.
|
|
298
|
+
*/
|
|
299
|
+
message <R = any>(message: unknown): Promise<R>;
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Subscribes to a server subscription
|
|
303
|
+
*
|
|
304
|
+
* https://github.com/hapijs/nes/blob/master/API.md#await-clientsubscribepath-handler
|
|
305
|
+
*
|
|
306
|
+
* @param path The requested subscription path.
|
|
307
|
+
* Paths are just like HTTP request paths (e.g.
|
|
308
|
+
* `'/item/5'` or `'/updates'` based on the
|
|
309
|
+
* paths supported by the server).
|
|
310
|
+
*
|
|
311
|
+
* @param handler The function used to receive subscription updates
|
|
312
|
+
*/
|
|
313
|
+
subscribe(path: string, handler: NesSubHandler): Promise<void>;
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Cancels a subscription
|
|
317
|
+
*
|
|
318
|
+
* https://github.com/hapijs/nes/blob/master/API.md#await-clientunsubscribepath-handler
|
|
319
|
+
*
|
|
320
|
+
* @param path the subscription path used to subscribe
|
|
321
|
+
* @param handler remove a specific handler from a
|
|
322
|
+
* subscription or `null` to remove all handlers for
|
|
323
|
+
* the given path
|
|
324
|
+
*/
|
|
325
|
+
unsubscribe(path: string, handler?: NesSubHandler): Promise<void>;
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Returns an array of the current subscription paths.
|
|
329
|
+
*
|
|
330
|
+
* https://github.com/hapijs/nes/blob/master/API.md#clientsubscriptions
|
|
331
|
+
*
|
|
332
|
+
*/
|
|
333
|
+
subscriptions(): string[];
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Sets or overrides the authentication credentials used
|
|
337
|
+
* to reconnect the client on disconnect when the client
|
|
338
|
+
* is configured to automatically reconnect
|
|
339
|
+
*
|
|
340
|
+
* Returns `true` if reconnection is enabled, otherwise
|
|
341
|
+
* `false` (in which case the method was ignored).
|
|
342
|
+
*
|
|
343
|
+
* Note: this will not update the credentials on the
|
|
344
|
+
* server - use `client.reauthenticate()`
|
|
345
|
+
*
|
|
346
|
+
* https://github.com/hapijs/nes/blob/master/API.md#clientoverridereconnectionauthauth
|
|
347
|
+
*
|
|
348
|
+
* @param auth same as the `auth` option passed to
|
|
349
|
+
* `client.connect()`
|
|
350
|
+
*/
|
|
351
|
+
overrideReconnectionAuth(auth: ClientConnectOptions['auth']): boolean;
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Will issue the `reauth` message to the server with
|
|
355
|
+
* updated `auth` details and also override the
|
|
356
|
+
* reconnection information, if reconnection is enabled.
|
|
357
|
+
* The server will respond with an error and drop the
|
|
358
|
+
* connection in case the new `auth` credentials are
|
|
359
|
+
* invalid.
|
|
360
|
+
*
|
|
361
|
+
* Rejects with `Error` if the request failed.
|
|
362
|
+
*
|
|
363
|
+
* Resolves with `true` if the request succeeds.
|
|
364
|
+
*
|
|
365
|
+
* Note: when authentication has a limited lifetime,
|
|
366
|
+
* `reauthenticate()` should be called early enough to
|
|
367
|
+
* avoid the server dropping the connection.
|
|
368
|
+
*
|
|
369
|
+
* https://github.com/hapijs/nes/blob/master/API.md#await-clientreauthenticateauth
|
|
370
|
+
*
|
|
371
|
+
* @param auth same as the `auth` option passed to
|
|
372
|
+
* `client.connect()`
|
|
373
|
+
*/
|
|
374
|
+
reauthenticate(auth: ClientConnectOptions['auth']): Promise<unknown>;
|
|
375
|
+
}
|
package/lib/client.js
CHANGED
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
|
|
105
105
|
options = options || {};
|
|
106
106
|
|
|
107
|
-
this._isBrowser =
|
|
107
|
+
this._isBrowser = Client.isBrowser();
|
|
108
108
|
|
|
109
109
|
if (!this._isBrowser) {
|
|
110
110
|
options.ws = options.ws || {};
|
|
@@ -148,6 +148,13 @@
|
|
|
148
148
|
|
|
149
149
|
Client.WebSocket = /* $lab:coverage:off$ */ (typeof WebSocket === 'undefined' ? null : WebSocket); /* $lab:coverage:on$ */
|
|
150
150
|
|
|
151
|
+
Client.isBrowser = function () {
|
|
152
|
+
|
|
153
|
+
// $lab:coverage:off$
|
|
154
|
+
return typeof WebSocket !== 'undefined' && typeof window !== 'undefined';
|
|
155
|
+
// $lab:coverage:on$
|
|
156
|
+
};
|
|
157
|
+
|
|
151
158
|
Client.prototype.connect = function (options) {
|
|
152
159
|
|
|
153
160
|
options = options || {};
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import * as Hapi from '@hapi/hapi';
|
|
2
|
+
|
|
3
|
+
import * as Iron from '@hapi/iron';
|
|
4
|
+
import { Client } from './client';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
ClientConnectOptions,
|
|
8
|
+
ClientRequestOptions,
|
|
9
|
+
ErrorCodes,
|
|
10
|
+
ErrorType,
|
|
11
|
+
NesError,
|
|
12
|
+
NesLog,
|
|
13
|
+
NesReqRes,
|
|
14
|
+
NesSubHandler
|
|
15
|
+
} from './client';
|
|
16
|
+
|
|
17
|
+
export namespace Nes {
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
ClientConnectOptions,
|
|
21
|
+
ClientRequestOptions,
|
|
22
|
+
ErrorCodes,
|
|
23
|
+
ErrorType,
|
|
24
|
+
NesError,
|
|
25
|
+
NesLog,
|
|
26
|
+
NesReqRes,
|
|
27
|
+
NesSubHandler
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SocketAuthObject<
|
|
31
|
+
U extends object = Hapi.UserCredentials,
|
|
32
|
+
A extends object = Hapi.AuthCredentials,
|
|
33
|
+
> {
|
|
34
|
+
isAuthenticated: boolean;
|
|
35
|
+
credentials: Hapi.AuthCredentials<U, A> | null;
|
|
36
|
+
artifacts: Hapi.AuthArtifacts | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface Socket<
|
|
40
|
+
App extends object = {},
|
|
41
|
+
Auth extends SocketAuthObject<any, any> = SocketAuthObject<any, any>
|
|
42
|
+
> {
|
|
43
|
+
id: string,
|
|
44
|
+
app: App,
|
|
45
|
+
auth: Auth,
|
|
46
|
+
info: {
|
|
47
|
+
remoteAddress: string,
|
|
48
|
+
remotePort: number,
|
|
49
|
+
'x-forwarded-for'?: string,
|
|
50
|
+
}
|
|
51
|
+
server: Hapi.Server,
|
|
52
|
+
disconnect(): Promise<void>,
|
|
53
|
+
send(message: unknown): Promise<void>,
|
|
54
|
+
publish(path: string, message: unknown): Promise<void>,
|
|
55
|
+
revoke(
|
|
56
|
+
path: string,
|
|
57
|
+
message?: unknown | null,
|
|
58
|
+
options?: {
|
|
59
|
+
ignoreClose?: boolean,
|
|
60
|
+
}
|
|
61
|
+
): Promise<void>,
|
|
62
|
+
isOpen(): boolean,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface ClientOpts {
|
|
66
|
+
onDisconnect?: (
|
|
67
|
+
willReconnect: boolean,
|
|
68
|
+
log: {
|
|
69
|
+
code: number,
|
|
70
|
+
explanation: string,
|
|
71
|
+
reason: string,
|
|
72
|
+
wasClean: string,
|
|
73
|
+
willReconnect: boolean,
|
|
74
|
+
wasRequested: boolean,
|
|
75
|
+
}
|
|
76
|
+
) => void
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface BroadcastOptions {
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Optional user filter. When provided, the
|
|
83
|
+
* message will be sent only to
|
|
84
|
+
* authenticated sockets with
|
|
85
|
+
* `credentials.user` equal to `user`.
|
|
86
|
+
* Requires the `auth.index` options to be
|
|
87
|
+
* configured to `true`.
|
|
88
|
+
*/
|
|
89
|
+
user?: string
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
type FilterReturn = (
|
|
93
|
+
boolean | {
|
|
94
|
+
/**
|
|
95
|
+
* an override `message` to send to this `socket`
|
|
96
|
+
* instead of the published one. Note that if you
|
|
97
|
+
* want to modify `message`, you must clone it first
|
|
98
|
+
* or the changes will apply to all other sockets.
|
|
99
|
+
*/
|
|
100
|
+
override: unknown
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
export interface SubscriptionOptions<S extends Socket = Socket<any, any>> {
|
|
105
|
+
/**
|
|
106
|
+
* Publishing filter function for making per-client
|
|
107
|
+
* connection decisions about which matching publication
|
|
108
|
+
* update should be sent to which client.
|
|
109
|
+
* @param path The path of the published update. The path
|
|
110
|
+
* is provided in case the subscription contains path
|
|
111
|
+
* parameters
|
|
112
|
+
* @param message The `JSON.stringify()` compliant
|
|
113
|
+
* message being published
|
|
114
|
+
* @param options Additional information about the
|
|
115
|
+
* subscription and client
|
|
116
|
+
* @returns
|
|
117
|
+
*/
|
|
118
|
+
filter?: (
|
|
119
|
+
path: string,
|
|
120
|
+
message: unknown,
|
|
121
|
+
options: {
|
|
122
|
+
socket: S,
|
|
123
|
+
credentials?: S['auth']['credentials'],
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The parameters parsed from the publish message
|
|
127
|
+
* path if the subscription path contains
|
|
128
|
+
* parameters.
|
|
129
|
+
*/
|
|
130
|
+
params?: unknown,
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The internal options data passed to the
|
|
134
|
+
* `server.publish()` call, if defined.
|
|
135
|
+
*/
|
|
136
|
+
internal: unknown
|
|
137
|
+
},
|
|
138
|
+
) => (FilterReturn | Promise<FilterReturn>),
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* A method called when a client subscribes to this
|
|
142
|
+
* subscription endpoint
|
|
143
|
+
*
|
|
144
|
+
* @param socket The `Socket` object of incoming
|
|
145
|
+
* connection
|
|
146
|
+
* @param path The path the client subscribed to
|
|
147
|
+
* @param params The parameters parsed from the
|
|
148
|
+
* subscription request path if the subscription path
|
|
149
|
+
* definition contains parameters.
|
|
150
|
+
|
|
151
|
+
* @returns
|
|
152
|
+
*/
|
|
153
|
+
onSubscribe?: (
|
|
154
|
+
socket: Socket,
|
|
155
|
+
path: string,
|
|
156
|
+
params?: unknown
|
|
157
|
+
) => void,
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* A method called when a client unsubscribes from this subscription endpoint
|
|
161
|
+
* @param socket The `Socket` object of incoming
|
|
162
|
+
* connection
|
|
163
|
+
* @param path The path the client subscribed to
|
|
164
|
+
* @param params The parameters parsed from the
|
|
165
|
+
* subscription request path if the subscription path
|
|
166
|
+
* definition contains parameters.
|
|
167
|
+
* @returns
|
|
168
|
+
*/
|
|
169
|
+
onUnsubscribe?: (
|
|
170
|
+
socket: Socket,
|
|
171
|
+
path: string,
|
|
172
|
+
params?: unknown
|
|
173
|
+
) => void,
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The subscription authentication options
|
|
177
|
+
*/
|
|
178
|
+
auth?: boolean | {
|
|
179
|
+
/**
|
|
180
|
+
* Same as the ***hapi*** auth modes.
|
|
181
|
+
*/
|
|
182
|
+
mode?: 'required' | 'optional',
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Same as the ***hapi*** auth scopes.
|
|
186
|
+
*/
|
|
187
|
+
scope?: string | string[],
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Same as the ***hapi*** auth entities.
|
|
191
|
+
*/
|
|
192
|
+
entity?: 'user' | 'app' | 'any',
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* if `true`, authenticated socket with `user`
|
|
196
|
+
* property in `credentials` are mapped for usage
|
|
197
|
+
* in `server.publish()` calls. Defaults to `false`.
|
|
198
|
+
*/
|
|
199
|
+
index?: boolean,
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export interface PublishOptions {
|
|
204
|
+
/**
|
|
205
|
+
* Optional user filter. When provided, the message will
|
|
206
|
+
* be sent only to authenticated sockets with
|
|
207
|
+
* `credentials.user` equal to `user`. Requires the
|
|
208
|
+
* subscription `auth.index` options to be configured to
|
|
209
|
+
* `true`.
|
|
210
|
+
*/
|
|
211
|
+
user?: string,
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Internal data that is passed to `filter` and may be
|
|
215
|
+
* used to filter messages on data that is not sent to
|
|
216
|
+
* the client.
|
|
217
|
+
*/
|
|
218
|
+
internal?: unknown
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export interface EachSocketOptions {
|
|
222
|
+
/**
|
|
223
|
+
* When set to a string path, limits the results to sockets that are subscribed to that path.
|
|
224
|
+
*/
|
|
225
|
+
subscription?: string,
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Optional user filter. When provided, the `each` method
|
|
229
|
+
* will be invoked with authenticated sockets with
|
|
230
|
+
* `credentials.user` equal to `user`. Requires the
|
|
231
|
+
* subscription `auth.index` options to be configured to
|
|
232
|
+
* `true`.
|
|
233
|
+
*/
|
|
234
|
+
user?: string
|
|
235
|
+
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Plugin options
|
|
240
|
+
*
|
|
241
|
+
* https://github.com/hapijs/nes/blob/master/API.md#registration
|
|
242
|
+
*/
|
|
243
|
+
export interface PluginOptions<
|
|
244
|
+
App extends object = {},
|
|
245
|
+
Auth extends SocketAuthObject<any, any> = SocketAuthObject<any, any>
|
|
246
|
+
> {
|
|
247
|
+
/**
|
|
248
|
+
* A function invoked for each incoming connection
|
|
249
|
+
* @param socket The `Socket` object of incoming
|
|
250
|
+
* connection
|
|
251
|
+
*/
|
|
252
|
+
onConnection?: (socket: Socket<App, Auth>) => void
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* A function invoked for each disconnection
|
|
256
|
+
* @param socket The `Socket` object of incoming
|
|
257
|
+
* connection
|
|
258
|
+
*/
|
|
259
|
+
onDisconnection?: (socket: Socket<App, Auth>) => void
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* A function used to receive custom client messages
|
|
263
|
+
* @param message The message sent by the client
|
|
264
|
+
* @returns
|
|
265
|
+
*/
|
|
266
|
+
onMessage?: (
|
|
267
|
+
socket: Socket<App, Auth>,
|
|
268
|
+
message: unknown
|
|
269
|
+
) => void
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Optional plugin authentication options. The details of
|
|
273
|
+
* this object do imply quiet a bit of logic, so it is
|
|
274
|
+
* best to see the documentation for more information.
|
|
275
|
+
*
|
|
276
|
+
* https://github.com/hapijs/nes/blob/master/API.md#registration
|
|
277
|
+
*/
|
|
278
|
+
auth?: false | {
|
|
279
|
+
endpoint?: string
|
|
280
|
+
id?: string
|
|
281
|
+
type?: 'cookie' | 'token' | 'direct',
|
|
282
|
+
route?: Hapi.RouteOptions<any>['auth'],
|
|
283
|
+
cookie?: string,
|
|
284
|
+
isSecure?: boolean,
|
|
285
|
+
isHttpOnly?: boolean,
|
|
286
|
+
isSameSite?: 'Strict' | 'Lax' | false,
|
|
287
|
+
path?: string | null,
|
|
288
|
+
domain?: string | null,
|
|
289
|
+
ttl?: number | null,
|
|
290
|
+
iron?: Iron.SealOptions,
|
|
291
|
+
password?: Iron.Password | Iron.password.Secret,
|
|
292
|
+
index?: boolean,
|
|
293
|
+
timeout?: number | false,
|
|
294
|
+
maxConnectionsPerUser?: number | false,
|
|
295
|
+
minAuthVerifyInterval?: number | false,
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* An optional array of header field names to include in
|
|
300
|
+
* server responses to the client. If set to `'*'`
|
|
301
|
+
* (without an array), allows all headers. Defaults to
|
|
302
|
+
* `null` (no headers).
|
|
303
|
+
*/
|
|
304
|
+
headers?: string[] | '*' | null,
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Optional message payload
|
|
308
|
+
*/
|
|
309
|
+
payload?: {
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* the maximum number of characters (after the full
|
|
313
|
+
* protocol object is converted to a string using
|
|
314
|
+
* `JSON.stringify()`) allowed in a single WebSocket
|
|
315
|
+
* message. This is important when using the protocol
|
|
316
|
+
* over a slow network (e.g. mobile) with large
|
|
317
|
+
* updates as the transmission time can exceed the
|
|
318
|
+
* timeout or heartbeat limits which will cause the
|
|
319
|
+
* client to disconnect. Defaults to `false`
|
|
320
|
+
* (no limit).
|
|
321
|
+
*/
|
|
322
|
+
maxChunkChars?: number | false,
|
|
323
|
+
},
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Configures connection keep-alive settings.
|
|
327
|
+
* When set to `false`, the server will not send
|
|
328
|
+
* heartbeats. Defaults to:
|
|
329
|
+
*
|
|
330
|
+
* ```js
|
|
331
|
+
* {
|
|
332
|
+
* interval: 15000,
|
|
333
|
+
* timeout: 5000
|
|
334
|
+
* }
|
|
335
|
+
* ```
|
|
336
|
+
*/
|
|
337
|
+
heartbeat?: false | {
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* The time interval between heartbeat messages in
|
|
341
|
+
* milliseconds. Defaults to `15000` (15 seconds).
|
|
342
|
+
*/
|
|
343
|
+
interval: number,
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* timeout in milliseconds after a heartbeat is sent
|
|
347
|
+
* to the client and before the client is considered
|
|
348
|
+
* disconnected by the server. Defaults to `5000`
|
|
349
|
+
* (5 seconds).
|
|
350
|
+
*/
|
|
351
|
+
timeout?: number,
|
|
352
|
+
},
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* If specified, limits the number of simultaneous client
|
|
356
|
+
* connections. Defaults to `false`.
|
|
357
|
+
*/
|
|
358
|
+
maxConnections?: number | false,
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* An origin string or an array of origin strings
|
|
362
|
+
* incoming client requests must match for the connection
|
|
363
|
+
* to be permitted. Defaults to no origin validation.
|
|
364
|
+
*/
|
|
365
|
+
origins?: string | string[]
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export { Client }
|
|
370
|
+
|
|
371
|
+
export const plugin: Hapi.Plugin<Nes.PluginOptions>;
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
declare module '@hapi/hapi' {
|
|
375
|
+
|
|
376
|
+
interface Server {
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Sends a message to all connected clients
|
|
380
|
+
* where:
|
|
381
|
+
*
|
|
382
|
+
* https://hapi.dev/module/nes/api/?v=13.0.1#await-serverbroadcastmessage-options
|
|
383
|
+
*
|
|
384
|
+
* @param message The message sent to the
|
|
385
|
+
* clients. Can be any type which can be
|
|
386
|
+
* safely converted to string using `JSON.
|
|
387
|
+
* stringify()`.
|
|
388
|
+
* @param options An optional object
|
|
389
|
+
*/
|
|
390
|
+
broadcast(message: unknown, options?: Nes.BroadcastOptions): void;
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Declares a subscription path client can
|
|
394
|
+
* subscribe to where:
|
|
395
|
+
*
|
|
396
|
+
* https://hapi.dev/module/nes/api/?v=13.0.1#serversubscriptionpath-options
|
|
397
|
+
*
|
|
398
|
+
* @param path An HTTP-like path. The path
|
|
399
|
+
* must begin with the `'/'` character. The
|
|
400
|
+
* path may contain path parameters as
|
|
401
|
+
* supported by the ***hapi*** route path parser.
|
|
402
|
+
|
|
403
|
+
* @param options An optional object
|
|
404
|
+
*/
|
|
405
|
+
subscription(path: string, options?: Nes.SubscriptionOptions): void;
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Sends a message to all the subscribed clients
|
|
409
|
+
*
|
|
410
|
+
* https://github.com/hapijs/nes/blob/master/API.md#await-serverpublishpath-message-options
|
|
411
|
+
*
|
|
412
|
+
* @param path the subscription path. The path is matched
|
|
413
|
+
* first against the available subscriptions added via
|
|
414
|
+
* `server.subscription()` and then against the specific
|
|
415
|
+
* path provided by each client at the time of
|
|
416
|
+
* registration (only matter when the subscription path
|
|
417
|
+
* contains parameters). When a match is found, the
|
|
418
|
+
* subscription `filter` function is called (if present)
|
|
419
|
+
* to further filter which client should receive which
|
|
420
|
+
* update.
|
|
421
|
+
*
|
|
422
|
+
* @param message The message sent to the clients. Can be any type which can be safely converted to string using `JSON.stringify()`.
|
|
423
|
+
* @param options optional object
|
|
424
|
+
*/
|
|
425
|
+
publish(path: string, message: unknown, options?: Nes.PublishOptions): void;
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Iterates over all connected sockets, optionally
|
|
429
|
+
* filtering on those that have subscribed to a given
|
|
430
|
+
* subscription. This operation is synchronous
|
|
431
|
+
*
|
|
432
|
+
* @param each Iteration method
|
|
433
|
+
* @param options Optional options
|
|
434
|
+
*/
|
|
435
|
+
eachSocket(
|
|
436
|
+
each: (socket: Nes.Socket) => void,
|
|
437
|
+
options?: Nes.EachSocketOptions
|
|
438
|
+
): void;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
interface Request {
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Provides access to the `Socket` object of the incoming
|
|
445
|
+
* connection
|
|
446
|
+
*/
|
|
447
|
+
socket: Nes.Socket;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
package/lib/listener.js
CHANGED
|
@@ -33,6 +33,7 @@ exports = module.exports = internals.Listener = function (server, settings) {
|
|
|
33
33
|
// WebSocket listener
|
|
34
34
|
|
|
35
35
|
const options = { server: this._server.listener };
|
|
36
|
+
|
|
36
37
|
if (settings.origin) {
|
|
37
38
|
options.verifyClient = (info) => settings.origin.indexOf(info.origin) >= 0;
|
|
38
39
|
}
|
|
@@ -103,6 +104,10 @@ internals.Listener.prototype._close = async function () {
|
|
|
103
104
|
await Promise.all(Object.keys(this._sockets._items).map((id) => this._sockets._items[id].disconnect()));
|
|
104
105
|
|
|
105
106
|
this._wss.close();
|
|
107
|
+
|
|
108
|
+
for (const ws of this._wss.clients) {
|
|
109
|
+
ws.terminate();
|
|
110
|
+
}
|
|
106
111
|
};
|
|
107
112
|
|
|
108
113
|
|
package/lib/socket.js
CHANGED
|
@@ -237,7 +237,7 @@ internals.Socket.prototype._onMessage = async function (message) {
|
|
|
237
237
|
try {
|
|
238
238
|
var request = JSON.parse(message);
|
|
239
239
|
}
|
|
240
|
-
catch
|
|
240
|
+
catch {
|
|
241
241
|
return this._error(Boom.badRequest('Cannot parse message'));
|
|
242
242
|
}
|
|
243
243
|
|
|
@@ -563,7 +563,7 @@ internals.Socket.prototype._authenticate = async function (request) {
|
|
|
563
563
|
const auth = await Iron.unseal(request.auth, config.password, config.iron ?? Iron.defaults);
|
|
564
564
|
this._setCredentials(auth);
|
|
565
565
|
}
|
|
566
|
-
catch
|
|
566
|
+
catch {
|
|
567
567
|
throw Boom.unauthorized('Invalid token');
|
|
568
568
|
}
|
|
569
569
|
}
|
|
@@ -585,7 +585,7 @@ internals.Socket.prototype._authByCookie = async function () {
|
|
|
585
585
|
try {
|
|
586
586
|
var { states } = await this.server.states.parse(cookies);
|
|
587
587
|
}
|
|
588
|
-
catch
|
|
588
|
+
catch {
|
|
589
589
|
throw Boom.unauthorized('Invalid nes authentication cookie');
|
|
590
590
|
}
|
|
591
591
|
|
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hapi/nes",
|
|
3
3
|
"description": "WebSocket adapter plugin for hapi routes",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "14.0.1",
|
|
5
5
|
"repository": "git://github.com/hapijs/nes",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/index.d.ts",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=18.0.0"
|
|
10
|
+
},
|
|
7
11
|
"files": [
|
|
8
12
|
"lib"
|
|
9
13
|
],
|
|
@@ -12,30 +16,29 @@
|
|
|
12
16
|
"plugin",
|
|
13
17
|
"websocket"
|
|
14
18
|
],
|
|
15
|
-
"eslintConfig": {
|
|
16
|
-
"extends": [
|
|
17
|
-
"plugin:@hapi/module"
|
|
18
|
-
]
|
|
19
|
-
},
|
|
20
19
|
"dependencies": {
|
|
21
20
|
"@hapi/boom": "^10.0.1",
|
|
22
|
-
"@hapi/bounce": "^3.0.
|
|
21
|
+
"@hapi/bounce": "^3.0.2",
|
|
23
22
|
"@hapi/call": "^9.0.1",
|
|
24
23
|
"@hapi/cryptiles": "^6.0.1",
|
|
25
|
-
"@hapi/hoek": "^11.0.
|
|
24
|
+
"@hapi/hoek": "^11.0.7",
|
|
26
25
|
"@hapi/iron": "^7.0.1",
|
|
27
26
|
"@hapi/teamwork": "^6.0.0",
|
|
28
27
|
"@hapi/validate": "^2.0.1",
|
|
29
|
-
"ws": "^
|
|
28
|
+
"@types/ws": "^8.5.13",
|
|
29
|
+
"ws": "^8.18.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@hapi/code": "^9.0.3",
|
|
33
|
-
"@hapi/eslint-plugin": "
|
|
34
|
-
"@hapi/hapi": "^21.
|
|
35
|
-
"@hapi/lab": "^
|
|
33
|
+
"@hapi/eslint-plugin": "^7.0.0",
|
|
34
|
+
"@hapi/hapi": "^21.3.12",
|
|
35
|
+
"@hapi/lab": "^26.0.0",
|
|
36
|
+
"@types/node": "^20.17.12",
|
|
37
|
+
"joi": "^17.13.3",
|
|
38
|
+
"typescript": "^5.4.5"
|
|
36
39
|
},
|
|
37
40
|
"scripts": {
|
|
38
|
-
"test": "lab -a @hapi/code -t 100 -L -m 10000",
|
|
41
|
+
"test": "lab -a @hapi/code -t 100 -L -m 10000 -Y",
|
|
39
42
|
"test-cov-html": "lab -a @hapi/code -r html -o coverage.html -m 10000"
|
|
40
43
|
},
|
|
41
44
|
"license": "BSD-3-Clause"
|