vrpc 3.0.0-alpha.2 → 3.0.2
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/CHANGELOG.md +60 -0
- package/LICENSE +1 -1
- package/README.md +12 -15
- package/browser/vrpc.js +1 -1
- package/package.json +8 -5
- package/vrpc/VrpcAdapter.js +229 -171
- package/vrpc/VrpcAgent.js +22 -22
- package/vrpc/VrpcClient.js +305 -289
- package/vrpc/VrpcNative.js +25 -2
- package/vrpc/adapter.hpp +4 -2
- package/vrpc/addon.cpp +1 -1
- package/vrpc/vrpc-agent-js +1 -1
package/vrpc/VrpcClient.js
CHANGED
|
@@ -16,7 +16,7 @@ Author: Dr. Burkhard C. Heisen (https://github.com/heisenware/vrpc)
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
|
|
19
|
-
Copyright (c) 2018 -
|
|
19
|
+
Copyright (c) 2018 - 2022 Dr. Burkhard C. Heisen <burkhard.heisen@heisenware.com>.
|
|
20
20
|
|
|
21
21
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
22
22
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -70,7 +70,7 @@ class VrpcClient extends EventEmitter {
|
|
|
70
70
|
* @param {Boolean} [options.bestEffort=false] If true, message will be sent with best effort, i.e. no caching if offline
|
|
71
71
|
* @example
|
|
72
72
|
* const client = new VrpcClient({
|
|
73
|
-
* domain: '
|
|
73
|
+
* domain: 'vrpc',
|
|
74
74
|
* broker: 'mqtt://vrpc.io'
|
|
75
75
|
* })
|
|
76
76
|
*/
|
|
@@ -116,7 +116,7 @@ class VrpcClient extends EventEmitter {
|
|
|
116
116
|
this._invokeId = 0
|
|
117
117
|
this._proxyId = 0
|
|
118
118
|
this._client = null
|
|
119
|
-
this._cachedSubscriptions =
|
|
119
|
+
this._cachedSubscriptions = {}
|
|
120
120
|
this._proxies = {}
|
|
121
121
|
if (log === 'console') {
|
|
122
122
|
this._log = console
|
|
@@ -152,7 +152,6 @@ class VrpcClient extends EventEmitter {
|
|
|
152
152
|
let username = this._username
|
|
153
153
|
let password = this._password
|
|
154
154
|
if (this._token) {
|
|
155
|
-
username = '__token__'
|
|
156
155
|
password = this._token
|
|
157
156
|
}
|
|
158
157
|
const options = {
|
|
@@ -162,6 +161,7 @@ class VrpcClient extends EventEmitter {
|
|
|
162
161
|
keepalive: 30,
|
|
163
162
|
clientId: this._mqttClientId,
|
|
164
163
|
rejectUnauthorized: false,
|
|
164
|
+
connectTimeout: this._timeout,
|
|
165
165
|
will: {
|
|
166
166
|
topic: `${this._vrpcClientId}/__clientInfo__`,
|
|
167
167
|
payload: JSON.stringify({ status: 'offline' })
|
|
@@ -229,10 +229,12 @@ class VrpcClient extends EventEmitter {
|
|
|
229
229
|
staticFunctions,
|
|
230
230
|
meta
|
|
231
231
|
} = json
|
|
232
|
-
if (removed.length !== 0)
|
|
232
|
+
if (removed.length !== 0) {
|
|
233
233
|
this.emit('instanceGone', removed, { domain, agent, className })
|
|
234
|
-
|
|
234
|
+
}
|
|
235
|
+
if (added.length !== 0) {
|
|
235
236
|
this.emit('instanceNew', added, { domain, agent, className })
|
|
237
|
+
}
|
|
236
238
|
this.emit('class', {
|
|
237
239
|
domain,
|
|
238
240
|
agent,
|
|
@@ -248,31 +250,29 @@ class VrpcClient extends EventEmitter {
|
|
|
248
250
|
this._eventEmitter.emit(i, { a, e, r })
|
|
249
251
|
}
|
|
250
252
|
})
|
|
251
|
-
|
|
252
|
-
|
|
253
|
+
return new Promise((resolve, reject) => {
|
|
254
|
+
this._client.once('offline', () => {
|
|
255
|
+
this._client.end()
|
|
253
256
|
reject(new Error(`Connection trial timed out (> ${this._timeout} ms)`))
|
|
254
|
-
}, this._timeout)
|
|
255
|
-
this.once('connect', () => {
|
|
256
|
-
clearTimeout(timer)
|
|
257
|
-
resolve()
|
|
258
257
|
})
|
|
258
|
+
this._client.once('connect', resolve)
|
|
259
259
|
})
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
/**
|
|
263
263
|
* Creates a new remote instance and provides a proxy to it.
|
|
264
264
|
*
|
|
265
|
-
* Remote instances can be "
|
|
266
|
-
*
|
|
267
|
-
* explicitly deleted. Life-cycle changes of
|
|
268
|
-
* under the `class`, `instanceNew`, and `instanceGone` events. A
|
|
269
|
-
* instance is created
|
|
265
|
+
* Remote instances can be "shared" or "isolated". Shared instances are
|
|
266
|
+
* visible and re-attachable across clients as long as they are not
|
|
267
|
+
* explicitly deleted. Life-cycle changes of shared instances are available
|
|
268
|
+
* under the `class`, `instanceNew`, and `instanceGone` events. A shared
|
|
269
|
+
* instance is created by default (`isIsolated: false`).
|
|
270
270
|
*
|
|
271
|
-
* When the `
|
|
272
|
-
*
|
|
273
|
-
*
|
|
271
|
+
* When the `isIsolated` option is true, the remote instance stays invisible
|
|
272
|
+
* to other clients and the corresponding proxy object is the only way to
|
|
273
|
+
* issue commands.
|
|
274
274
|
*
|
|
275
|
-
* **NOTE** When creating
|
|
275
|
+
* **NOTE** When creating an instance that already exists, the new proxy will
|
|
276
276
|
* simply attach to (and not re-create) it - just like `getInstance()` was
|
|
277
277
|
* called.
|
|
278
278
|
*
|
|
@@ -280,28 +280,32 @@ class VrpcClient extends EventEmitter {
|
|
|
280
280
|
* @param {String} options.className Name of the class which should be
|
|
281
281
|
* instantiated
|
|
282
282
|
* @param {String} [options.instance] Name of the created instance. If not
|
|
283
|
-
* provided an
|
|
283
|
+
* provided an id will be generated
|
|
284
284
|
* @param {Array} [options.args] Positional arguments for the constructor call
|
|
285
285
|
* @param {String} [options.agent] Agent name. If not provided class default
|
|
286
286
|
* is used
|
|
287
|
-
* @param {bool} [options.cacheProxy=false] If true the proxy object for a
|
|
288
|
-
* instance is cached and used in subsequent calls
|
|
289
|
-
* @
|
|
290
|
-
*
|
|
287
|
+
* @param {bool} [options.cacheProxy=false] If true the proxy object for a
|
|
288
|
+
* given instance is cached and (re-)used in subsequent calls
|
|
289
|
+
* @param {bool} [options.isIsolated=false] If true the created proxy will be
|
|
290
|
+
* visible only to the client who created it
|
|
291
|
+
* @returns {Promise<Proxy>} Object reflecting a proxy to the original object
|
|
292
|
+
* which is handled by the agent
|
|
291
293
|
* @example
|
|
292
|
-
* // create
|
|
294
|
+
* // create isolated instance
|
|
293
295
|
* const proxy1 = await client.create({
|
|
294
|
-
* className: 'Foo'
|
|
296
|
+
* className: 'Foo',
|
|
297
|
+
* instance: 'myPersonalInstance',
|
|
298
|
+
* isIsolated: true
|
|
295
299
|
* })
|
|
296
|
-
* // create
|
|
300
|
+
* // create shared instance
|
|
297
301
|
* const proxy2 = await client.create({
|
|
298
302
|
* className: 'Foo',
|
|
299
|
-
* instance: '
|
|
303
|
+
* instance: 'aSharedFooInstance'
|
|
300
304
|
* })
|
|
301
|
-
* // create
|
|
305
|
+
* // create shared instance providing three constructor arguments
|
|
302
306
|
* const proxy3 = await client.create({
|
|
303
307
|
* className: 'Bar',
|
|
304
|
-
* instance: '
|
|
308
|
+
* instance: 'mySharedBarInstance',
|
|
305
309
|
* args: [42, "second argument", { some: 'option' }]
|
|
306
310
|
* })
|
|
307
311
|
*/
|
|
@@ -317,9 +321,9 @@ class VrpcClient extends EventEmitter {
|
|
|
317
321
|
const json = {
|
|
318
322
|
c: className,
|
|
319
323
|
f: isIsolated ? '__createIsolated__' : '__createShared__',
|
|
324
|
+
a: [instance, ...args],
|
|
320
325
|
i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
|
|
321
326
|
s: `${this._domain}/${os.hostname()}/${this._instance}`,
|
|
322
|
-
a: [instance, ...args],
|
|
323
327
|
v: VRPC_PROTOCOL_VERSION
|
|
324
328
|
}
|
|
325
329
|
const proxy = await this._getProxy(agent, className, json)
|
|
@@ -336,37 +340,15 @@ class VrpcClient extends EventEmitter {
|
|
|
336
340
|
*
|
|
337
341
|
* @param {String} instance The instance to be retrieved
|
|
338
342
|
* @param {Object} [options] Explicitly define agent and class
|
|
339
|
-
* @param {String} options.className Name of the instance's class
|
|
340
|
-
* @param {String} options.agent Agent name. If not provided class default is used
|
|
341
|
-
* @param {bool} options.noWait If true immediately fail if instance could not be found in local cache
|
|
343
|
+
* @param {String} [options.className] Name of the instance's class
|
|
344
|
+
* @param {String} [options.agent] Agent name. If not provided class default is used as priority hit
|
|
345
|
+
* @param {bool} [options.noWait=false] If true immediately fail if instance could not be found in local cache
|
|
342
346
|
* @returns {Promise<Proxy>} Proxy object reflecting the remotely existing instance
|
|
343
347
|
*/
|
|
344
|
-
async getInstance (instance, options) {
|
|
348
|
+
async getInstance (instance, options = {}) {
|
|
345
349
|
if (this._proxies[instance]) return this._proxies[instance]
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
agent,
|
|
349
|
-
className,
|
|
350
|
-
instance: instanceString
|
|
351
|
-
} = await this._getInstanceData(instance, options)
|
|
352
|
-
return this._createProxy(agent, className, instanceString)
|
|
353
|
-
} else {
|
|
354
|
-
// deprecate this
|
|
355
|
-
this._indicateDeprecation(
|
|
356
|
-
'VrpcClient.getInstance(): ' +
|
|
357
|
-
'Using an object as single argument is deprecated, ' +
|
|
358
|
-
'use "getInstance(instance, options)" instead.'
|
|
359
|
-
)
|
|
360
|
-
const fakedOptions = { ...instance }
|
|
361
|
-
const fakedInstance = fakedOptions.instance
|
|
362
|
-
delete fakedOptions.instance
|
|
363
|
-
const {
|
|
364
|
-
agent,
|
|
365
|
-
className,
|
|
366
|
-
instance: instanceString
|
|
367
|
-
} = await this._getInstanceData(fakedInstance, fakedOptions)
|
|
368
|
-
return this._createProxy(agent, className, instanceString)
|
|
369
|
-
}
|
|
350
|
+
const { agent, className } = await this._getInstanceData(instance, options)
|
|
351
|
+
return this._createProxy(agent, className, instance)
|
|
370
352
|
}
|
|
371
353
|
|
|
372
354
|
/**
|
|
@@ -378,36 +360,23 @@ class VrpcClient extends EventEmitter {
|
|
|
378
360
|
* @param {String} instance The instance to be deleted
|
|
379
361
|
* @param {Object} [options] Explicitly define agent and class
|
|
380
362
|
* @param {String} options.className Name of the instance's class
|
|
381
|
-
* @param {String} options.agent Agent name. If not provided class default is used
|
|
363
|
+
* @param {String} options.agent Agent name. If not provided class default is used as priority hit
|
|
382
364
|
* @returns {Promise<Boolean>} true if successful, false otherwise
|
|
383
365
|
*/
|
|
384
|
-
async delete (instance, options) {
|
|
385
|
-
|
|
386
|
-
if (typeof instance === 'string') {
|
|
387
|
-
instanceData = await this._getInstanceData(instance)
|
|
388
|
-
if (options) instanceData = { ...options, ...instanceData }
|
|
389
|
-
} else {
|
|
390
|
-
// deprecate this
|
|
391
|
-
this._indicateDeprecation(
|
|
392
|
-
'VrpcClient.delete(): ' +
|
|
393
|
-
'Using an object as single argument is deprecated, ' +
|
|
394
|
-
'use "delete(instance, options)" instead.'
|
|
395
|
-
)
|
|
396
|
-
instanceData = { ...instanceData, ...instance }
|
|
397
|
-
}
|
|
398
|
-
const { agent, className, instance: instanceString } = instanceData
|
|
366
|
+
async delete (instance, options = {}) {
|
|
367
|
+
const { agent, className } = await this._getInstanceData(instance, options)
|
|
399
368
|
const json = {
|
|
400
369
|
c: className,
|
|
401
370
|
f: '__delete__',
|
|
371
|
+
a: [instance],
|
|
402
372
|
i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
|
|
403
373
|
s: `${this._domain}/${os.hostname()}/${this._instance}`,
|
|
404
|
-
a: [instanceString],
|
|
405
374
|
v: VRPC_PROTOCOL_VERSION
|
|
406
375
|
}
|
|
407
376
|
const topic = `${this._domain}/${agent}/${className}/__static__/__delete__`
|
|
408
377
|
this._mqttPublish(topic, JSON.stringify(json))
|
|
409
378
|
if (this._proxies[instance]) delete this._proxies[instance]
|
|
410
|
-
return this._handleAgentAnswer(json)
|
|
379
|
+
return this._handleAgentAnswer(json, agent)
|
|
411
380
|
}
|
|
412
381
|
|
|
413
382
|
/**
|
|
@@ -426,20 +395,25 @@ class VrpcClient extends EventEmitter {
|
|
|
426
395
|
args = [],
|
|
427
396
|
agent = this._agent
|
|
428
397
|
} = {}) {
|
|
429
|
-
const wrapped = this._wrapArguments(
|
|
398
|
+
const wrapped = this._wrapArguments({
|
|
399
|
+
agent,
|
|
400
|
+
className,
|
|
401
|
+
functionName,
|
|
402
|
+
args
|
|
403
|
+
})
|
|
430
404
|
if (!wrapped) return // Skipping remote call -> handled locally
|
|
431
405
|
const json = {
|
|
432
406
|
c: className,
|
|
433
407
|
f: functionName,
|
|
408
|
+
a: wrapped,
|
|
434
409
|
i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
|
|
435
410
|
s: this._vrpcClientId,
|
|
436
|
-
a: wrapped,
|
|
437
411
|
v: VRPC_PROTOCOL_VERSION
|
|
438
412
|
}
|
|
439
413
|
const topic = `${this._domain}/${agent}/${className}/__static__/${functionName}`
|
|
440
414
|
await this._waitUntilClassIsOnline(agent, className)
|
|
441
415
|
this._mqttPublish(topic, JSON.stringify(json))
|
|
442
|
-
return this._handleAgentAnswer(json)
|
|
416
|
+
return this._handleAgentAnswer(json, agent)
|
|
443
417
|
}
|
|
444
418
|
|
|
445
419
|
/**
|
|
@@ -471,41 +445,43 @@ class VrpcClient extends EventEmitter {
|
|
|
471
445
|
const json = {
|
|
472
446
|
c: className,
|
|
473
447
|
f: functionName,
|
|
448
|
+
a: [],
|
|
474
449
|
i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
|
|
475
450
|
s: this._vrpcClientId,
|
|
476
|
-
a: [],
|
|
477
451
|
v: VRPC_PROTOCOL_VERSION
|
|
478
452
|
}
|
|
479
453
|
if (agent === '*') {
|
|
480
454
|
const result = []
|
|
481
455
|
const agents = this.getAvailableAgents()
|
|
482
|
-
for (const
|
|
483
|
-
const topic = `${this._domain}/${
|
|
484
|
-
const wrapped = this._wrapArguments(
|
|
485
|
-
|
|
456
|
+
for (const agent of agents) {
|
|
457
|
+
const topic = `${this._domain}/${agent}/${className}/__static__/__callAll__`
|
|
458
|
+
const wrapped = this._wrapArguments({
|
|
459
|
+
agent,
|
|
460
|
+
className,
|
|
486
461
|
functionName,
|
|
487
|
-
|
|
488
|
-
)
|
|
462
|
+
args
|
|
463
|
+
})
|
|
489
464
|
if (!wrapped) continue // using cache
|
|
490
465
|
json.a = [functionName, ...wrapped]
|
|
491
|
-
await this._waitUntilClassIsOnline(
|
|
466
|
+
await this._waitUntilClassIsOnline(agent, className)
|
|
492
467
|
this._mqttPublish(topic, JSON.stringify(json))
|
|
493
|
-
const tmp = await this._handleAgentAnswer(json)
|
|
468
|
+
const tmp = await this._handleAgentAnswer(json, agent)
|
|
494
469
|
result.push(...tmp)
|
|
495
470
|
}
|
|
496
471
|
return result
|
|
497
472
|
}
|
|
498
473
|
const topic = `${this._domain}/${agent}/${className}/__static__/__callAll__`
|
|
499
|
-
const wrapped = this._wrapArguments(
|
|
500
|
-
|
|
474
|
+
const wrapped = this._wrapArguments({
|
|
475
|
+
agent,
|
|
476
|
+
className,
|
|
501
477
|
functionName,
|
|
502
|
-
|
|
503
|
-
)
|
|
478
|
+
args
|
|
479
|
+
})
|
|
504
480
|
if (!wrapped) return // using cache
|
|
505
481
|
json.a = [functionName, ...wrapped]
|
|
506
482
|
await this._waitUntilClassIsOnline(agent, className)
|
|
507
483
|
this._mqttPublish(topic, JSON.stringify(json))
|
|
508
|
-
return this._handleAgentAnswer(json)
|
|
484
|
+
return this._handleAgentAnswer(json, agent)
|
|
509
485
|
}
|
|
510
486
|
|
|
511
487
|
/**
|
|
@@ -535,19 +511,7 @@ class VrpcClient extends EventEmitter {
|
|
|
535
511
|
* @param {Boolean} [options.mustBeOnline=true] Only retrieve currently online agents
|
|
536
512
|
* @returns {Array} Array of agent names.
|
|
537
513
|
*/
|
|
538
|
-
getAvailableAgents (
|
|
539
|
-
/// DEPRECATION INFORMATION AND WORKAROUND - TO BE REMOVED IN 3.x
|
|
540
|
-
// `options = {}` was `domain = this._domain` before
|
|
541
|
-
if (typeof options === 'string') {
|
|
542
|
-
this._indicateDeprecation(
|
|
543
|
-
'VrpcClient.getAvailableAgents(): ' +
|
|
544
|
-
'Providing a domain here has no effect and will be an error in future.'
|
|
545
|
-
)
|
|
546
|
-
options = { mustBeOnline: true }
|
|
547
|
-
}
|
|
548
|
-
///
|
|
549
|
-
|
|
550
|
-
const { mustBeOnline = true } = options
|
|
514
|
+
getAvailableAgents ({ mustBeOnline = true } = {}) {
|
|
551
515
|
const agents = []
|
|
552
516
|
if (mustBeOnline) {
|
|
553
517
|
// loop agents
|
|
@@ -568,34 +532,7 @@ class VrpcClient extends EventEmitter {
|
|
|
568
532
|
* @param {Boolean} [options.mustBeOnline=true] Only retrieve currently online classes
|
|
569
533
|
* @returns {Array} Array of class names.
|
|
570
534
|
*/
|
|
571
|
-
getAvailableClasses (
|
|
572
|
-
options = { agent: this._agent, mustBeOnline: true },
|
|
573
|
-
domain
|
|
574
|
-
) {
|
|
575
|
-
/// DEPRECATION INFORMATION AND WORKAROUND - TO BE REMOVED IN 3.x
|
|
576
|
-
let agent = this._agent
|
|
577
|
-
let mustBeOnline = true
|
|
578
|
-
// `options = {}` was `agent = this._agent` before
|
|
579
|
-
if (typeof options === 'string') {
|
|
580
|
-
this._indicateDeprecation(
|
|
581
|
-
'VrpcClient.getAvailableClasses(): ' +
|
|
582
|
-
'setting the agent as string argument is deprecated.`' +
|
|
583
|
-
'Use `{ agent: <myAgent> }` instead.'
|
|
584
|
-
)
|
|
585
|
-
agent = options
|
|
586
|
-
} else {
|
|
587
|
-
agent = options.agent
|
|
588
|
-
mustBeOnline = options.mustBeOnline
|
|
589
|
-
}
|
|
590
|
-
// was `domain = this._domain` before
|
|
591
|
-
if (domain) {
|
|
592
|
-
this._indicateDeprecation(
|
|
593
|
-
'VrpcClient.getAvailableClasses(): ' +
|
|
594
|
-
'Providing a domain here has no effect and will be an error in future.'
|
|
595
|
-
)
|
|
596
|
-
}
|
|
597
|
-
///
|
|
598
|
-
|
|
535
|
+
getAvailableClasses ({ agent = this._agent, mustBeOnline = true } = {}) {
|
|
599
536
|
if (agent === '*') throw new Error('Agent must be specified')
|
|
600
537
|
if (!this._agents[agent]) return []
|
|
601
538
|
const { classes, status } = this._agents[agent]
|
|
@@ -604,43 +541,19 @@ class VrpcClient extends EventEmitter {
|
|
|
604
541
|
}
|
|
605
542
|
|
|
606
543
|
/**
|
|
607
|
-
* Retrieves all (
|
|
544
|
+
* Retrieves all (shared) instances on specific class and agent.
|
|
608
545
|
*
|
|
609
|
-
* @param {String} className Class name
|
|
610
546
|
* @param {Object} [options]
|
|
547
|
+
* @param {String} options.className Class name
|
|
611
548
|
* @param {String} [options.agent] Agent name. If not provided class default is used
|
|
612
549
|
* @param {Boolean} [options.mustBeOnline=true] Only retrieve currently online classes
|
|
613
550
|
* @returns {Array} Array of instance names
|
|
614
551
|
*/
|
|
615
|
-
getAvailableInstances (
|
|
552
|
+
getAvailableInstances ({
|
|
616
553
|
className,
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
) {
|
|
620
|
-
/// DEPRECATION INFORMATION AND WORKAROUND - TO BE REMOVED IN 3.x
|
|
621
|
-
let agent = this._agent
|
|
622
|
-
let mustBeOnline = true
|
|
623
|
-
// `options = {}` was `agent = this._agent` before
|
|
624
|
-
if (typeof options === 'string') {
|
|
625
|
-
this._indicateDeprecation(
|
|
626
|
-
'VrpcClient.getAvailableInstances(): ' +
|
|
627
|
-
'Setting the agent as string argument is deprecated.`' +
|
|
628
|
-
'Use `{ agent: <myAgent> }` instead.'
|
|
629
|
-
)
|
|
630
|
-
agent = options
|
|
631
|
-
} else {
|
|
632
|
-
agent = options.agent
|
|
633
|
-
mustBeOnline = options.mustBeOnline
|
|
634
|
-
}
|
|
635
|
-
// was `domain = this._domain` before
|
|
636
|
-
if (domain) {
|
|
637
|
-
this._indicateDeprecation(
|
|
638
|
-
'VrpcClient.getAvailableInstances(): ' +
|
|
639
|
-
'Providing a domain here has no effect and will be an error in future.'
|
|
640
|
-
)
|
|
641
|
-
}
|
|
642
|
-
///
|
|
643
|
-
|
|
554
|
+
agent = this._agent,
|
|
555
|
+
mustBeOnline = true
|
|
556
|
+
} = {}) {
|
|
644
557
|
if (agent === '*') throw new Error('Agent must be specified')
|
|
645
558
|
if (!this._agents[agent]) return []
|
|
646
559
|
const { classes, status } = this._agents[agent]
|
|
@@ -651,41 +564,17 @@ class VrpcClient extends EventEmitter {
|
|
|
651
564
|
/**
|
|
652
565
|
* Retrieves all member functions of specific class and agent.
|
|
653
566
|
*
|
|
654
|
-
* @param {String} className Class name
|
|
655
567
|
* @param {Object} [options]
|
|
568
|
+
* @param {String} options.className Class name
|
|
656
569
|
* @param {String} [options.agent] Agent name. If not provided class default is used
|
|
657
570
|
* @param {Boolean} [options.mustBeOnline=true] Only retrieve currently online classes
|
|
658
571
|
* @returns {Array} Array of member function names
|
|
659
572
|
*/
|
|
660
|
-
getAvailableMemberFunctions (
|
|
573
|
+
getAvailableMemberFunctions ({
|
|
661
574
|
className,
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
) {
|
|
665
|
-
/// DEPRECATION INFORMATION AND WORKAROUND - TO BE REMOVED IN 3.x
|
|
666
|
-
let agent = this._agent
|
|
667
|
-
let mustBeOnline = true
|
|
668
|
-
// `options = {}` was `agent = this._agent` before
|
|
669
|
-
if (typeof options === 'string') {
|
|
670
|
-
this._indicateDeprecation(
|
|
671
|
-
'VrpcClient.getAvailableMemberFunctions(): ' +
|
|
672
|
-
'Setting the agent as string argument is deprecated.`' +
|
|
673
|
-
'Use `{ agent: <myAgent> }` instead.'
|
|
674
|
-
)
|
|
675
|
-
agent = options
|
|
676
|
-
} else {
|
|
677
|
-
agent = options.agent
|
|
678
|
-
mustBeOnline = options.mustBeOnline
|
|
679
|
-
}
|
|
680
|
-
// was `domain = this._domain` before
|
|
681
|
-
if (domain) {
|
|
682
|
-
this._indicateDeprecation(
|
|
683
|
-
'VrpcClient.getAvailableMemberFunctions(): ' +
|
|
684
|
-
'Providing a domain here has no effect and will be an error in future.'
|
|
685
|
-
)
|
|
686
|
-
}
|
|
687
|
-
///
|
|
688
|
-
|
|
575
|
+
agent = this._agent,
|
|
576
|
+
mustBeOnline = true
|
|
577
|
+
} = {}) {
|
|
689
578
|
if (agent === '*') throw new Error('Agent must be specified')
|
|
690
579
|
if (!this._agents[agent]) return []
|
|
691
580
|
const { classes, status } = this._agents[agent]
|
|
@@ -698,41 +587,17 @@ class VrpcClient extends EventEmitter {
|
|
|
698
587
|
/**
|
|
699
588
|
* Retrieves all static functions of specific class and agent.
|
|
700
589
|
*
|
|
701
|
-
* @param {String} className Class name
|
|
702
590
|
* @param {Object} [options]
|
|
591
|
+
* @param {String} options.className Class name
|
|
703
592
|
* @param {String} [options.agent] Agent name. If not provided class default is used
|
|
704
593
|
* @param {Boolean} [options.mustBeOnline=true] Only retrieve currently online classes
|
|
705
594
|
* @returns {Array} Array of static function names
|
|
706
595
|
*/
|
|
707
|
-
getAvailableStaticFunctions (
|
|
596
|
+
getAvailableStaticFunctions ({
|
|
708
597
|
className,
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
) {
|
|
712
|
-
/// DEPRECATION INFORMATION AND WORKAROUND - TO BE REMOVED IN 3.x
|
|
713
|
-
let agent = this._agent
|
|
714
|
-
let mustBeOnline = true
|
|
715
|
-
// `options = {}` was `agent = this._agent` before
|
|
716
|
-
if (typeof options === 'string') {
|
|
717
|
-
this._indicateDeprecation(
|
|
718
|
-
'VrpcClient.getAvailableStaticFunctions(): ' +
|
|
719
|
-
'Setting the agent as string argument is deprecated.`' +
|
|
720
|
-
'Use `{ agent: <myAgent> }` instead.'
|
|
721
|
-
)
|
|
722
|
-
agent = options
|
|
723
|
-
} else {
|
|
724
|
-
agent = options.agent
|
|
725
|
-
mustBeOnline = options.mustBeOnline
|
|
726
|
-
}
|
|
727
|
-
// was `domain = this._domain` before
|
|
728
|
-
if (domain) {
|
|
729
|
-
this._indicateDeprecation(
|
|
730
|
-
'VrpcClient.getAvailableStaticFunctions(): ' +
|
|
731
|
-
'Providing a domain here has no effect and will be an error in future.'
|
|
732
|
-
)
|
|
733
|
-
}
|
|
734
|
-
///
|
|
735
|
-
|
|
598
|
+
agent = this._agent,
|
|
599
|
+
mustBeOnline = true
|
|
600
|
+
} = {}) {
|
|
736
601
|
if (agent === '*') throw new Error('Agent must be specified')
|
|
737
602
|
if (!this._agents[agent]) return []
|
|
738
603
|
const { classes, status } = this._agents[agent]
|
|
@@ -810,7 +675,7 @@ class VrpcClient extends EventEmitter {
|
|
|
810
675
|
.createHash('md5')
|
|
811
676
|
.update(clientInfo)
|
|
812
677
|
.digest('hex')
|
|
813
|
-
.
|
|
678
|
+
.substring(0, 12)
|
|
814
679
|
return `vc3${this._instance}${md5}` // 3 + 8 + 12 = 23 (max clientId)
|
|
815
680
|
}
|
|
816
681
|
|
|
@@ -939,34 +804,120 @@ class VrpcClient extends EventEmitter {
|
|
|
939
804
|
// Remove overloads
|
|
940
805
|
const uniqueFuncs = new Set(functions)
|
|
941
806
|
// Build proxy
|
|
942
|
-
uniqueFuncs.forEach(
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
807
|
+
uniqueFuncs.forEach(functionName => {
|
|
808
|
+
// as we are messing with Node.js' event system we have to intercept here
|
|
809
|
+
if (functionName === 'removeAllListeners') {
|
|
810
|
+
proxy[functionName] = async eventName => {
|
|
811
|
+
if (!eventName) {
|
|
812
|
+
throw new Error('VRPC does not support removing all listeners')
|
|
813
|
+
}
|
|
814
|
+
const topic = `${this._domain}/${agent}/${className}/${instance}:${eventName}`
|
|
815
|
+
const id = `__e__${topic}`
|
|
816
|
+
if (this._cachedSubscriptions[topic]) {
|
|
817
|
+
this._eventEmitter.removeAllListeners(id)
|
|
818
|
+
this._mqttUnsubscribe(topic)
|
|
819
|
+
delete this._cachedSubscriptions[topic]
|
|
820
|
+
}
|
|
821
|
+
try {
|
|
822
|
+
const json = {
|
|
823
|
+
c: instance,
|
|
824
|
+
f: functionName,
|
|
825
|
+
a: [eventName],
|
|
826
|
+
i: `${this._instance}-${this._invokeId++ %
|
|
827
|
+
Number.MAX_SAFE_INTEGER}`,
|
|
828
|
+
s: this._vrpcClientId,
|
|
829
|
+
v: VRPC_PROTOCOL_VERSION
|
|
830
|
+
}
|
|
831
|
+
this._mqttPublish(
|
|
832
|
+
`${targetTopic}/${functionName}`,
|
|
833
|
+
JSON.stringify(json)
|
|
834
|
+
)
|
|
835
|
+
return this._handleAgentAnswer(json, agent)
|
|
836
|
+
} catch (err) {
|
|
837
|
+
throw new Error(
|
|
838
|
+
`Could not remotely call "${functionName}" because: ${err.message}`
|
|
839
|
+
)
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
return
|
|
843
|
+
}
|
|
844
|
+
proxy[functionName] = async (...args) => {
|
|
845
|
+
const wrapped = this._wrapArguments({
|
|
846
|
+
agent,
|
|
847
|
+
className,
|
|
848
|
+
instance,
|
|
849
|
+
proxyId,
|
|
850
|
+
functionName,
|
|
851
|
+
args
|
|
852
|
+
})
|
|
853
|
+
if (!wrapped) return true // Skipping remote call -> handled locally
|
|
946
854
|
try {
|
|
947
855
|
const json = {
|
|
948
856
|
c: instance,
|
|
949
|
-
f:
|
|
950
|
-
i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
|
|
951
|
-
s: this._vrpcClientId,
|
|
857
|
+
f: functionName,
|
|
952
858
|
a: wrapped,
|
|
859
|
+
i: `${this._instance}-${this._invokeId++ %
|
|
860
|
+
Number.MAX_SAFE_INTEGER}`,
|
|
861
|
+
s: this._vrpcClientId,
|
|
953
862
|
v: VRPC_PROTOCOL_VERSION
|
|
954
863
|
}
|
|
955
|
-
this._mqttPublish(
|
|
956
|
-
|
|
864
|
+
this._mqttPublish(
|
|
865
|
+
`${targetTopic}/${functionName}`,
|
|
866
|
+
JSON.stringify(json)
|
|
867
|
+
)
|
|
868
|
+
return this._handleAgentAnswer(json, agent)
|
|
957
869
|
} catch (err) {
|
|
958
870
|
throw new Error(
|
|
959
|
-
`Could not remotely call "${
|
|
871
|
+
`Could not remotely call "${functionName}" because: ${err.message}`
|
|
960
872
|
)
|
|
961
873
|
}
|
|
962
874
|
}
|
|
963
875
|
})
|
|
876
|
+
if (!uniqueFuncs.has('vrpcOn') && !uniqueFuncs.has('vrpcOff')) {
|
|
877
|
+
proxy.vrpcOn = async (functionName, ...args) => {
|
|
878
|
+
if (!uniqueFuncs.has(functionName)) throw new Error('Bad magic')
|
|
879
|
+
const wrapped = this._wrapArguments({
|
|
880
|
+
agent,
|
|
881
|
+
className,
|
|
882
|
+
instance,
|
|
883
|
+
proxyId,
|
|
884
|
+
functionName: 'vrpcOn',
|
|
885
|
+
args: [functionName, ...args]
|
|
886
|
+
})
|
|
887
|
+
if (!wrapped) return // Skipping remote call -> handled locally
|
|
888
|
+
try {
|
|
889
|
+
const json = {
|
|
890
|
+
c: instance,
|
|
891
|
+
f: functionName,
|
|
892
|
+
a: wrapped.slice(1), // first argument was remote function name
|
|
893
|
+
i: `${this._instance}-${this._invokeId++ %
|
|
894
|
+
Number.MAX_SAFE_INTEGER}`,
|
|
895
|
+
s: this._vrpcClientId,
|
|
896
|
+
v: VRPC_PROTOCOL_VERSION
|
|
897
|
+
}
|
|
898
|
+
this._mqttPublish(
|
|
899
|
+
`${targetTopic}/${functionName}`,
|
|
900
|
+
JSON.stringify(json)
|
|
901
|
+
)
|
|
902
|
+
return this._handleAgentAnswer(json, agent)
|
|
903
|
+
} catch (err) {
|
|
904
|
+
throw new Error(
|
|
905
|
+
`Could not remotely call "${functionName}" because: ${err.message}`
|
|
906
|
+
)
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
proxy.vrpcOff = functionName => {
|
|
910
|
+
const topic = `${this._domain}/${agent}/${className}/${instance}-${functionName}`
|
|
911
|
+
const id = `__e__${topic}`
|
|
912
|
+
this._eventEmitter.removeAllListeners(id)
|
|
913
|
+
}
|
|
914
|
+
}
|
|
964
915
|
return proxy
|
|
965
916
|
}
|
|
966
917
|
|
|
967
|
-
async _handleAgentAnswer ({ i, c, f }) {
|
|
918
|
+
async _handleAgentAnswer ({ i, c, f }, agent) {
|
|
968
919
|
return new Promise((resolve, reject) => {
|
|
969
|
-
const msg = `Function call "${c}::${f}()" timed out (> ${this._timeout} ms)`
|
|
920
|
+
const msg = `Function call "${c}::${f}()" on agent "${agent}" timed out (> ${this._timeout} ms)`
|
|
970
921
|
const timer = setTimeout(() => {
|
|
971
922
|
this._eventEmitter.removeAllListeners(i)
|
|
972
923
|
reject(new Error(msg))
|
|
@@ -974,13 +925,14 @@ class VrpcClient extends EventEmitter {
|
|
|
974
925
|
this._eventEmitter.once(i, data => {
|
|
975
926
|
clearTimeout(timer)
|
|
976
927
|
if (data.e) {
|
|
977
|
-
reject(new Error(data.e))
|
|
928
|
+
reject(new Error(`[vrpc ${agent}-${c}-${f}]: ${data.e}`))
|
|
978
929
|
} else {
|
|
979
930
|
const ret = data.r
|
|
980
931
|
// Handle functions returning a promise
|
|
981
|
-
if (typeof ret === 'string' && ret.
|
|
932
|
+
if (typeof ret === 'string' && ret.substring(0, 5) === '__p__') {
|
|
982
933
|
this._eventEmitter.once(ret, promiseData => {
|
|
983
|
-
|
|
934
|
+
// TODO improve error message here, like above
|
|
935
|
+
if (promiseData.e) reject(new Error(`[vrpc ${agent}-${c}-${f}]: ${promiseData.e}`))
|
|
984
936
|
else resolve(promiseData.r)
|
|
985
937
|
})
|
|
986
938
|
} else {
|
|
@@ -1011,79 +963,128 @@ class VrpcClient extends EventEmitter {
|
|
|
1011
963
|
})
|
|
1012
964
|
}
|
|
1013
965
|
|
|
1014
|
-
_wrapArguments (
|
|
966
|
+
_wrapArguments ({ agent, className, instance, proxyId, functionName, args }) {
|
|
1015
967
|
const wrapped = []
|
|
1016
|
-
|
|
1017
|
-
args.forEach((x, i) => {
|
|
968
|
+
for (const [i, x] of args.entries()) {
|
|
1018
969
|
// Check whether provided argument is a function
|
|
1019
970
|
if (this._isFunction(x)) {
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
971
|
+
const callback = x
|
|
972
|
+
if (
|
|
973
|
+
functionName === 'vrpcOn' ||
|
|
974
|
+
(functionName === 'on' && i === 1 && typeof args[0] === 'string')
|
|
975
|
+
) {
|
|
976
|
+
// special case of an (remote-)event emitter registration
|
|
977
|
+
// we test three conditions:
|
|
978
|
+
// 1) functionName must be "on"
|
|
979
|
+
// 2) callback is second argument
|
|
980
|
+
// 3) first argument was string
|
|
981
|
+
const event = args[0]
|
|
982
|
+
const id = this._onRemoteEvent({
|
|
983
|
+
agent,
|
|
984
|
+
className,
|
|
985
|
+
instance,
|
|
986
|
+
event,
|
|
987
|
+
callback
|
|
988
|
+
})
|
|
989
|
+
// indicate that no network call is required
|
|
990
|
+
if (id === null) return null
|
|
1028
991
|
wrapped.push(id)
|
|
992
|
+
continue
|
|
1029
993
|
} else if (
|
|
1030
994
|
(functionName === 'off' || functionName === 'removeListener') &&
|
|
1031
995
|
i === 1 &&
|
|
1032
996
|
typeof args[0] === 'string'
|
|
1033
997
|
) {
|
|
1034
|
-
const
|
|
1035
|
-
|
|
998
|
+
const event = args[0]
|
|
999
|
+
const id = this._offRemoteEvent({
|
|
1000
|
+
agent,
|
|
1001
|
+
className,
|
|
1002
|
+
instance,
|
|
1003
|
+
event,
|
|
1004
|
+
callback
|
|
1005
|
+
})
|
|
1006
|
+
// indicate that no network call is required
|
|
1007
|
+
if (id === null) return null
|
|
1036
1008
|
wrapped.push(id)
|
|
1037
|
-
|
|
1009
|
+
continue
|
|
1038
1010
|
} else {
|
|
1011
|
+
// Regular function callback (can be static or member function)
|
|
1012
|
+
const remoteId = proxyId || `${agent}-${className}`
|
|
1039
1013
|
const id = `__f__${remoteId}-${functionName}-${i}-${this._invokeId++ %
|
|
1040
1014
|
Number.MAX_SAFE_INTEGER}`
|
|
1041
1015
|
wrapped.push(id)
|
|
1042
|
-
this._eventEmitter.once(id, ({ a }) =>
|
|
1043
|
-
|
|
1044
|
-
})
|
|
1016
|
+
this._eventEmitter.once(id, ({ a }) => x.apply(null, a))
|
|
1017
|
+
continue
|
|
1045
1018
|
}
|
|
1046
|
-
}
|
|
1019
|
+
}
|
|
1020
|
+
// special case of an EventEmitter provided as argument
|
|
1021
|
+
if (this._isEmitter(x)) {
|
|
1047
1022
|
const { emitter, event } = x
|
|
1048
|
-
const id =
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1023
|
+
const id = this._onRemoteEvent({
|
|
1024
|
+
agent,
|
|
1025
|
+
className,
|
|
1026
|
+
instance,
|
|
1027
|
+
event,
|
|
1028
|
+
callback: (...args) => emitter.emit(event, ...args)
|
|
1052
1029
|
})
|
|
1053
|
-
|
|
1054
|
-
|
|
1030
|
+
// indicate that no network call is required
|
|
1031
|
+
if (id === null) return null
|
|
1032
|
+
wrapped.push(id)
|
|
1033
|
+
continue
|
|
1055
1034
|
}
|
|
1056
|
-
|
|
1057
|
-
|
|
1035
|
+
// default behavior is to not touch
|
|
1036
|
+
wrapped.push(x)
|
|
1037
|
+
}
|
|
1058
1038
|
return wrapped
|
|
1059
1039
|
}
|
|
1060
1040
|
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
this.
|
|
1065
|
-
|
|
1041
|
+
_onRemoteEvent ({ agent, className, instance, event, callback }) {
|
|
1042
|
+
// the topic on which any remote event will be published to
|
|
1043
|
+
const topic = instance
|
|
1044
|
+
? `${this._domain}/${agent}/${className}/${instance}:${event}`
|
|
1045
|
+
: `${this._domain}/${agent}/${className}/${event}`
|
|
1046
|
+
// prepare a special id for the agent to know that events should be
|
|
1047
|
+
// published to the provided topic after the prefix
|
|
1048
|
+
const id = `__e__${topic}`
|
|
1049
|
+
// call our proxy callback when remote events were received
|
|
1050
|
+
const handler = ({ a }) => callback.apply(null, a)
|
|
1051
|
+
this._eventEmitter.on(id, handler)
|
|
1052
|
+
if (!this._cachedSubscriptions[topic]) {
|
|
1053
|
+
// when not yet subscribed to this topic do it now
|
|
1054
|
+
this._mqttSubscribe(topic)
|
|
1055
|
+
this._cachedSubscriptions[topic] = [{ callback, handler }]
|
|
1056
|
+
return id
|
|
1066
1057
|
}
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
emitter.emit(id, ...a)
|
|
1072
|
-
})
|
|
1073
|
-
return id
|
|
1058
|
+
// otherwise just add the new callback-handler pair
|
|
1059
|
+
this._cachedSubscriptions[topic].push({ callback, handler })
|
|
1060
|
+
// indicate that no network call is required
|
|
1061
|
+
return null
|
|
1074
1062
|
}
|
|
1075
1063
|
|
|
1076
|
-
|
|
1077
|
-
const
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1064
|
+
_offRemoteEvent ({ agent, className, instance, event, callback }) {
|
|
1065
|
+
const topic = instance
|
|
1066
|
+
? `${this._domain}/${agent}/${className}/${instance}:${event}`
|
|
1067
|
+
: `${this._domain}/${agent}/${className}/${event}`
|
|
1068
|
+
const id = `__e__${topic}`
|
|
1069
|
+
if (this._cachedSubscriptions[topic]) {
|
|
1070
|
+
const index = this._cachedSubscriptions[topic].findIndex(x => x.callback === callback)
|
|
1071
|
+
if (index === -1) {
|
|
1072
|
+
this._log.warn(`Failed removing event listener ${id}`)
|
|
1073
|
+
return null
|
|
1074
|
+
}
|
|
1075
|
+
const [{ handler }] = this._cachedSubscriptions[topic].splice(index, 1)
|
|
1076
|
+
this._eventEmitter.removeListener(id, handler)
|
|
1077
|
+
if (this._cachedSubscriptions[topic].length === 0) {
|
|
1078
|
+
this._mqttUnsubscribe(topic)
|
|
1079
|
+
delete this._cachedSubscriptions[topic]
|
|
1084
1080
|
return id
|
|
1085
1081
|
}
|
|
1082
|
+
// indicate that no network call is required
|
|
1083
|
+
return null
|
|
1084
|
+
} else {
|
|
1085
|
+
this._log.warn(`Can not unsubscribe from non-existing event: ${event}`)
|
|
1086
1086
|
}
|
|
1087
|
+
return id
|
|
1087
1088
|
}
|
|
1088
1089
|
|
|
1089
1090
|
_stripSignature (method) {
|
|
@@ -1111,7 +1112,7 @@ class VrpcClient extends EventEmitter {
|
|
|
1111
1112
|
)
|
|
1112
1113
|
}
|
|
1113
1114
|
|
|
1114
|
-
async _getInstanceData (instance, options
|
|
1115
|
+
async _getInstanceData (instance, options) {
|
|
1115
1116
|
const instanceData = this._getInstanceFromCache(instance, options)
|
|
1116
1117
|
if (!instanceData) {
|
|
1117
1118
|
if (options.noWait) {
|
|
@@ -1123,6 +1124,21 @@ class VrpcClient extends EventEmitter {
|
|
|
1123
1124
|
}
|
|
1124
1125
|
|
|
1125
1126
|
_getInstanceFromCache (instance, options = {}) {
|
|
1127
|
+
// when no agent is specified, but an explicit class default exists give
|
|
1128
|
+
// such instances priority in being found
|
|
1129
|
+
if (!options.agent && this._agent !== '*') {
|
|
1130
|
+
const { classes, status } = this._agents[this._agent]
|
|
1131
|
+
if (!classes || status === 'offline') {
|
|
1132
|
+
for (const className in classes) {
|
|
1133
|
+
if (options.className && className !== options.className) continue
|
|
1134
|
+
const { instances } = classes[className]
|
|
1135
|
+
if (!instances) continue
|
|
1136
|
+
if (instances.includes(instance)) {
|
|
1137
|
+
return { agent, className, instance }
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1126
1142
|
// loop agents
|
|
1127
1143
|
for (const agent in this._agents) {
|
|
1128
1144
|
if (options.agent && agent !== options.agent) continue
|
|
@@ -1175,7 +1191,7 @@ class VrpcClient extends EventEmitter {
|
|
|
1175
1191
|
* @param {String} info.domain - Domain name
|
|
1176
1192
|
* @param {String} info.agent - Agent name
|
|
1177
1193
|
* @param {String} info.className - Class name
|
|
1178
|
-
* @param {Array.<String>} info.instances - Array of
|
|
1194
|
+
* @param {Array.<String>} info.instances - Array of instances
|
|
1179
1195
|
* @param {Array.<String>} info.memberFunctions - Array of member functions
|
|
1180
1196
|
* @param {Array.<String>} info.staticFunctions - Array of static functions
|
|
1181
1197
|
* @param {MetaData} info.meta - Object associating further information to functions
|