Skip to content

Commit ed08693

Browse files
committed
adapter fixes and agent create
1 parent 4f9a2aa commit ed08693

File tree

7 files changed

+173
-57
lines changed

7 files changed

+173
-57
lines changed

Diff for: CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1313
- possibility to configure the keepalive interval
1414
- possibility to skip receiving meta data (which is the new default)
1515
- support for the node 16 "cause" property on error objects
16+
- create function for VrpcAgent that allows local instance creation
1617

1718
### Changed
1819

@@ -32,6 +33,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
3233
### Fixed
3334

3435
- invalid log message triggered when JSON return values are circular
36+
- multiple emitter registration under the same clientId
37+
- issue when loading files that are not ending with ".js"
3538

3639
## [3.1.1] - Aug 17 2022
3740

Diff for: browser/vrpc.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: docs/api.md

+24
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ Agent capable of making existing code available to remote control by clients.
240240
* _instance_
241241
* [.serve()](#VrpcAgent+serve) ⇒ <code>Promise</code>
242242
* [.end([obj], [unregister])](#VrpcAgent+end) ⇒ <code>Promise</code>
243+
* [.create(options)](#VrpcAgent+create) ⇒ <code>Object</code>
243244
* ["connect"](#VrpcAgent+event_connect)
244245
* ["reconnect"](#VrpcAgent+event_reconnect)
245246
* ["close"](#VrpcAgent+event_close)
@@ -315,6 +316,29 @@ Stops the agent
315316
- [unregister] <code>Boolean</code> <code> = false</code> - If true, fully un-registers agent from broker
316317

317318

319+
* * *
320+
321+
<a name="VrpcAgent+create"></a>
322+
323+
### vrpcAgent.create(options) ⇒ <code>Object</code>
324+
Creates a new instance locally
325+
326+
NOTE: The instance must previously be registered by the local VrpcAdapter
327+
328+
**Kind**: instance method of [<code>VrpcAgent</code>](#VrpcAgent)
329+
**Returns**: <code>Object</code> - The real instance (not a proxy!)
330+
**Params**
331+
332+
- options <code>Object</code>
333+
- .className <code>String</code> - Name of the class which should be
334+
instantiated
335+
- [.instance] <code>String</code> - Name of the created instance. If not
336+
provided an id will be generated
337+
- [.args] <code>Array</code> - Positional arguments for the constructor call
338+
- [.isIsolated] <code>bool</code> <code> = false</code> - If true the created instance will
339+
be visible only to the client who created it
340+
341+
318342
* * *
319343

320344
<a name="VrpcAgent+event_connect"></a>

Diff for: tests/agent/vrpc-agent.spec.js

+66-13
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ const { VrpcAgent, VrpcClient, VrpcAdapter } = require('../../index')
55
const assert = require('assert')
66
const sinon = require('sinon')
77

8-
class Foo {}
8+
class Foo {
9+
ping () {
10+
return 'pong'
11+
}
12+
}
913

1014
VrpcAdapter.register(Foo)
1115

@@ -16,27 +20,32 @@ describe('vrpc-agent', () => {
1620
describe('construction and connection', () => {
1721
it('should not construct using bad parameters', async () => {
1822
assert.throws(
19-
() => new VrpcAgent({ broker: 'mqtt://doesNotWork:1883', domain: null }),
23+
() =>
24+
new VrpcAgent({ broker: 'mqtt://doesNotWork:1883', domain: null }),
2025
{
2126
message: 'The domain must be specified'
2227
}
2328
)
2429
assert.throws(
25-
() => new VrpcAgent({
26-
broker: 'mqtt://doesNotWork:1883',
27-
domain: '*'
28-
}),
30+
() =>
31+
new VrpcAgent({
32+
broker: 'mqtt://doesNotWork:1883',
33+
domain: '*'
34+
}),
2935
{
30-
message: 'The domain must NOT contain any of those characters: "+", "/", "#", "*"'
36+
message:
37+
'The domain must NOT contain any of those characters: "+", "/", "#", "*"'
3138
}
3239
)
3340
assert.throws(
34-
() => new VrpcAgent({
35-
broker: 'mqtt://doesNotWork:1883',
36-
domain: 'a/b'
37-
}),
41+
() =>
42+
new VrpcAgent({
43+
broker: 'mqtt://doesNotWork:1883',
44+
domain: 'a/b'
45+
}),
3846
{
39-
message: 'The domain must NOT contain any of those characters: "+", "/", "#", "*"'
47+
message:
48+
'The domain must NOT contain any of those characters: "+", "/", "#", "*"'
4049
}
4150
)
4251
})
@@ -69,7 +78,10 @@ describe('vrpc-agent', () => {
6978
agent.on('reconnect', reconnectSpy)
7079
agent.on('reconnect', () => agent.end())
7180
await agent.serve()
72-
assert.strictEqual(errorSpy.args[0][0].message, 'Connection refused: Not authorized')
81+
assert.strictEqual(
82+
errorSpy.args[0][0].message,
83+
'Connection refused: Not authorized'
84+
)
7385
assert(reconnectSpy.calledOnce)
7486
})
7587
context('when constructed using good parameters and broker', () => {
@@ -247,4 +259,45 @@ describe('vrpc-agent', () => {
247259
assert(clientGoneSpy.calledWith(client2.getClientId()))
248260
})
249261
})
262+
/***************************
263+
* local instance creation *
264+
***************************/
265+
describe('creating instances locally', () => {
266+
const instanceNewSpy = sinon.spy()
267+
let agent
268+
let client
269+
before(async () => {
270+
agent = new VrpcAgent({
271+
broker: 'mqtt://broker:1883',
272+
domain: 'test.vrpc',
273+
agent: 'agent3',
274+
username: 'Erwin',
275+
password: '12345'
276+
})
277+
await agent.serve()
278+
client = new VrpcClient({
279+
broker: 'mqtt://broker:1883',
280+
domain: 'test.vrpc',
281+
username: 'Erwin',
282+
password: '12345'
283+
})
284+
await client.connect()
285+
})
286+
after(async () => {
287+
client.end()
288+
agent.end()
289+
})
290+
it('should be possible to create an instance using the agent', async () => {
291+
client.on('instanceNew', instanceNewSpy)
292+
agent.create({
293+
agent: 'agent3',
294+
className: 'Foo',
295+
instance: 'locallyCreatedFoo'
296+
})
297+
const proxy = await client.getInstance('locallyCreatedFoo')
298+
const value = await proxy.ping()
299+
assert.equal(value, 'pong')
300+
assert(instanceNewSpy.called)
301+
})
302+
})
250303
})

Diff for: vrpc/VrpcAdapter.js

+22-18
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,10 @@ class VrpcAdapter {
111111
this._registerClass(Klass, absJsdocPath, options)
112112
} else {
113113
const { jsdocPath } = options
114+
const sJsdocPath =
115+
jsdocPath && jsdocPath.endsWith('.js') ? jsdocPath : `${jsdocPath}.js`
114116
if (jsdocPath) {
115-
const absJsdocPath = path.resolve(caller(), '../', jsdocPath)
117+
const absJsdocPath = path.resolve(caller(), '../', sJsdocPath)
116118
this._registerClass(code, absJsdocPath, { ...options, jsdoc: true })
117119
} else {
118120
this._registerClass(code, null, options)
@@ -626,9 +628,10 @@ class VrpcAdapter {
626628
Object.entries(VrpcAdapter._listeners).forEach(([ik, iv]) => {
627629
Object.entries(iv).forEach(([ek, ev]) => {
628630
const { clients, listener, event } = ev
629-
if (clients.has(clientId)) {
630-
clients.delete(clientId)
631-
if (clients.size === 0) {
631+
const index = clients.indexOf(clientId)
632+
if (index !== -1) {
633+
clients.splice(index, 1)
634+
if (clients.length === 0) {
632635
const instance = VrpcAdapter.getInstance(ik)
633636
if (instance && instance.removeListener) {
634637
instance.removeListener(event, listener)
@@ -680,9 +683,7 @@ class VrpcAdapter {
680683
eventId: arg,
681684
event: args[0]
682685
})
683-
if (
684-
VrpcAdapter.getInstance(context).listeners(arg).includes(listener)
685-
) {
686+
if (!listener) {
686687
return null // skip call as listener is already registered
687688
}
688689
unwrapped.push(listener)
@@ -738,28 +739,30 @@ class VrpcAdapter {
738739
VrpcAdapter._listeners[instanceId] = {
739740
[eventId]: {
740741
event,
741-
clients: new Set([clientId]),
742+
clients: [clientId],
742743
listener: VrpcAdapter._generateListener({
743744
eventId,
744745
instanceId,
745746
isCallAll
746747
})
747748
}
748749
}
749-
} else if (!VrpcAdapter._listeners[instanceId][eventId]) {
750+
return VrpcAdapter._listeners[instanceId][eventId].listener
751+
}
752+
if (!VrpcAdapter._listeners[instanceId][eventId]) {
750753
VrpcAdapter._listeners[instanceId][eventId] = {
751754
event,
752-
clients: new Set([clientId]),
755+
clients: [clientId],
753756
listener: VrpcAdapter._generateListener({
754757
eventId,
755758
instanceId,
756759
isCallAll
757760
})
758761
}
759-
} else {
760-
VrpcAdapter._listeners[instanceId][eventId].clients.add(clientId)
762+
return VrpcAdapter._listeners[instanceId][eventId].listener
761763
}
762-
return VrpcAdapter._listeners[instanceId][eventId].listener
764+
VrpcAdapter._listeners[instanceId][eventId].clients.push(clientId)
765+
return null
763766
}
764767

765768
static _generateListener ({ eventId, instanceId, isCallAll }) {
@@ -775,8 +778,9 @@ class VrpcAdapter {
775778
VrpcAdapter._listeners[instanceId][eventId]
776779
) {
777780
const { listener, clients } = VrpcAdapter._listeners[instanceId][eventId]
778-
clients.delete(clientId)
779-
if (clients.size === 0) {
781+
const index = clients.indexOf(clientId)
782+
if (index !== -1) clients.splice(index, 1)
783+
if (clients.length === 0) {
780784
delete VrpcAdapter._listeners[instanceId][eventId]
781785
if (Object.keys(VrpcAdapter._listeners[instanceId]).length === 0) {
782786
delete VrpcAdapter._listeners[instanceId]
@@ -790,9 +794,9 @@ class VrpcAdapter {
790794
const eventIds = VrpcAdapter._listeners[instanceId]
791795
if (!eventIds) return
792796
Object.entries(eventIds).forEach(([ek, ev]) => {
793-
if (ev.event === event && ev.clients.has(clientId)) {
794-
ev.clients.delete(clientId)
795-
if (ev.clients.size === 0) {
797+
if (ev.event === event && ev.clients.includes(clientId)) {
798+
ev.clients = ev.clients.filter(x => x !== clientId)
799+
if (ev.clients.length === 0) {
796800
VrpcAdapter.getInstance(instanceId).removeListener(
797801
ev.event,
798802
ev.listener

Diff for: vrpc/VrpcAgent.js

+30
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,36 @@ class VrpcAgent extends EventEmitter {
243243
}
244244
}
245245

246+
/**
247+
* Creates a new instance locally
248+
*
249+
* NOTE: The instance must previously be registered by the local VrpcAdapter
250+
*
251+
* @param {Object} options
252+
* @param {String} options.className Name of the class which should be
253+
* instantiated
254+
* @param {String} [options.instance] Name of the created instance. If not
255+
* provided an id will be generated
256+
* @param {Array} [options.args] Positional arguments for the constructor call
257+
* @param {bool} [options.isIsolated=false] If true the created instance will
258+
* be visible only to the client who created it
259+
* @returns {Object} The real instance (not a proxy!)
260+
*/
261+
create ({
262+
className,
263+
instance = nanoid(8),
264+
args = [],
265+
isIsolated = false
266+
}) {
267+
const obj = VrpcAdapter.create({ className, instance, args, isIsolated })
268+
if (!this._hasSharedInstance(instance)) {
269+
this._subscribeToMethodsOfNewInstance(className, instance)
270+
this._publishClassInfoMessage(className)
271+
this._publishClassInfoConciseMessage(className)
272+
}
273+
return obj
274+
}
275+
246276
static _generateAgentName () {
247277
const { username } = os.userInfo()
248278
const pathId = crypto

0 commit comments

Comments
 (0)