vrpc 3.1.0 → 3.2.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/browser/vrpc.js +1 -1
- package/browser/vrpc.js.LICENSE.txt +2 -0
- package/package.json +4 -3
- package/vrpc/VrpcAdapter.js +74 -46
- package/vrpc/VrpcAgent.js +64 -18
- package/vrpc/VrpcBrowserAdapter.js +885 -0
- package/vrpc/VrpcBrowserAgent.js +675 -0
- package/vrpc/VrpcClient.js +104 -43
- package/CHANGELOG.md +0 -808
package/vrpc/VrpcClient.js
CHANGED
|
@@ -65,11 +65,13 @@ class VrpcClient extends EventEmitter {
|
|
|
65
65
|
* @param {String} options.domain Sets the domain
|
|
66
66
|
* @param {String} [options.agent="*"] Sets default agent
|
|
67
67
|
* @param {String} [options.broker="mqtts://vrpc.io:8883"] Broker url in form: `<scheme>://<host>:<port>`
|
|
68
|
-
* @param {Number} [options.timeout=
|
|
68
|
+
* @param {Number} [options.timeout=12000] Maximum time in ms to wait for a RPC answer
|
|
69
69
|
* @param {Object} [options.log=console] Log object (must support debug, info, warn, and error level)
|
|
70
|
-
* @param {Boolean} [options.bestEffort=
|
|
70
|
+
* @param {Boolean} [options.bestEffort=true] If true, message will be sent with best effort, i.e. no caching if offline
|
|
71
71
|
* @param {String} [options.mqttClientId='<generated()>'] Explicitly sets the mqtt client id
|
|
72
72
|
* @param {String} [options.identity] Explicitly sets a vrpc client identity
|
|
73
|
+
* @param {String} [options.keepalive] Sets the MQTT keepalive interval (in seconds)
|
|
74
|
+
* @param {Boolean} [options.requiresSchema=false] If true, any available schema information is shipped
|
|
73
75
|
* @example
|
|
74
76
|
* const client = new VrpcClient({
|
|
75
77
|
* domain: 'vrpc',
|
|
@@ -83,11 +85,13 @@ class VrpcClient extends EventEmitter {
|
|
|
83
85
|
domain,
|
|
84
86
|
agent = '*',
|
|
85
87
|
broker = 'mqtts://vrpc.io:8883',
|
|
86
|
-
timeout =
|
|
88
|
+
timeout = 12 * 1000,
|
|
87
89
|
log = 'console',
|
|
88
|
-
bestEffort =
|
|
90
|
+
bestEffort = true,
|
|
89
91
|
mqttClientId = null,
|
|
90
|
-
identity = null
|
|
92
|
+
identity = null,
|
|
93
|
+
keepalive = 30,
|
|
94
|
+
requiresSchema = false
|
|
91
95
|
} = {}) {
|
|
92
96
|
super()
|
|
93
97
|
// domain sanity check
|
|
@@ -117,7 +121,9 @@ class VrpcClient extends EventEmitter {
|
|
|
117
121
|
this._broker = broker
|
|
118
122
|
this._timeout = timeout
|
|
119
123
|
this._identity = identity
|
|
124
|
+
this._keepalive = keepalive
|
|
120
125
|
this._qos = this._qos = bestEffort ? 0 : 1
|
|
126
|
+
this._requiresSchema = requiresSchema
|
|
121
127
|
|
|
122
128
|
this._instance = nanoid(8)
|
|
123
129
|
this._mqttClientId = mqttClientId || this._createMqttClientId()
|
|
@@ -170,7 +176,7 @@ class VrpcClient extends EventEmitter {
|
|
|
170
176
|
username,
|
|
171
177
|
password,
|
|
172
178
|
clean: true,
|
|
173
|
-
keepalive:
|
|
179
|
+
keepalive: this._keepalive,
|
|
174
180
|
clientId: this._mqttClientId,
|
|
175
181
|
rejectUnauthorized: false,
|
|
176
182
|
connectTimeout: this._timeout,
|
|
@@ -201,7 +207,11 @@ class VrpcClient extends EventEmitter {
|
|
|
201
207
|
// Agent info
|
|
202
208
|
this._mqttSubscribe(`${this._domain}/${agent}/__agentInfo__`)
|
|
203
209
|
// Class info
|
|
204
|
-
|
|
210
|
+
if (this._requiresSchema) {
|
|
211
|
+
this._mqttSubscribe(`${this._domain}/${agent}/+/__classInfo__`)
|
|
212
|
+
} else {
|
|
213
|
+
this._mqttSubscribe(`${this._domain}/${agent}/+/__classInfoConcise__`)
|
|
214
|
+
}
|
|
205
215
|
// RPC responses
|
|
206
216
|
this._mqttSubscribe(this._vrpcClientId)
|
|
207
217
|
this.emit('connect')
|
|
@@ -222,10 +232,16 @@ class VrpcClient extends EventEmitter {
|
|
|
222
232
|
this._agents[agent].status = status
|
|
223
233
|
this._agents[agent].hostname = hostname
|
|
224
234
|
this._agents[agent].version = version
|
|
235
|
+
if (status === 'offline') {
|
|
236
|
+
this._clearCachedSubscriptions({ lostAgent: agent })
|
|
237
|
+
}
|
|
225
238
|
this.emit('agent', { domain, agent, status, hostname, version })
|
|
226
239
|
// ClassInfo message
|
|
227
|
-
} else if (
|
|
228
|
-
|
|
240
|
+
} else if (
|
|
241
|
+
instance === '__classInfo__' ||
|
|
242
|
+
instance === '__classInfoConcise__'
|
|
243
|
+
) {
|
|
244
|
+
// Json properties: { className, instances, memberFunctions, staticFunctions }
|
|
229
245
|
const json = JSON.parse(message.toString())
|
|
230
246
|
this._createIfNotExist(agent)
|
|
231
247
|
const oldClassInfo = this._agents[agent].classes[klass]
|
|
@@ -234,15 +250,13 @@ class VrpcClient extends EventEmitter {
|
|
|
234
250
|
const removed = oldInstances.filter(x => !newInstances.includes(x))
|
|
235
251
|
const added = newInstances.filter(x => !oldInstances.includes(x))
|
|
236
252
|
this._agents[agent].classes[klass] = json
|
|
237
|
-
const {
|
|
238
|
-
|
|
239
|
-
instances,
|
|
240
|
-
memberFunctions,
|
|
241
|
-
staticFunctions,
|
|
242
|
-
meta
|
|
243
|
-
} = json
|
|
253
|
+
const { className, instances, memberFunctions, staticFunctions, meta } =
|
|
254
|
+
json
|
|
244
255
|
if (removed.length !== 0) {
|
|
245
256
|
this.emit('instanceGone', removed, { domain, agent, className })
|
|
257
|
+
removed.forEach(lostInstance =>
|
|
258
|
+
this._clearCachedSubscriptions({ lostInstance })
|
|
259
|
+
)
|
|
246
260
|
}
|
|
247
261
|
if (added.length !== 0) {
|
|
248
262
|
this.emit('instanceNew', added, { domain, agent, className })
|
|
@@ -254,7 +268,7 @@ class VrpcClient extends EventEmitter {
|
|
|
254
268
|
instances,
|
|
255
269
|
memberFunctions,
|
|
256
270
|
staticFunctions,
|
|
257
|
-
meta
|
|
271
|
+
meta: meta || {}
|
|
258
272
|
})
|
|
259
273
|
// RPC message
|
|
260
274
|
} else {
|
|
@@ -710,10 +724,12 @@ class VrpcClient extends EventEmitter {
|
|
|
710
724
|
`Could not publish MQTT message because: ${err.message}`
|
|
711
725
|
)
|
|
712
726
|
if (err.message === 'client disconnecting') {
|
|
713
|
-
//
|
|
727
|
+
// This most certainly relates to a bug in MQTT.js (#1284)
|
|
728
|
+
this._log.error(`Failed to publish, because: ${err.message}`)
|
|
729
|
+
this.emit('error', err)
|
|
714
730
|
this._log.info('Forcing reconnect now...')
|
|
715
731
|
this._client.disconnecting = false
|
|
716
|
-
this._client.
|
|
732
|
+
this._client.reconnect()
|
|
717
733
|
}
|
|
718
734
|
}
|
|
719
735
|
}
|
|
@@ -730,10 +746,12 @@ class VrpcClient extends EventEmitter {
|
|
|
730
746
|
`Could not subscribe to topic(s): '${topic}', because: ${err.message}`
|
|
731
747
|
)
|
|
732
748
|
if (err.message === 'client disconnecting') {
|
|
733
|
-
//
|
|
749
|
+
// This most certainly relates to a bug in MQTT.js (#1284)
|
|
750
|
+
this._log.error(`Failed to subscribe, because: ${err.message}`)
|
|
751
|
+
this.emit('error', err)
|
|
734
752
|
this._log.info('Forcing reconnect now...')
|
|
735
753
|
this._client.disconnecting = false
|
|
736
|
-
this._client.
|
|
754
|
+
this._client.reconnect()
|
|
737
755
|
}
|
|
738
756
|
} else {
|
|
739
757
|
const topicArray = Array.isArray(topic) ? topic : [topic]
|
|
@@ -880,8 +898,9 @@ class VrpcClient extends EventEmitter {
|
|
|
880
898
|
c: instance,
|
|
881
899
|
f: functionName,
|
|
882
900
|
a: [eventName],
|
|
883
|
-
i: `${this._instance}-${
|
|
884
|
-
Number.MAX_SAFE_INTEGER
|
|
901
|
+
i: `${this._instance}-${
|
|
902
|
+
this._invokeId++ % Number.MAX_SAFE_INTEGER
|
|
903
|
+
}`,
|
|
885
904
|
s: this._vrpcClientId,
|
|
886
905
|
v: VRPC_PROTOCOL_VERSION
|
|
887
906
|
}
|
|
@@ -913,8 +932,9 @@ class VrpcClient extends EventEmitter {
|
|
|
913
932
|
c: instance,
|
|
914
933
|
f: functionName,
|
|
915
934
|
a: wrapped,
|
|
916
|
-
i: `${this._instance}-${
|
|
917
|
-
Number.MAX_SAFE_INTEGER
|
|
935
|
+
i: `${this._instance}-${
|
|
936
|
+
this._invokeId++ % Number.MAX_SAFE_INTEGER
|
|
937
|
+
}`,
|
|
918
938
|
s: this._vrpcClientId,
|
|
919
939
|
v: VRPC_PROTOCOL_VERSION
|
|
920
940
|
}
|
|
@@ -947,8 +967,9 @@ class VrpcClient extends EventEmitter {
|
|
|
947
967
|
c: instance,
|
|
948
968
|
f: functionName,
|
|
949
969
|
a: wrapped.slice(1), // first argument was remote function name
|
|
950
|
-
i: `${this._instance}-${
|
|
951
|
-
Number.MAX_SAFE_INTEGER
|
|
970
|
+
i: `${this._instance}-${
|
|
971
|
+
this._invokeId++ % Number.MAX_SAFE_INTEGER
|
|
972
|
+
}`,
|
|
952
973
|
s: this._vrpcClientId,
|
|
953
974
|
v: VRPC_PROTOCOL_VERSION
|
|
954
975
|
}
|
|
@@ -982,16 +1003,23 @@ class VrpcClient extends EventEmitter {
|
|
|
982
1003
|
this._eventEmitter.once(i, data => {
|
|
983
1004
|
clearTimeout(timer)
|
|
984
1005
|
if (data.e) {
|
|
985
|
-
|
|
1006
|
+
const { message, cause } = VrpcClient._prepareError(data.e)
|
|
1007
|
+
reject(new Error(`[vrpc ${agent}-${c}-${f}]: ${message}`, { cause }))
|
|
986
1008
|
} else {
|
|
987
1009
|
const ret = data.r
|
|
988
1010
|
// Handle functions returning a promise
|
|
989
1011
|
if (typeof ret === 'string' && ret.substring(0, 5) === '__p__') {
|
|
990
1012
|
this._eventEmitter.once(ret, promiseData => {
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1013
|
+
if (promiseData.e) {
|
|
1014
|
+
const { message, cause } = VrpcClient._prepareError(
|
|
1015
|
+
promiseData.e
|
|
1016
|
+
)
|
|
1017
|
+
reject(
|
|
1018
|
+
new Error(`[vrpc ${agent}-${c}-${f}]: ${message}`, { cause })
|
|
1019
|
+
)
|
|
1020
|
+
} else {
|
|
1021
|
+
resolve(promiseData.r)
|
|
1022
|
+
}
|
|
995
1023
|
})
|
|
996
1024
|
} else {
|
|
997
1025
|
resolve(ret)
|
|
@@ -1001,17 +1029,26 @@ class VrpcClient extends EventEmitter {
|
|
|
1001
1029
|
})
|
|
1002
1030
|
}
|
|
1003
1031
|
|
|
1032
|
+
static _prepareError (rpcError) {
|
|
1033
|
+
if (typeof rpcError === 'object') {
|
|
1034
|
+
return { message: rpcError.message, cause: rpcError.cause }
|
|
1035
|
+
}
|
|
1036
|
+
return { message: rpcError, cause: undefined }
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1004
1039
|
async _waitForInstance (instance, options = {}) {
|
|
1005
1040
|
return new Promise((resolve, reject) => {
|
|
1006
|
-
const handler =
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
if (
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1041
|
+
const handler =
|
|
1042
|
+
timer =>
|
|
1043
|
+
(instances, { agent, className }) => {
|
|
1044
|
+
if (instances.includes(instance)) {
|
|
1045
|
+
if (options.agent && agent !== options.agent) return
|
|
1046
|
+
if (options.className && className !== options.className) return
|
|
1047
|
+
clearTimeout(timer)
|
|
1048
|
+
this.removeListener('instanceNew', handler)
|
|
1049
|
+
resolve({ agent, className, instance })
|
|
1050
|
+
}
|
|
1013
1051
|
}
|
|
1014
|
-
}
|
|
1015
1052
|
const timer = setTimeout(() => {
|
|
1016
1053
|
this.removeListener('instanceNew', handler(timer))
|
|
1017
1054
|
const msg = `Could not find instance: ${instance} (> ${this._timeout} ms)`
|
|
@@ -1068,8 +1105,9 @@ class VrpcClient extends EventEmitter {
|
|
|
1068
1105
|
} else {
|
|
1069
1106
|
// Regular function callback (can be static or member function)
|
|
1070
1107
|
const remoteId = proxyId || `${agent}-${className}`
|
|
1071
|
-
const id = `__f__${remoteId}-${functionName}-${i}-${
|
|
1072
|
-
Number.MAX_SAFE_INTEGER
|
|
1108
|
+
const id = `__f__${remoteId}-${functionName}-${i}-${
|
|
1109
|
+
this._invokeId++ % Number.MAX_SAFE_INTEGER
|
|
1110
|
+
}`
|
|
1073
1111
|
wrapped.push(id)
|
|
1074
1112
|
this._eventEmitter.once(id, ({ a }) => x.apply(null, a))
|
|
1075
1113
|
continue
|
|
@@ -1147,6 +1185,29 @@ class VrpcClient extends EventEmitter {
|
|
|
1147
1185
|
return id
|
|
1148
1186
|
}
|
|
1149
1187
|
|
|
1188
|
+
_clearCachedSubscriptions ({ lostAgent, lostInstance }) {
|
|
1189
|
+
const obsoleteTopics = Object.keys(this._cachedSubscriptions).filter(
|
|
1190
|
+
topic => {
|
|
1191
|
+
const [, agent, , instanceEvent] = topic.split('/')
|
|
1192
|
+
const [instance] = instanceEvent.split(':')
|
|
1193
|
+
if (agent === lostAgent || instance === lostInstance) {
|
|
1194
|
+
return true
|
|
1195
|
+
}
|
|
1196
|
+
return false
|
|
1197
|
+
}
|
|
1198
|
+
)
|
|
1199
|
+
obsoleteTopics.forEach(topic => {
|
|
1200
|
+
const id = `__e__${topic}`
|
|
1201
|
+
this._log.info(`Clearing subscriptions for obsolete topic: ${topic}`)
|
|
1202
|
+
const subscriptions = this._cachedSubscriptions[topic]
|
|
1203
|
+
subscriptions.forEach(({ handler }) => {
|
|
1204
|
+
this._eventEmitter.removeListener(id, handler)
|
|
1205
|
+
})
|
|
1206
|
+
this._mqttUnsubscribe(topic)
|
|
1207
|
+
delete this._cachedSubscriptions[topic]
|
|
1208
|
+
})
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1150
1211
|
_stripSignature (method) {
|
|
1151
1212
|
const pos = method.indexOf('-')
|
|
1152
1213
|
if (pos > 0) return method.substring(0, pos)
|
|
@@ -1164,7 +1225,7 @@ class VrpcClient extends EventEmitter {
|
|
|
1164
1225
|
|
|
1165
1226
|
_isEmitter (variable) {
|
|
1166
1227
|
return (
|
|
1167
|
-
typeof variable === 'object' &&
|
|
1228
|
+
variable && typeof variable === 'object' &&
|
|
1168
1229
|
variable.hasOwnProperty('emitter') &&
|
|
1169
1230
|
variable.hasOwnProperty('event') &&
|
|
1170
1231
|
typeof variable.emitter === 'object' &&
|