@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.
@@ -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 = 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/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 (err) {
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 (err) {
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 (err) {
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": "13.0.1",
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.1",
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.2",
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": "^7.3.1"
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.2.1",
35
- "@hapi/lab": "^25.1.2"
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"