Skip to content

Commit

Permalink
adapter fixes and agent create
Browse files Browse the repository at this point in the history
  • Loading branch information
bheisen committed May 15, 2024
1 parent 4f9a2aa commit ed08693
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 57 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- possibility to configure the keepalive interval
- possibility to skip receiving meta data (which is the new default)
- support for the node 16 "cause" property on error objects
- create function for VrpcAgent that allows local instance creation

### Changed

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

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

## [3.1.1] - Aug 17 2022

Expand Down
2 changes: 1 addition & 1 deletion browser/vrpc.js

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ Agent capable of making existing code available to remote control by clients.
* _instance_
* [.serve()](#VrpcAgent+serve) ⇒ <code>Promise</code>
* [.end([obj], [unregister])](#VrpcAgent+end) ⇒ <code>Promise</code>
* [.create(options)](#VrpcAgent+create) ⇒ <code>Object</code>
* ["connect"](#VrpcAgent+event_connect)
* ["reconnect"](#VrpcAgent+event_reconnect)
* ["close"](#VrpcAgent+event_close)
Expand Down Expand Up @@ -315,6 +316,29 @@ Stops the agent
- [unregister] <code>Boolean</code> <code> = false</code> - If true, fully un-registers agent from broker


* * *

<a name="VrpcAgent+create"></a>

### vrpcAgent.create(options) ⇒ <code>Object</code>
Creates a new instance locally

NOTE: The instance must previously be registered by the local VrpcAdapter

**Kind**: instance method of [<code>VrpcAgent</code>](#VrpcAgent)
**Returns**: <code>Object</code> - The real instance (not a proxy!)
**Params**

- options <code>Object</code>
- .className <code>String</code> - Name of the class which should be
instantiated
- [.instance] <code>String</code> - Name of the created instance. If not
provided an id will be generated
- [.args] <code>Array</code> - Positional arguments for the constructor call
- [.isIsolated] <code>bool</code> <code> = false</code> - If true the created instance will
be visible only to the client who created it


* * *

<a name="VrpcAgent+event_connect"></a>
Expand Down
79 changes: 66 additions & 13 deletions tests/agent/vrpc-agent.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ const { VrpcAgent, VrpcClient, VrpcAdapter } = require('../../index')
const assert = require('assert')
const sinon = require('sinon')

class Foo {}
class Foo {
ping () {
return 'pong'
}
}

VrpcAdapter.register(Foo)

Expand All @@ -16,27 +20,32 @@ describe('vrpc-agent', () => {
describe('construction and connection', () => {
it('should not construct using bad parameters', async () => {
assert.throws(
() => new VrpcAgent({ broker: 'mqtt://doesNotWork:1883', domain: null }),
() =>
new VrpcAgent({ broker: 'mqtt://doesNotWork:1883', domain: null }),
{
message: 'The domain must be specified'
}
)
assert.throws(
() => new VrpcAgent({
broker: 'mqtt://doesNotWork:1883',
domain: '*'
}),
() =>
new VrpcAgent({
broker: 'mqtt://doesNotWork:1883',
domain: '*'
}),
{
message: 'The domain must NOT contain any of those characters: "+", "/", "#", "*"'
message:
'The domain must NOT contain any of those characters: "+", "/", "#", "*"'
}
)
assert.throws(
() => new VrpcAgent({
broker: 'mqtt://doesNotWork:1883',
domain: 'a/b'
}),
() =>
new VrpcAgent({
broker: 'mqtt://doesNotWork:1883',
domain: 'a/b'
}),
{
message: 'The domain must NOT contain any of those characters: "+", "/", "#", "*"'
message:
'The domain must NOT contain any of those characters: "+", "/", "#", "*"'
}
)
})
Expand Down Expand Up @@ -69,7 +78,10 @@ describe('vrpc-agent', () => {
agent.on('reconnect', reconnectSpy)
agent.on('reconnect', () => agent.end())
await agent.serve()
assert.strictEqual(errorSpy.args[0][0].message, 'Connection refused: Not authorized')
assert.strictEqual(
errorSpy.args[0][0].message,
'Connection refused: Not authorized'
)
assert(reconnectSpy.calledOnce)
})
context('when constructed using good parameters and broker', () => {
Expand Down Expand Up @@ -247,4 +259,45 @@ describe('vrpc-agent', () => {
assert(clientGoneSpy.calledWith(client2.getClientId()))
})
})
/***************************
* local instance creation *
***************************/
describe('creating instances locally', () => {
const instanceNewSpy = sinon.spy()
let agent
let client
before(async () => {
agent = new VrpcAgent({
broker: 'mqtt://broker:1883',
domain: 'test.vrpc',
agent: 'agent3',
username: 'Erwin',
password: '12345'
})
await agent.serve()
client = new VrpcClient({
broker: 'mqtt://broker:1883',
domain: 'test.vrpc',
username: 'Erwin',
password: '12345'
})
await client.connect()
})
after(async () => {
client.end()
agent.end()
})
it('should be possible to create an instance using the agent', async () => {
client.on('instanceNew', instanceNewSpy)
agent.create({
agent: 'agent3',
className: 'Foo',
instance: 'locallyCreatedFoo'
})
const proxy = await client.getInstance('locallyCreatedFoo')
const value = await proxy.ping()
assert.equal(value, 'pong')
assert(instanceNewSpy.called)
})
})
})
40 changes: 22 additions & 18 deletions vrpc/VrpcAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,10 @@ class VrpcAdapter {
this._registerClass(Klass, absJsdocPath, options)
} else {
const { jsdocPath } = options
const sJsdocPath =
jsdocPath && jsdocPath.endsWith('.js') ? jsdocPath : `${jsdocPath}.js`
if (jsdocPath) {
const absJsdocPath = path.resolve(caller(), '../', jsdocPath)
const absJsdocPath = path.resolve(caller(), '../', sJsdocPath)
this._registerClass(code, absJsdocPath, { ...options, jsdoc: true })
} else {
this._registerClass(code, null, options)
Expand Down Expand Up @@ -626,9 +628,10 @@ class VrpcAdapter {
Object.entries(VrpcAdapter._listeners).forEach(([ik, iv]) => {
Object.entries(iv).forEach(([ek, ev]) => {
const { clients, listener, event } = ev
if (clients.has(clientId)) {
clients.delete(clientId)
if (clients.size === 0) {
const index = clients.indexOf(clientId)
if (index !== -1) {
clients.splice(index, 1)
if (clients.length === 0) {
const instance = VrpcAdapter.getInstance(ik)
if (instance && instance.removeListener) {
instance.removeListener(event, listener)
Expand Down Expand Up @@ -680,9 +683,7 @@ class VrpcAdapter {
eventId: arg,
event: args[0]
})
if (
VrpcAdapter.getInstance(context).listeners(arg).includes(listener)
) {
if (!listener) {
return null // skip call as listener is already registered
}
unwrapped.push(listener)
Expand Down Expand Up @@ -738,28 +739,30 @@ class VrpcAdapter {
VrpcAdapter._listeners[instanceId] = {
[eventId]: {
event,
clients: new Set([clientId]),
clients: [clientId],
listener: VrpcAdapter._generateListener({
eventId,
instanceId,
isCallAll
})
}
}
} else if (!VrpcAdapter._listeners[instanceId][eventId]) {
return VrpcAdapter._listeners[instanceId][eventId].listener
}
if (!VrpcAdapter._listeners[instanceId][eventId]) {
VrpcAdapter._listeners[instanceId][eventId] = {
event,
clients: new Set([clientId]),
clients: [clientId],
listener: VrpcAdapter._generateListener({
eventId,
instanceId,
isCallAll
})
}
} else {
VrpcAdapter._listeners[instanceId][eventId].clients.add(clientId)
return VrpcAdapter._listeners[instanceId][eventId].listener
}
return VrpcAdapter._listeners[instanceId][eventId].listener
VrpcAdapter._listeners[instanceId][eventId].clients.push(clientId)
return null
}

static _generateListener ({ eventId, instanceId, isCallAll }) {
Expand All @@ -775,8 +778,9 @@ class VrpcAdapter {
VrpcAdapter._listeners[instanceId][eventId]
) {
const { listener, clients } = VrpcAdapter._listeners[instanceId][eventId]
clients.delete(clientId)
if (clients.size === 0) {
const index = clients.indexOf(clientId)
if (index !== -1) clients.splice(index, 1)
if (clients.length === 0) {
delete VrpcAdapter._listeners[instanceId][eventId]
if (Object.keys(VrpcAdapter._listeners[instanceId]).length === 0) {
delete VrpcAdapter._listeners[instanceId]
Expand All @@ -790,9 +794,9 @@ class VrpcAdapter {
const eventIds = VrpcAdapter._listeners[instanceId]
if (!eventIds) return
Object.entries(eventIds).forEach(([ek, ev]) => {
if (ev.event === event && ev.clients.has(clientId)) {
ev.clients.delete(clientId)
if (ev.clients.size === 0) {
if (ev.event === event && ev.clients.includes(clientId)) {
ev.clients = ev.clients.filter(x => x !== clientId)
if (ev.clients.length === 0) {
VrpcAdapter.getInstance(instanceId).removeListener(
ev.event,
ev.listener
Expand Down
30 changes: 30 additions & 0 deletions vrpc/VrpcAgent.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,36 @@ class VrpcAgent extends EventEmitter {
}
}

/**
* Creates a new instance locally
*
* NOTE: The instance must previously be registered by the local VrpcAdapter
*
* @param {Object} options
* @param {String} options.className Name of the class which should be
* instantiated
* @param {String} [options.instance] Name of the created instance. If not
* provided an id will be generated
* @param {Array} [options.args] Positional arguments for the constructor call
* @param {bool} [options.isIsolated=false] If true the created instance will
* be visible only to the client who created it
* @returns {Object} The real instance (not a proxy!)
*/
create ({
className,
instance = nanoid(8),
args = [],
isIsolated = false
}) {
const obj = VrpcAdapter.create({ className, instance, args, isIsolated })
if (!this._hasSharedInstance(instance)) {
this._subscribeToMethodsOfNewInstance(className, instance)
this._publishClassInfoMessage(className)
this._publishClassInfoConciseMessage(className)
}
return obj
}

static _generateAgentName () {
const { username } = os.userInfo()
const pathId = crypto
Expand Down
Loading

0 comments on commit ed08693

Please sign in to comment.