@logux/server 0.13.1 → 0.15.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/request/index.js CHANGED
@@ -1,45 +1,7 @@
1
- import fetch from 'node-fetch'
2
-
3
- function argsToString(url, opts, response) {
4
- let { method, ...other } = opts
5
- return `${method || 'GET'} ${url} ${JSON.stringify(other)} ${response}`
6
- }
7
-
8
1
  export class ResponseError extends Error {
9
- constructor(statusCode, url, opts, response) {
10
- super(`${statusCode} response on ${argsToString(url, opts, response)}`)
2
+ constructor(statusCode, url) {
3
+ super(`${statusCode} response on ${url}`)
11
4
  this.name = 'ResponseError'
12
5
  this.statusCode = statusCode
13
6
  }
14
7
  }
15
-
16
- export async function request(url, opts = {}, fetcher = fetch) {
17
- let response = await fetcher(url, opts)
18
- if (response.status === 204) {
19
- return undefined
20
- } else if (response.status >= 200 && response.status <= 299) {
21
- return response.json()
22
- } else {
23
- throw new ResponseError(response.status, url, opts, await response.text())
24
- }
25
- }
26
-
27
- export function get(url, opts = {}, fetcher = fetch) {
28
- return request(url, { ...opts, method: 'GET' }, fetcher)
29
- }
30
-
31
- export function post(url, opts = {}, fetcher = fetch) {
32
- return request(url, { ...opts, method: 'POST' }, fetcher)
33
- }
34
-
35
- export function put(url, opts = {}, fetcher = fetch) {
36
- return request(url, { ...opts, method: 'PUT' }, fetcher)
37
- }
38
-
39
- export function patch(url, opts = {}, fetcher = fetch) {
40
- return request(url, { ...opts, method: 'PATCH' }, fetcher)
41
- }
42
-
43
- export function del(url, opts = {}, fetcher = fetch) {
44
- return request(url, { ...opts, method: 'DELETE' }, fetcher)
45
- }
package/server/index.d.ts CHANGED
@@ -1,21 +1,31 @@
1
- import {
1
+ import type { Log } from '@logux/core'
2
+
3
+ import type {
2
4
  BaseServer,
3
- type BaseServerOptions,
4
- type Logger
5
+ BaseServerOptions,
6
+ Logger,
7
+ ServerMeta
5
8
  } from '../base-server/index.js'
6
9
 
10
+ export interface LogStream {
11
+ /**
12
+ * Used to synchronously write log messages on application failure.
13
+ */
14
+ flushSync?(): void
15
+
16
+ write(str: string): void
17
+ }
18
+
7
19
  export interface LoggerOptions {
8
20
  /**
9
- * Stream to be used by logger to write log.
21
+ * Use color for human output.
10
22
  */
11
- stream?: {
12
- /**
13
- * Used by pino to synchronously write log messages on application failure.
14
- */
15
- flushSync?(): void
23
+ color?: boolean
16
24
 
17
- write(str: string): void
18
- }
25
+ /**
26
+ * Stream to be used by logger to write log.
27
+ */
28
+ stream?: LogStream
19
29
 
20
30
  /**
21
31
  * Logger message format.
@@ -28,28 +38,6 @@ export interface ServerOptions extends BaseServerOptions {
28
38
  * Logger with custom settings.
29
39
  *
30
40
  * You can either configure built-in logger or provide your own.
31
- * Custom logger example: pino logger that streams logs to
32
- * elasticsearch
33
- *
34
- * ```js
35
- * import pino from 'pino'
36
- * import pinoElastic from 'pino-elasticsearch'
37
- *
38
- * const streamToElastic = pinoElastic({
39
- * consistency: 'one',
40
- * node: 'http://localhost:9200',
41
- * ecs: true
42
- * })
43
- *
44
- * const server = new Server(
45
- * Server.loadOptions(process, {
46
- * …,
47
- * logger: pino({ level: 'info' }, streamToElastic)
48
- * })
49
- * )
50
- * ```
51
- *
52
- * Other logger examples can be found here http://getpino.io/#/docs/ecosystem
53
41
  */
54
42
  logger?: Logger | LoggerOptions
55
43
  }
@@ -68,15 +56,18 @@ export interface ServerOptions extends BaseServerOptions {
68
56
  * }
69
57
  *
70
58
  * const server = new Server(Object.assign({
71
- * subprotocol: '1.0.0',
72
- * supports: '1.x || 0.x',
73
- * fileUrl: import.meta.url
59
+ * subprotocol: 1,
60
+ * minSubprotocol: 1,
61
+ * root: import.meta.dirname
74
62
  * }, envOptions))
75
63
  *
76
64
  * server.listen()
77
65
  * ```
78
66
  */
79
- export class Server<Headers extends object = {}> extends BaseServer<Headers> {
67
+ export class Server<
68
+ Headers extends object = unknown,
69
+ ClientData extends object = unknown
70
+ > extends BaseServer<Headers, Log<ServerMeta>, ClientData> {
80
71
  /**
81
72
  * Server options.
82
73
  *
@@ -96,9 +87,9 @@ export class Server<Headers extends object = {}> extends BaseServer<Headers> {
96
87
  *
97
88
  * ```js
98
89
  * const server = new Server(Server.loadOptions(process, {
99
- * subprotocol: '1.0.0',
100
- * supports: '1.x',
101
- * fileUrl: import.meta.url,
90
+ * minSubprotocol: 1,
91
+ * subprotocol: 1,
92
+ * root: import.meta.dirname,
102
93
  * port: 31337
103
94
  * }))
104
95
  * ```
package/server/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import glob from 'fast-glob'
2
- import { join, relative } from 'node:path'
3
- import pico from 'picocolors'
1
+ import { glob } from 'node:fs/promises'
2
+ import { join, matchesGlob, relative } from 'node:path'
3
+ import { styleText } from 'node:util'
4
4
 
5
5
  import { BaseServer } from '../base-server/index.js'
6
6
  import { createReporter } from '../create-reporter/index.js'
@@ -13,18 +13,9 @@ let cliOptionsSpec = {
13
13
  'LOGUX_PORT=1337 LOGUX_HOST=127.0.0.1 $0'
14
14
  ],
15
15
  options: {
16
- backend: {
17
- description: 'Backend to process actions'
18
- },
19
16
  cert: {
20
17
  description: 'Path to SSL certificate'
21
18
  },
22
- controlMask: {
23
- description: 'CIDR masks of control servers'
24
- },
25
- controlSecret: {
26
- description: 'Secret to control Logux server'
27
- },
28
19
  host: {
29
20
  alias: 'h',
30
21
  description: 'Host to bind server'
@@ -37,6 +28,9 @@ let cliOptionsSpec = {
37
28
  description: 'Logger type',
38
29
  parse: value => oneOf(['human', 'json'], value)
39
30
  },
31
+ minSubprotocol: {
32
+ description: 'Check supported client subprotocols'
33
+ },
40
34
  port: {
41
35
  alias: 'p',
42
36
  description: 'Port to bind server',
@@ -47,9 +41,6 @@ let cliOptionsSpec = {
47
41
  },
48
42
  subprotocol: {
49
43
  description: 'Server subprotocol'
50
- },
51
- supports: {
52
- description: 'Range of supported client subprotocols'
53
44
  }
54
45
  }
55
46
  }
@@ -106,7 +97,7 @@ export class Server extends BaseServer {
106
97
  let [help, options] = loadOptions(
107
98
  cliOptionsSpec,
108
99
  process,
109
- defaults.root ? { path: join(defaults.root, '.env') } : undefined
100
+ defaults.root ? join(defaults.root, '.env') : undefined
110
101
  )
111
102
  if (help) {
112
103
  process.stdout.write(help + '\n')
@@ -116,7 +107,10 @@ export class Server extends BaseServer {
116
107
  return Object.assign(defaults, options)
117
108
  } catch (e) {
118
109
  process.stderr.write(
119
- `${pico.bgRed(pico.black(' FATAL '))} ${e.message}\n`
110
+ styleText(
111
+ 'red',
112
+ styleText('bgRed', styleText('black', ' FATAL ')) + `${e.message}\n`
113
+ )
120
114
  )
121
115
  return process.exit(1)
122
116
  }
@@ -125,14 +119,24 @@ export class Server extends BaseServer {
125
119
  async autoloadModules(
126
120
  files = ['modules/*/index.js', 'modules/*.js', '!**/*.{test,spec}.js']
127
121
  ) {
128
- let matches = await glob(files, {
129
- absolute: true,
122
+ if (!Array.isArray(files)) files = [files]
123
+ let ignore = files.filter(i => i.startsWith('!')).map(i => i.slice(1))
124
+ let patterns = files.filter(i => !i.startsWith('!'))
125
+
126
+ let matches = new Set()
127
+ for await (let entry of glob(patterns, {
130
128
  cwd: this.options.root,
131
- onlyFiles: true
132
- })
129
+ withFileTypes: true
130
+ })) {
131
+ if (!entry.isFile()) continue
132
+ let file = join(entry.parentPath, entry.name)
133
+ let path = relative(this.options.root, file)
134
+ if (ignore.some(i => matchesGlob(path, i))) continue
135
+ matches.add(file)
136
+ }
133
137
 
134
138
  await Promise.all(
135
- matches.map(async file => {
139
+ [...matches].map(async file => {
136
140
  let serverModule = (await import(file)).default
137
141
  if (typeof serverModule === 'function') {
138
142
  await serverModule(this)
@@ -9,7 +9,7 @@ import type { BaseServer } from '../base-server/index.js'
9
9
  * const client = server.connected.get(0)
10
10
  * ```
11
11
  */
12
- export class ServerClient {
12
+ export class ServerClient<ClientData extends object = unknown> {
13
13
  /**
14
14
  * Server, which received client.
15
15
  */
@@ -30,6 +30,23 @@ export class ServerClient {
30
30
  */
31
31
  connection: ServerConnection
32
32
 
33
+ /**
34
+ * Open structure to save data for the whole connection.
35
+ *
36
+ * ```js
37
+ * server.auth(async ({ client, userId, token }) => {
38
+ * let session = await findSession(token)
39
+ * if (!session) return false
40
+ * client.data.sessionId = session.id
41
+ * return true
42
+ * })
43
+ * server.on('disconnected', client => {
44
+ * touchSession(client.data.sessionId)
45
+ * })
46
+ * ```
47
+ */
48
+ data: ClientData
49
+
33
50
  /**
34
51
  * HTTP headers of WS connection.
35
52
  *
@@ -102,17 +119,9 @@ export class ServerClient {
102
119
  destroy(): void
103
120
 
104
121
  /**
105
- * Check client subprotocol version. It uses `semver` npm package
106
- * to parse requirements.
107
- *
108
- * ```js
109
- * if (client.isSubprotocol('4.x')) {
110
- * useOldAPI()
111
- * }
112
- * ```
122
+ * Wait until the client will confirm all sent actions.
113
123
  *
114
- * @param range npm’s version requirements.
115
- * @returns Is version satisfies requirements.
124
+ * @returns Promise with `false` if the client was disconnected.
116
125
  */
117
- isSubprotocol(range: string): boolean
126
+ drain(): Promise<boolean>
118
127
  }
@@ -1,9 +1,9 @@
1
1
  import { LoguxError, parseId } from '@logux/core'
2
- import cookie from 'cookie'
2
+ import { parseCookie } from 'cookie'
3
3
  import fastq from 'fastq'
4
- import semver from 'semver'
5
4
 
6
5
  import { ALLOWED_META } from '../allowed-meta/index.js'
6
+ import { Context } from '../context/index.js'
7
7
  import { filterMeta } from '../filter-meta/index.js'
8
8
  import { FilteredNode } from '../filtered-node/index.js'
9
9
 
@@ -11,6 +11,8 @@ async function onSend(action, meta) {
11
11
  return [action, filterMeta(meta)]
12
12
  }
13
13
 
14
+ function ignore() {}
15
+
14
16
  function reportDetails(client) {
15
17
  return {
16
18
  connectionId: client.key,
@@ -70,7 +72,9 @@ export class ServerClient {
70
72
  this.userId = undefined
71
73
  this.clientId = undefined
72
74
  this.nodeId = undefined
75
+ this.data = {}
73
76
  this.processing = false
77
+ this.sending = new Set()
74
78
  this.connection = connection
75
79
  this.key = key.toString()
76
80
  if (connection.ws) {
@@ -81,18 +85,35 @@ export class ServerClient {
81
85
  this.httpHeaders = {}
82
86
  }
83
87
 
84
- this.node = new FilteredNode(this, app.nodeId, app.log, connection, {
88
+ let Node = app.options.Node || FilteredNode
89
+
90
+ this.node = new Node(this, app.nodeId, app.log, connection, {
85
91
  auth: this.auth.bind(this),
86
92
  onReceive: this.onReceive.bind(this),
87
93
  onSend,
88
94
  ping: app.options.ping,
89
95
  subprotocol: app.options.subprotocol,
96
+ syncBatch: app.options.syncBatch,
90
97
  timeout: app.options.timeout
91
98
  })
92
99
  if (this.app.env === 'development') {
93
100
  this.node.setLocalHeaders({ env: 'development' })
94
101
  }
95
102
 
103
+ if (app.connectLoader) {
104
+ this.node.syncSinceQuery = async lastSynced => {
105
+ let context = new Context(app, this)
106
+ let entries = await app.connectLoader(context, lastSynced)
107
+ let added = 0
108
+ for (let entry of entries) {
109
+ // `added` should be taken before `filterMeta()` removes it
110
+ if (entry[1].added > added) added = entry[1].added
111
+ entry[1] = filterMeta(entry[1])
112
+ }
113
+ return { added, entries }
114
+ }
115
+ }
116
+
96
117
  this.node.catch(err => {
97
118
  err.connectionId = this.key
98
119
  this.app.emitter.emit('error', err)
@@ -116,10 +137,10 @@ export class ServerClient {
116
137
  this.clientId = clientId
117
138
  this.userId = userId
118
139
 
119
- if (this.app.options.supports) {
120
- if (!this.isSubprotocol(this.app.options.supports)) {
140
+ if (this.app.options.minSubprotocol) {
141
+ if (this.node.remoteSubprotocol < this.app.options.minSubprotocol) {
121
142
  throw new LoguxError('wrong-subprotocol', {
122
- supported: this.app.options.supports,
143
+ supported: this.app.options.minSubprotocol,
123
144
  used: this.node.remoteSubprotocol
124
145
  })
125
146
  }
@@ -142,13 +163,14 @@ export class ServerClient {
142
163
  try {
143
164
  result = await this.app.authenticator({
144
165
  client: this,
145
- cookie: cookie.parse(headers.cookie || ''),
166
+ cookie: parseCookie(headers.cookie || ''),
146
167
  headers: this.node.remoteHeaders,
147
168
  token,
148
169
  userId: this.userId
149
170
  })
150
171
  } catch (e) {
151
172
  if (e.name === 'LoguxError') {
173
+ /* c8 ignore next 1 */
152
174
  throw e
153
175
  } else {
154
176
  e.nodeId = nodeId
@@ -223,8 +245,21 @@ export class ServerClient {
223
245
  this.app.connected.delete(this.key)
224
246
  }
225
247
 
226
- isSubprotocol(range) {
227
- return semver.satisfies(this.node.remoteSubprotocol, range)
248
+ async drain() {
249
+ if (this.sending.size > 0) await Promise.all(this.sending)
250
+ if (this.destroyed || !this.node.connected) return false
251
+ if (this.node.syncing === 0) return true
252
+ return new Promise(resolve => {
253
+ let unbind = this.node.on('state', () => {
254
+ if (this.node.state === 'synchronized') {
255
+ unbind()
256
+ resolve(true)
257
+ } else if (!this.node.connected) {
258
+ unbind()
259
+ resolve(false)
260
+ }
261
+ })
262
+ })
228
263
  }
229
264
 
230
265
  onReceive(action, meta) {
@@ -251,8 +286,10 @@ export class ServerClient {
251
286
 
252
287
  if (isChannel) {
253
288
  for (let channel of this.app.channels) {
254
- let pattern = channel.regexp || channel.pattern.regex
255
- if (action.channel.match(pattern)) {
289
+ let match = channel.regexp
290
+ ? action.channel.match(channel.regexp)
291
+ : channel.pattern(action.channel)
292
+ if (match) {
256
293
  queueName = channel.queue
257
294
  break
258
295
  }
@@ -287,4 +324,12 @@ export class ServerClient {
287
324
  })
288
325
  })
289
326
  }
327
+
328
+ track(sending) {
329
+ let done = Promise.resolve(sending).then(ignore, ignore)
330
+ this.sending.add(done)
331
+ void done.then(() => {
332
+ this.sending.delete(done)
333
+ })
334
+ }
290
335
  }
@@ -1,5 +1,14 @@
1
- import type { LoguxSubscribeAction, LoguxUnsubscribeAction } from '@logux/actions'
2
- import type { Action, AnyAction, TestLog, TestPair } from '@logux/core'
1
+ import type {
2
+ LoguxSubscribeAction,
3
+ LoguxUnsubscribeAction
4
+ } from '@logux/actions'
5
+ import type {
6
+ Action,
7
+ AnyAction,
8
+ ClientNode,
9
+ TestLog,
10
+ TestPair
11
+ } from '@logux/core'
3
12
 
4
13
  import type { ServerMeta } from '../base-server/index.js'
5
14
  import type { TestServer } from '../test-server/index.js'
@@ -12,7 +21,7 @@ export interface TestClientOptions {
12
21
  cookie?: object
13
22
  headers?: object
14
23
  httpHeaders?: { [key: string]: string }
15
- subprotocol?: string
24
+ subprotocol?: number
16
25
  token?: string
17
26
  }
18
27
 
@@ -77,6 +86,11 @@ export class TestClient {
77
86
  */
78
87
  log: TestLog
79
88
 
89
+ /**
90
+ * Logux node.
91
+ */
92
+ node: ClientNode<object, TestLog<ServerMeta>>
93
+
80
94
  /**
81
95
  * Client’s node ID.
82
96
  *
@@ -127,7 +141,7 @@ export class TestClient {
127
141
  * @param test Function, where do you expect action will be received
128
142
  * @returns Promise with all received actions
129
143
  */
130
- collect(test: () => Promise<any>): Promise<Action[]>
144
+ collect(test: () => Promise<unknown>): Promise<Action[]>
131
145
 
132
146
  /**
133
147
  * Connect to test server.
@@ -166,7 +180,10 @@ export class TestClient {
166
180
  * @param meta Optional action’s meta.
167
181
  * @returns Promise until `logux/processed` answer.
168
182
  */
169
- process(action: AnyAction, meta?: Partial<ServerMeta>): Promise<Action[]>
183
+ process<TypeAction extends Action = AnyAction>(
184
+ action: TypeAction,
185
+ meta?: Partial<ServerMeta>
186
+ ): Promise<Action[]>
170
187
 
171
188
  /**
172
189
  * Collect actions received from server during the `test` call.
@@ -181,10 +198,10 @@ export class TestClient {
181
198
  * @param test Function, where do you expect action will be received
182
199
  * @returns Promise with all received actions
183
200
  */
184
- received(test: () => any | Promise<any>): Promise<Action[]>
201
+ received(test: () => unknown): Promise<Action[]>
185
202
 
186
203
  /**
187
- * Subscribe to the channel and collect all actions dunring the subscription.
204
+ * Subscribe to the channel and collect all actions during the subscription.
188
205
  *
189
206
  * ```js
190
207
  * let posts = await client.subscribe('posts')
@@ -1,5 +1,5 @@
1
1
  import { ClientNode, TestPair } from '@logux/core'
2
- import cookie from 'cookie'
2
+ import { stringifyCookie } from 'cookie'
3
3
  import { setTimeout } from 'node:timers/promises'
4
4
 
5
5
  import { filterMeta } from '../filter-meta/index.js'
@@ -34,9 +34,9 @@ export class TestClient {
34
34
  this.node.setLocalHeaders(opts.headers)
35
35
  }
36
36
  if (opts.cookie) {
37
- this.pair.right.ws.upgradeReq.headers.cookie = Object.keys(opts.cookie)
38
- .map(i => cookie.serialize(i, opts.cookie[i]))
39
- .join('; ')
37
+ this.pair.right.ws.upgradeReq.headers.cookie = stringifyCookie(
38
+ opts.cookie
39
+ )
40
40
  }
41
41
  server.unbind.push(() => {
42
42
  this.node.destroy()
@@ -46,7 +46,7 @@ export class TestClient {
46
46
  async collect(test) {
47
47
  let added = []
48
48
  let unbind = this.node.log.on('add', (action, meta) => {
49
- if (!meta.id.includes(` ${this.nodeId} `)) {
49
+ if (meta.id.split(' ')[1] !== this.nodeId) {
50
50
  added.push(action)
51
51
  }
52
52
  })
@@ -67,8 +67,8 @@ export class TestClient {
67
67
  })
68
68
 
69
69
  this.server.addClient(this.pair.right)
70
- this.node.connection.connect()
71
- this.node.waitFor('synchronized').then(() => {
70
+ void this.node.connection.connect()
71
+ void this.node.waitFor('synchronized').then(() => {
72
72
  this.node.throwsError = true
73
73
  unbind()
74
74
  resolve()
@@ -136,7 +136,7 @@ export class TestClient {
136
136
  async received(test) {
137
137
  let actions = []
138
138
  let unbind = this.log.on('add', (action, meta) => {
139
- if (!meta.id.includes(` ${this.nodeId} `)) {
139
+ if (meta.id.split(' ')[1] !== this.nodeId) {
140
140
  actions.push(action)
141
141
  }
142
142
  })
@@ -1,7 +1,7 @@
1
- import type { TestLog, TestTime } from '@logux/core'
1
+ import type { Log, TestLog, TestTime } from '@logux/core'
2
2
 
3
- import { BaseServer } from '../base-server/index.js'
4
3
  import type {
4
+ BaseServer,
5
5
  BaseServerOptions,
6
6
  Logger,
7
7
  ServerMeta
@@ -9,8 +9,10 @@ import type {
9
9
  import type { LoggerOptions } from '../server/index.js'
10
10
  import type { TestClient, TestClientOptions } from '../test-client/index.js'
11
11
 
12
- export interface TestServerOptions
13
- extends Omit<BaseServerOptions, 'subprotocol' | 'supports'> {
12
+ export interface TestServerOptions extends Omit<
13
+ BaseServerOptions,
14
+ 'minSubprotocol' | 'subprotocol'
15
+ > {
14
16
  /**
15
17
  * Disable built-in auth.
16
18
  */
@@ -21,9 +23,9 @@ export interface TestServerOptions
21
23
  */
22
24
  logger?: Logger | LoggerOptions
23
25
 
24
- subprotocol?: string
26
+ minSubprotocol?: number
25
27
 
26
- supports?: string
28
+ subprotocol?: number
27
29
  }
28
30
 
29
31
  /**
@@ -46,8 +48,22 @@ export interface TestServerOptions
46
48
  * ```
47
49
  */
48
50
  export class TestServer<
49
- Headers extends object = {}
50
- > extends BaseServer<Headers> {
51
+ Headers extends object = unknown,
52
+ ClientData extends object = unknown
53
+ > extends BaseServer<Headers, Log<ServerMeta>, ClientData> {
54
+ /**
55
+ * fetch() compatible API to test HTTP endpoints.
56
+ *
57
+ * ```js
58
+ * server.http('GET', '/version', (req, res) => {
59
+ * res.end('1.0.0')
60
+ * })
61
+ * let res = await server.fetch()
62
+ * expect(await res.text()).toEqual('1.0.0')
63
+ * ```
64
+ */
65
+ fetch: typeof fetch
66
+
51
67
  /**
52
68
  * Server actions log, with methods to check actions inside.
53
69
  *
@@ -93,7 +109,7 @@ export class TestServer<
93
109
  *
94
110
  * @param test Callback with subscripting or action sending.
95
111
  */
96
- expectDenied(test: () => any | Promise<any>): Promise<void>
112
+ expectDenied(test: () => unknown): Promise<void>
97
113
 
98
114
  /**
99
115
  * Call callback and throw an error if there was no error during
@@ -102,10 +118,7 @@ export class TestServer<
102
118
  * @param text RegExp or string of error message.
103
119
  * @param test Callback with subscripting or action sending.
104
120
  */
105
- expectError(
106
- text: RegExp | string,
107
- test: () => any | Promise<any>
108
- ): Promise<void>
121
+ expectError(text: RegExp | string, test: () => unknown): Promise<void>
109
122
 
110
123
  /**
111
124
  * Call callback and throw an error if there was no `logux/undo` in return
@@ -120,7 +133,7 @@ export class TestServer<
120
133
  * @param reason The reason in undo action.
121
134
  * @param test Callback with subscripting or action sending.
122
135
  */
123
- expectUndo(reason: string, test: () => any | Promise<any>): Promise<void>
136
+ expectUndo(reason: string, test: () => unknown): Promise<void>
124
137
 
125
138
  /**
126
139
  * Try to connect client and throw an error is client didn’t received