@hapi/nes 13.0.1 → 14.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.
@@ -0,0 +1,372 @@
1
+ // Same as exported type in @hapi/hapi v20
2
+ type HTTP_METHODS = 'ACL' | 'BIND' | 'CHECKOUT' | 'CONNECT' | 'COPY' | 'DELETE' | 'GET' | 'HEAD' | 'LINK' | 'LOCK' |
3
+ 'M-SEARCH' | 'MERGE' | 'MKACTIVITY' | 'MKCALENDAR' | 'MKCOL' | 'MOVE' | 'NOTIFY' | 'OPTIONS' | 'PATCH' | 'POST' |
4
+ 'PROPFIND' | 'PROPPATCH' | 'PURGE' | 'PUT' | 'REBIND' | 'REPORT' | 'SEARCH' | 'SOURCE' | 'SUBSCRIBE' | 'TRACE' |
5
+ 'UNBIND' | 'UNLINK' | 'UNLOCK' | 'UNSUBSCRIBE';
6
+
7
+ type ErrorType = (
8
+ 'timeout' |
9
+ 'disconnect' |
10
+ 'server' |
11
+ 'protocol' |
12
+ 'ws' |
13
+ 'user'
14
+ );
15
+
16
+ type ErrorCodes = {
17
+ 1000: 'Normal closure',
18
+ 1001: 'Going away',
19
+ 1002: 'Protocol error',
20
+ 1003: 'Unsupported data',
21
+ 1004: 'Reserved',
22
+ 1005: 'No status received',
23
+ 1006: 'Abnormal closure',
24
+ 1007: 'Invalid frame payload data',
25
+ 1008: 'Policy violation',
26
+ 1009: 'Message too big',
27
+ 1010: 'Mandatory extension',
28
+ 1011: 'Internal server error',
29
+ 1015: 'TLS handshake'
30
+ };
31
+
32
+ type NesLog = {
33
+
34
+ readonly code: keyof ErrorCodes;
35
+ readonly explanation: ErrorCodes[keyof ErrorCodes] | 'Unknown';
36
+ readonly reason: string;
37
+ readonly wasClean: boolean;
38
+ readonly willReconnect: boolean;
39
+ readonly wasRequested: boolean;
40
+ }
41
+
42
+ export interface NesError extends Error {
43
+
44
+ type: ErrorType;
45
+ isNes: true;
46
+ statusCode?: number;
47
+ data?: any;
48
+ headers?: Record<string, string>;
49
+ path?: string;
50
+ }
51
+
52
+ export interface ClientConnectOptions {
53
+
54
+ /**
55
+ * sets the credentials used to authenticate.
56
+ * when the server is configured for
57
+ *
58
+ * - `'token'` type authentication, the value
59
+ * is the token response received from the
60
+ * authentication endpoint (called manually by
61
+ * the application). When the server is
62
+ * configured for `'direct'` type
63
+ * authentication, the value is the credentials
64
+ * expected by the server for the specified
65
+ * authentication strategy used which typically
66
+ * means an object with headers
67
+ * (e.g. `{ headers: { authorization: 'Basic am9objpzZWNyZXQ=' } }`).
68
+ */
69
+ auth?: string | {
70
+ headers?: Record<string, string>;
71
+ payload?: Record<string, string>;
72
+ };
73
+
74
+ /**
75
+ * A boolean that indicates whether the client
76
+ * should try to reconnect. Defaults to `true`.
77
+ */
78
+ reconnect?: boolean;
79
+
80
+ /**
81
+ * Time in milliseconds to wait between each
82
+ * reconnection attempt. The delay time is
83
+ * cumulative, meaning that if the value is set
84
+ * to `1000` (1 second), the first wait will be
85
+ * 1 seconds, then 2 seconds, 3 seconds, until
86
+ * the `maxDelay` value is reached and then
87
+ * `maxDelay` is used.
88
+ */
89
+ delay?: number;
90
+
91
+ /**
92
+ * The maximum delay time in milliseconds
93
+ * between reconnections.
94
+ */
95
+ maxDelay?: number;
96
+
97
+ /**
98
+ * number of reconnection attempts. Defaults to
99
+ * `Infinity` (unlimited).
100
+ */
101
+ retries?: number;
102
+
103
+ /**
104
+ * socket connection timeout in milliseconds.
105
+ * Defaults to the WebSocket implementation
106
+ * timeout default.
107
+ */
108
+ timeout?: number;
109
+ }
110
+
111
+ type NesReqRes<R> = {
112
+ payload: R;
113
+ statusCode: number;
114
+ headers: Record<string, string>;
115
+ }
116
+
117
+ export interface ClientRequestOptions {
118
+
119
+ /**
120
+ * The requested endpoint path or route id.
121
+ */
122
+ path: string;
123
+
124
+ /**
125
+ * The requested HTTP method (can also be any
126
+ * method string supported by the server).
127
+ * Defaults to `'GET'`.
128
+ */
129
+ method?: Omit<HTTP_METHODS | Lowercase<HTTP_METHODS>, 'HEAD' | 'head'>;
130
+ /**
131
+ * An object where each key is a request header
132
+ * and the value the header content. Cannot
133
+ * include an Authorization header. Defaults to
134
+ * no headers.
135
+ */
136
+ headers?: Record<string, string>;
137
+
138
+ /**
139
+ * The request payload sent to the server.
140
+ */
141
+ payload?: any;
142
+ }
143
+
144
+ export interface NesSubHandler {
145
+
146
+ (
147
+ message: unknown,
148
+ flags: {
149
+
150
+ /**
151
+ * Set to `true` when the message is the
152
+ * last update from the server due to a
153
+ * subscription revocation.
154
+ */
155
+ revoked?: boolean;
156
+
157
+ }
158
+ ): void;
159
+ }
160
+
161
+ /**
162
+ * Creates a new client object
163
+ *
164
+ * https://github.com/hapijs/nes/blob/master/API.md#client-5
165
+ */
166
+ export class Client {
167
+
168
+ /**
169
+ * https://github.com/hapijs/nes/blob/master/API.md#new-clienturl-options
170
+ * @param url
171
+ * @param options
172
+ */
173
+ constructor(
174
+ url: `ws://${string}` | `wss://${string}`,
175
+ options?: {
176
+ ws?: string | string[];
177
+ timeout?: number | boolean;
178
+ });
179
+
180
+ /**
181
+ * The unique socket identifier assigned by the
182
+ * server. The value is set after the
183
+ * connection is established.
184
+ */
185
+ readonly id: string | null;
186
+
187
+ /**
188
+ * A property set by the developer to handle
189
+ * errors. Invoked whenever an error happens
190
+ * that cannot be associated with a pending
191
+ * request.
192
+ *
193
+ * https://github.com/hapijs/nes/blob/master/API.md#clientonerror
194
+ */
195
+ onError(err: NesError): void;
196
+
197
+ /**
198
+ * A property set by the developer used to set
199
+ * a handler for connection events (initial
200
+ * connection and subsequent reconnections)
201
+ *
202
+ * https://github.com/hapijs/nes/blob/master/API.md#clientonconnect
203
+ */
204
+ onConnect(): void;
205
+
206
+ /**
207
+ * A property set by the developer used to set
208
+ * a handler for disconnection events
209
+ *
210
+ * https://github.com/hapijs/nes/blob/master/API.md#clientondisconnect
211
+ *
212
+ * @param willReconnect A boolean indicating if
213
+ * the client will automatically attempt to
214
+ * reconnect
215
+ * @param log A log object containing
216
+ * information about the disconnection
217
+ */
218
+ onDisconnect(willReconnect: boolean, log: NesLog): void;
219
+
220
+ /**
221
+ * A property set by the developer used to set
222
+ * a handler for heartbeat timeout events
223
+ *
224
+ * https://github.com/hapijs/nes/blob/master/API.md#clientonheartbeattimeout
225
+ *
226
+ * @param willReconnect A boolean indicating if
227
+ * the client will automatically attempt to
228
+ * reconnect
229
+ */
230
+ onHeartbeatTimeout(willReconnect: boolean): void;
231
+
232
+ /**
233
+ * A property set by the developer used to set
234
+ * a custom message handler. Invoked whenever
235
+ * the server calls `server.broadcast()` or
236
+ * `socket.send()`.
237
+ *
238
+ * https://github.com/hapijs/nes/blob/master/API.md#clientonupdate
239
+ *
240
+ * @param message
241
+ */
242
+
243
+ onUpdate(message: unknown): void;
244
+
245
+
246
+ /**
247
+ * Connects the client to the server
248
+ *
249
+ * https://github.com/hapijs/nes/blob/master/API.md#await-clientconnectoptions
250
+ */
251
+ connect(options?: ClientConnectOptions): Promise<void>;
252
+
253
+ /**
254
+ * Disconnects the client from the server and
255
+ * stops future reconnects.
256
+ *
257
+ * https://github.com/hapijs/nes/blob/master/API.md#await-clientdisconnect
258
+ */
259
+ disconnect(): Promise<void>;
260
+
261
+ /**
262
+ * Sends an endpoint request to the server.
263
+ * This overload will perform a `GET` request by
264
+ * default.
265
+ *
266
+ * Rejects with `Error` if the request failed.
267
+ *
268
+ * https://github.com/hapijs/nes/blob/master/API.md#await-clientrequestoptions
269
+ *
270
+ * @param path The endpoint path
271
+ */
272
+ request <R = any>(path: string): Promise<NesReqRes<R>>;
273
+
274
+ /**
275
+ * Sends an endpoint request to the server.
276
+ *
277
+ * Rejects with `Error` if the request failed.
278
+ *
279
+ * https://github.com/hapijs/nes/blob/master/API.md#await-clientrequestoptions
280
+ *
281
+ * @param options The request options
282
+ */
283
+ request <R = any>(options: ClientRequestOptions): Promise<NesReqRes<R>>;
284
+
285
+
286
+ /**
287
+ * Sends a custom message to the server which
288
+ * is received by the server `onMessage` handler
289
+ *
290
+ * https://github.com/hapijs/nes/blob/master/API.md#await-clientmessagemessage
291
+ *
292
+ * @param message The message sent to the
293
+ * server. Can be any type which can be safely
294
+ * converted to string using `JSON.stringify()`.
295
+ */
296
+ message <R = any>(message: unknown): Promise<R>;
297
+
298
+ /**
299
+ * Subscribes to a server subscription
300
+ *
301
+ * https://github.com/hapijs/nes/blob/master/API.md#await-clientsubscribepath-handler
302
+ *
303
+ * @param path The requested subscription path.
304
+ * Paths are just like HTTP request paths (e.g.
305
+ * `'/item/5'` or `'/updates'` based on the
306
+ * paths supported by the server).
307
+ *
308
+ * @param handler The function used to receive subscription updates
309
+ */
310
+ subscribe(path: string, handler: NesSubHandler): Promise<void>;
311
+
312
+ /**
313
+ * Cancels a subscription
314
+ *
315
+ * https://github.com/hapijs/nes/blob/master/API.md#await-clientunsubscribepath-handler
316
+ *
317
+ * @param path the subscription path used to subscribe
318
+ * @param handler remove a specific handler from a
319
+ * subscription or `null` to remove all handlers for
320
+ * the given path
321
+ */
322
+ unsubscribe(path: string, handler?: NesSubHandler): Promise<void>;
323
+
324
+ /**
325
+ * Returns an array of the current subscription paths.
326
+ *
327
+ * https://github.com/hapijs/nes/blob/master/API.md#clientsubscriptions
328
+ *
329
+ */
330
+ subscriptions(): string[];
331
+
332
+ /**
333
+ * Sets or overrides the authentication credentials used
334
+ * to reconnect the client on disconnect when the client
335
+ * is configured to automatically reconnect
336
+ *
337
+ * Returns `true` if reconnection is enabled, otherwise
338
+ * `false` (in which case the method was ignored).
339
+ *
340
+ * Note: this will not update the credentials on the
341
+ * server - use `client.reauthenticate()`
342
+ *
343
+ * https://github.com/hapijs/nes/blob/master/API.md#clientoverridereconnectionauthauth
344
+ *
345
+ * @param auth same as the `auth` option passed to
346
+ * `client.connect()`
347
+ */
348
+ overrideReconnectionAuth(auth: ClientConnectOptions['auth']): boolean;
349
+
350
+ /**
351
+ * Will issue the `reauth` message to the server with
352
+ * updated `auth` details and also override the
353
+ * reconnection information, if reconnection is enabled.
354
+ * The server will respond with an error and drop the
355
+ * connection in case the new `auth` credentials are
356
+ * invalid.
357
+ *
358
+ * Rejects with `Error` if the request failed.
359
+ *
360
+ * Resolves with `true` if the request succeeds.
361
+ *
362
+ * Note: when authentication has a limited lifetime,
363
+ * `reauthenticate()` should be called early enough to
364
+ * avoid the server dropping the connection.
365
+ *
366
+ * https://github.com/hapijs/nes/blob/master/API.md#await-clientreauthenticateauth
367
+ *
368
+ * @param auth same as the `auth` option passed to
369
+ * `client.connect()`
370
+ */
371
+ reauthenticate(auth: ClientConnectOptions['auth']): Promise<unknown>;
372
+ }
package/lib/client.js CHANGED
@@ -104,7 +104,7 @@
104
104
 
105
105
  options = options || {};
106
106
 
107
- this._isBrowser = typeof WebSocket !== 'undefined';
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/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": "13.0.1",
4
+ "version": "14.0.0",
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
  ],
@@ -26,16 +30,19 @@
26
30
  "@hapi/iron": "^7.0.1",
27
31
  "@hapi/teamwork": "^6.0.0",
28
32
  "@hapi/validate": "^2.0.1",
29
- "ws": "^7.3.1"
33
+ "ws": "^8.17.1"
30
34
  },
31
35
  "devDependencies": {
32
36
  "@hapi/code": "^9.0.3",
33
37
  "@hapi/eslint-plugin": "*",
34
- "@hapi/hapi": "^21.2.1",
35
- "@hapi/lab": "^25.1.2"
38
+ "@hapi/hapi": "^21.3.9",
39
+ "@hapi/lab": "^25.2.0",
40
+ "@types/node": "^20.14.2",
41
+ "joi": "^17.13.3",
42
+ "typescript": "^5.4.5"
36
43
  },
37
44
  "scripts": {
38
- "test": "lab -a @hapi/code -t 100 -L -m 10000",
45
+ "test": "lab -a @hapi/code -t 100 -L -m 10000 -Y",
39
46
  "test-cov-html": "lab -a @hapi/code -r html -o coverage.html -m 10000"
40
47
  },
41
48
  "license": "BSD-3-Clause"