vrpc 3.4.0 → 3.5.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 +7 -1
- package/browser/vrpc.js.LICENSE.txt +1 -14
- package/package.json +15 -15
- package/vrpc/VrpcAdapter.js +5 -11
- package/vrpc/VrpcAgent.js +26 -16
- package/vrpc/VrpcBrowserAdapter.js +0 -5
- package/vrpc/VrpcBrowserAgent.js +23 -10
- package/vrpc/VrpcClient.js +206 -82
- package/vrpc/vrpc-agent-js +1 -1
package/vrpc/VrpcClient.js
CHANGED
|
@@ -38,7 +38,6 @@ SOFTWARE.
|
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
40
|
const os = require('os')
|
|
41
|
-
const crypto = require('crypto')
|
|
42
41
|
const { nanoid } = require('nanoid')
|
|
43
42
|
const mqtt = require('mqtt')
|
|
44
43
|
const EventEmitter = require('events')
|
|
@@ -64,7 +63,7 @@ class VrpcClient extends EventEmitter {
|
|
|
64
63
|
* @param {String} [options.token] Access token
|
|
65
64
|
* @param {String} options.domain Sets the domain
|
|
66
65
|
* @param {String} [options.agent="*"] Sets default agent
|
|
67
|
-
* @param {String} [options.broker="mqtts://
|
|
66
|
+
* @param {String} [options.broker="mqtts://broker.hivemq.com:8883"] Broker url in form: `<scheme>://<host>:<port>`
|
|
68
67
|
* @param {Number} [options.timeout=12000] Maximum time in ms to wait for a RPC answer
|
|
69
68
|
* @param {Object} [options.log=console] Log object (must support debug, info, warn, and error level)
|
|
70
69
|
* @param {Boolean} [options.bestEffort=true] If true, message will be sent with best effort, i.e. no caching if offline
|
|
@@ -84,7 +83,7 @@ class VrpcClient extends EventEmitter {
|
|
|
84
83
|
username,
|
|
85
84
|
domain,
|
|
86
85
|
agent = '*',
|
|
87
|
-
broker = 'mqtts://
|
|
86
|
+
broker = 'mqtts://broker.hivemq.com:8883',
|
|
88
87
|
timeout = 12 * 1000,
|
|
89
88
|
log = 'console',
|
|
90
89
|
bestEffort = true,
|
|
@@ -135,6 +134,7 @@ class VrpcClient extends EventEmitter {
|
|
|
135
134
|
this._client = null
|
|
136
135
|
this._cachedSubscriptions = {}
|
|
137
136
|
this._proxies = {}
|
|
137
|
+
this._callbackIds = new WeakMap()
|
|
138
138
|
if (log === 'console') {
|
|
139
139
|
this._log = console
|
|
140
140
|
this._log.debug = () => {}
|
|
@@ -189,6 +189,19 @@ class VrpcClient extends EventEmitter {
|
|
|
189
189
|
if (password === undefined) delete options.password
|
|
190
190
|
this._client = mqtt.connect(this._broker, options)
|
|
191
191
|
|
|
192
|
+
// Persistent lifecycle handlers
|
|
193
|
+
this._client.on('offline', () => {
|
|
194
|
+
this._log.warn('MQTT client went offline.')
|
|
195
|
+
// Clear the cache to ensure a fresh state upon reconnection
|
|
196
|
+
this._agents = {}
|
|
197
|
+
this.emit('offline')
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
this._client.on('reconnect', () => {
|
|
201
|
+
this._log.info('MQTT client is attempting to reconnect...')
|
|
202
|
+
this.emit('reconnect')
|
|
203
|
+
})
|
|
204
|
+
|
|
192
205
|
this._client.on('error', err => {
|
|
193
206
|
this.emit('error', err)
|
|
194
207
|
})
|
|
@@ -214,75 +227,105 @@ class VrpcClient extends EventEmitter {
|
|
|
214
227
|
}
|
|
215
228
|
// RPC responses
|
|
216
229
|
this._mqttSubscribe(this._vrpcClientId)
|
|
230
|
+
|
|
231
|
+
// Re-subscribe to all cached event topics
|
|
232
|
+
this._log.info('Restoring event subscriptions after reconnect...')
|
|
233
|
+
for (const topic of Object.keys(this._cachedSubscriptions)) {
|
|
234
|
+
this._mqttSubscribe(topic)
|
|
235
|
+
}
|
|
236
|
+
|
|
217
237
|
this.emit('connect')
|
|
218
238
|
})
|
|
219
239
|
|
|
220
240
|
this._client.on('message', (topic, message) => {
|
|
221
241
|
if (message.length === 0) return
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
// AgentInfo message
|
|
229
|
-
if (klass === '__agentInfo__') {
|
|
230
|
-
const { status, hostname, version } = JSON.parse(message.toString())
|
|
231
|
-
this._createIfNotExist(agent)
|
|
232
|
-
this._agents[agent].status = status
|
|
233
|
-
this._agents[agent].hostname = hostname
|
|
234
|
-
this._agents[agent].version = version
|
|
235
|
-
if (status === 'offline') {
|
|
236
|
-
this._clearCachedSubscriptions({ lostAgent: agent })
|
|
242
|
+
try {
|
|
243
|
+
const tokens = topic.split('/')
|
|
244
|
+
const [domain, agent, klass, instance] = tokens
|
|
245
|
+
if (domain !== this._domain) {
|
|
246
|
+
this._log.warn(`Received message from foreign domain: ${domain}`)
|
|
247
|
+
return
|
|
237
248
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
this.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
this.
|
|
249
|
+
// AgentInfo message
|
|
250
|
+
if (klass === '__agentInfo__') {
|
|
251
|
+
const { status, hostname, version } = JSON.parse(message.toString())
|
|
252
|
+
this._createIfNotExist(agent)
|
|
253
|
+
this._agents[agent].status = status
|
|
254
|
+
this._agents[agent].hostname = hostname
|
|
255
|
+
this._agents[agent].version = version
|
|
256
|
+
if (status === 'offline') {
|
|
257
|
+
this._clearCachedSubscriptions({ lostAgent: agent })
|
|
258
|
+
}
|
|
259
|
+
this.emit('agent', { domain, agent, status, hostname, version })
|
|
260
|
+
// ClassInfo message
|
|
261
|
+
} else if (
|
|
262
|
+
instance === '__classInfo__' ||
|
|
263
|
+
instance === '__classInfoConcise__'
|
|
264
|
+
) {
|
|
265
|
+
// Json properties: { className, instances, memberFunctions, staticFunctions }
|
|
266
|
+
const json = JSON.parse(message.toString())
|
|
267
|
+
this._createIfNotExist(agent)
|
|
268
|
+
const oldClassInfo = this._agents[agent].classes[klass]
|
|
269
|
+
const newInstances = json.instances || []
|
|
270
|
+
const oldInstances = oldClassInfo ? oldClassInfo.instances : []
|
|
271
|
+
const removed = oldInstances.filter(x => !newInstances.includes(x))
|
|
272
|
+
const added = newInstances.filter(x => !oldInstances.includes(x))
|
|
273
|
+
this._agents[agent].classes[klass] = json
|
|
274
|
+
const {
|
|
275
|
+
className,
|
|
276
|
+
instances,
|
|
277
|
+
memberFunctions,
|
|
278
|
+
staticFunctions,
|
|
279
|
+
meta
|
|
280
|
+
} = json
|
|
281
|
+
if (removed.length !== 0) {
|
|
282
|
+
this.emit('instanceGone', removed, { domain, agent, className })
|
|
283
|
+
removed.forEach(lostInstance =>
|
|
284
|
+
this._clearCachedSubscriptions({ lostInstance })
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
if (added.length !== 0) {
|
|
288
|
+
this.emit('instanceNew', added, { domain, agent, className })
|
|
289
|
+
}
|
|
290
|
+
this.emit('class', {
|
|
291
|
+
domain,
|
|
292
|
+
agent,
|
|
293
|
+
className,
|
|
294
|
+
instances,
|
|
295
|
+
memberFunctions,
|
|
296
|
+
staticFunctions,
|
|
297
|
+
meta: meta || {}
|
|
298
|
+
})
|
|
299
|
+
// RPC message
|
|
300
|
+
} else {
|
|
301
|
+
const { i, a, e, r } = JSON.parse(message.toString())
|
|
302
|
+
this._eventEmitter.emit(i, { a, e, r })
|
|
263
303
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
instances,
|
|
269
|
-
memberFunctions,
|
|
270
|
-
staticFunctions,
|
|
271
|
-
meta: meta || {}
|
|
272
|
-
})
|
|
273
|
-
// RPC message
|
|
274
|
-
} else {
|
|
275
|
-
const { i, a, e, r } = JSON.parse(message.toString())
|
|
276
|
-
this._eventEmitter.emit(i, { a, e, r })
|
|
304
|
+
} catch (err) {
|
|
305
|
+
this._log.error(
|
|
306
|
+
`Could not parse MQTT message on topic "${topic}": ${err.message}`
|
|
307
|
+
)
|
|
277
308
|
}
|
|
278
309
|
})
|
|
310
|
+
|
|
279
311
|
return new Promise((resolve, reject) => {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
312
|
+
const timeout = setTimeout(() => {
|
|
313
|
+
// This will run only if the 'connect' event isn't fired in time.
|
|
314
|
+
// We explicitly end the client to stop it from retrying in the background.
|
|
315
|
+
this._client.end(true, () => {
|
|
316
|
+
reject(
|
|
317
|
+
new Error(`Connection trial timed out (> ${this._timeout} ms)`)
|
|
318
|
+
)
|
|
319
|
+
})
|
|
320
|
+
}, this._timeout)
|
|
321
|
+
|
|
322
|
+
this._client.once('connect', () => {
|
|
323
|
+
// If we connect successfully, clear the timeout and resolve the promise.
|
|
324
|
+
clearTimeout(timeout)
|
|
325
|
+
resolve()
|
|
283
326
|
})
|
|
284
|
-
this._client.once('connect', resolve)
|
|
285
327
|
})
|
|
328
|
+
// --------------------------------------------------------------------------------
|
|
286
329
|
}
|
|
287
330
|
|
|
288
331
|
/**
|
|
@@ -343,6 +386,9 @@ class VrpcClient extends EventEmitter {
|
|
|
343
386
|
cacheProxy = false,
|
|
344
387
|
isIsolated = false
|
|
345
388
|
} = {}) {
|
|
389
|
+
if (!this._isConnected()) {
|
|
390
|
+
throw new Error('Client is not connected')
|
|
391
|
+
}
|
|
346
392
|
if (agent === '*') throw new Error('Agent must be specified')
|
|
347
393
|
const json = {
|
|
348
394
|
c: className,
|
|
@@ -372,6 +418,9 @@ class VrpcClient extends EventEmitter {
|
|
|
372
418
|
* @returns {Promise<Proxy>} Proxy object reflecting the remotely existing instance
|
|
373
419
|
*/
|
|
374
420
|
async getInstance (instance, options = {}) {
|
|
421
|
+
if (!this._isConnected()) {
|
|
422
|
+
throw new Error('Client is not connected')
|
|
423
|
+
}
|
|
375
424
|
if (this._proxies[instance]) return this._proxies[instance]
|
|
376
425
|
const { agent, className } = await this._getInstanceData(instance, options)
|
|
377
426
|
return this._createProxy(agent, className, instance)
|
|
@@ -390,6 +439,9 @@ class VrpcClient extends EventEmitter {
|
|
|
390
439
|
* @returns {Promise<Boolean>} true if successful, false otherwise
|
|
391
440
|
*/
|
|
392
441
|
async delete (instance, options = {}) {
|
|
442
|
+
if (!this._isConnected()) {
|
|
443
|
+
throw new Error('Client is not connected')
|
|
444
|
+
}
|
|
393
445
|
const { agent, className } = await this._getInstanceData(instance, options)
|
|
394
446
|
const json = {
|
|
395
447
|
c: className,
|
|
@@ -402,7 +454,25 @@ class VrpcClient extends EventEmitter {
|
|
|
402
454
|
const topic = `${this._domain}/${agent}/${className}/__static__/__delete__`
|
|
403
455
|
this._mqttPublish(topic, JSON.stringify(json))
|
|
404
456
|
if (this._proxies[instance]) delete this._proxies[instance]
|
|
405
|
-
|
|
457
|
+
const result = await this._handleAgentAnswer(json, agent)
|
|
458
|
+
// Now that deletion is confirmed, proactively update all local caches
|
|
459
|
+
if (result) {
|
|
460
|
+
// 1. Remove from the proxy cache
|
|
461
|
+
if (this._proxies[instance]) {
|
|
462
|
+
delete this._proxies[instance]
|
|
463
|
+
}
|
|
464
|
+
// 2. Remove from the main agent/instance cache
|
|
465
|
+
if (this._agents[agent] && this._agents[agent].classes) {
|
|
466
|
+
const classInfo = this._agents[agent].classes[className]
|
|
467
|
+
if (classInfo && classInfo.instances) {
|
|
468
|
+
const index = classInfo.instances.indexOf(instance)
|
|
469
|
+
if (index > -1) {
|
|
470
|
+
classInfo.instances.splice(index, 1)
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return result
|
|
406
476
|
}
|
|
407
477
|
|
|
408
478
|
/**
|
|
@@ -421,6 +491,9 @@ class VrpcClient extends EventEmitter {
|
|
|
421
491
|
args = [],
|
|
422
492
|
agent = this._agent
|
|
423
493
|
} = {}) {
|
|
494
|
+
if (!this._isConnected()) {
|
|
495
|
+
throw new Error('Client is not connected')
|
|
496
|
+
}
|
|
424
497
|
const wrapped = this._wrapArguments({
|
|
425
498
|
agent,
|
|
426
499
|
className,
|
|
@@ -468,6 +541,9 @@ class VrpcClient extends EventEmitter {
|
|
|
468
541
|
args = [],
|
|
469
542
|
agent = this._agent
|
|
470
543
|
} = {}) {
|
|
544
|
+
if (!this._isConnected()) {
|
|
545
|
+
throw new Error('Client is not connected')
|
|
546
|
+
}
|
|
471
547
|
const json = {
|
|
472
548
|
c: className,
|
|
473
549
|
f: functionName,
|
|
@@ -644,10 +720,10 @@ class VrpcClient extends EventEmitter {
|
|
|
644
720
|
async reconnectWithToken (token, { agent = this._agent } = {}) {
|
|
645
721
|
this._token = token
|
|
646
722
|
this._agent = agent
|
|
647
|
-
this._client
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
723
|
+
if (this._client) {
|
|
724
|
+
await new Promise(resolve => this._client.end(false, {}, resolve))
|
|
725
|
+
}
|
|
726
|
+
return this.connect()
|
|
651
727
|
}
|
|
652
728
|
|
|
653
729
|
/**
|
|
@@ -678,11 +754,49 @@ class VrpcClient extends EventEmitter {
|
|
|
678
754
|
* @returns {Promise} Resolves when ended
|
|
679
755
|
*/
|
|
680
756
|
async end () {
|
|
757
|
+
if (!this._client) return
|
|
681
758
|
this._mqttPublish(
|
|
682
759
|
`${this._vrpcClientId}/__clientInfo__`,
|
|
683
760
|
JSON.stringify({ status: 'offline', v: VRPC_PROTOCOL_VERSION })
|
|
684
761
|
)
|
|
685
|
-
|
|
762
|
+
this._eventEmitter.removeAllListeners()
|
|
763
|
+
this.removeAllListeners()
|
|
764
|
+
const client = this._client
|
|
765
|
+
this._client = null // Prevent race conditions during shutdown
|
|
766
|
+
return new Promise(resolve => client.end(false, {}, resolve))
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// Private functions
|
|
770
|
+
|
|
771
|
+
static _createHash (str, length = 20) {
|
|
772
|
+
// Extended DJB2 hash with two accumulators
|
|
773
|
+
let hash1 = 5381
|
|
774
|
+
let hash2 = 52711 // different seed
|
|
775
|
+
|
|
776
|
+
for (let i = 0; i < str.length; i++) {
|
|
777
|
+
const char = str.charCodeAt(i)
|
|
778
|
+
hash1 = (hash1 * 33) ^ char
|
|
779
|
+
hash2 = (hash2 * 33) ^ char
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Combine into longer hex string
|
|
783
|
+
const token =
|
|
784
|
+
(hash1 >>> 0).toString(16).padStart(8, '0') +
|
|
785
|
+
(hash2 >>> 0).toString(16).padStart(8, '0')
|
|
786
|
+
|
|
787
|
+
return token.slice(0, length)
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
_isConnected () {
|
|
791
|
+
return this._client && this._client.connected
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
_getCallbackId (callback, { readOnly = false } = {}) {
|
|
795
|
+
if (!this._callbackIds.has(callback)) {
|
|
796
|
+
if (readOnly) return null
|
|
797
|
+
this._callbackIds.set(callback, nanoid(8))
|
|
798
|
+
}
|
|
799
|
+
return this._callbackIds.get(callback)
|
|
686
800
|
}
|
|
687
801
|
|
|
688
802
|
_generateUserName () {
|
|
@@ -700,12 +814,8 @@ class VrpcClient extends EventEmitter {
|
|
|
700
814
|
os.totalmem() +
|
|
701
815
|
os.type()
|
|
702
816
|
// console.log('ClientInfo:', clientInfo)
|
|
703
|
-
const
|
|
704
|
-
|
|
705
|
-
.update(clientInfo)
|
|
706
|
-
.digest('hex')
|
|
707
|
-
.substring(0, 12)
|
|
708
|
-
return `vc3${this._instance}${md5}` // 3 + 8 + 12 = 23 (max clientId)
|
|
817
|
+
const hash = VrpcClient._createHash(clientInfo, 12)
|
|
818
|
+
return `vc3${this._instance}${hash}` // 3 + 8 + 12 = 23 (max clientId)
|
|
709
819
|
}
|
|
710
820
|
|
|
711
821
|
_createVrpcClientId () {
|
|
@@ -845,7 +955,7 @@ class VrpcClient extends EventEmitter {
|
|
|
845
955
|
this.removeListener('class', checkClass)
|
|
846
956
|
reject(
|
|
847
957
|
new Error(
|
|
848
|
-
`
|
|
958
|
+
`Connection to class "${className}" on agent "${agent}" and domain "${this._domain}" timed out (> ${this._timeout} ms)`
|
|
849
959
|
)
|
|
850
960
|
)
|
|
851
961
|
}, this._timeout)
|
|
@@ -1129,16 +1239,24 @@ class VrpcClient extends EventEmitter {
|
|
|
1129
1239
|
}
|
|
1130
1240
|
|
|
1131
1241
|
_onRemoteEvent ({ agent, className, instance, event, callback }) {
|
|
1242
|
+
const callbackId = this._getCallbackId(callback)
|
|
1243
|
+
const eventCallback = `${event}-${callbackId}`
|
|
1244
|
+
|
|
1132
1245
|
// the topic on which any remote event will be published to
|
|
1133
1246
|
const topic = instance
|
|
1134
|
-
? `${this._domain}/${agent}/${className}/${instance}:${
|
|
1135
|
-
: `${this._domain}/${agent}/${className}/${
|
|
1247
|
+
? `${this._domain}/${agent}/${className}/${instance}:${eventCallback}`
|
|
1248
|
+
: `${this._domain}/${agent}/${className}/${eventCallback}`
|
|
1249
|
+
|
|
1136
1250
|
// prepare a special id for the agent to know that events should be
|
|
1137
|
-
// published to the provided topic after the prefix
|
|
1251
|
+
// published to the provided topic after the prefix and before the suffix
|
|
1252
|
+
|
|
1138
1253
|
const id = `__e__${topic}`
|
|
1254
|
+
|
|
1139
1255
|
// call our proxy callback when remote events were received
|
|
1140
1256
|
const handler = ({ a }) => callback.apply(null, a)
|
|
1141
|
-
this._eventEmitter.
|
|
1257
|
+
if (this._eventEmitter.listenerCount(id) === 0) {
|
|
1258
|
+
this._eventEmitter.on(id, handler)
|
|
1259
|
+
}
|
|
1142
1260
|
if (!this._cachedSubscriptions[topic]) {
|
|
1143
1261
|
// when not yet subscribed to this topic do it now
|
|
1144
1262
|
this._mqttSubscribe(topic)
|
|
@@ -1151,10 +1269,16 @@ class VrpcClient extends EventEmitter {
|
|
|
1151
1269
|
}
|
|
1152
1270
|
|
|
1153
1271
|
_offRemoteEvent ({ agent, className, instance, event, callback }) {
|
|
1272
|
+
const callbackId = this._getCallbackId(callback, {
|
|
1273
|
+
readOnly: true
|
|
1274
|
+
})
|
|
1275
|
+
const eventCallback = `${event}-${callbackId}`
|
|
1154
1276
|
const topic = instance
|
|
1155
|
-
? `${this._domain}/${agent}/${className}/${instance}:${
|
|
1156
|
-
: `${this._domain}/${agent}/${className}/${
|
|
1277
|
+
? `${this._domain}/${agent}/${className}/${instance}:${eventCallback}`
|
|
1278
|
+
: `${this._domain}/${agent}/${className}/${eventCallback}`
|
|
1279
|
+
|
|
1157
1280
|
const id = `__e__${topic}`
|
|
1281
|
+
|
|
1158
1282
|
if (this._cachedSubscriptions[topic]) {
|
|
1159
1283
|
const index = this._cachedSubscriptions[topic].findIndex(
|
|
1160
1284
|
x => x.callback === callback
|
|
@@ -1164,8 +1288,8 @@ class VrpcClient extends EventEmitter {
|
|
|
1164
1288
|
return null
|
|
1165
1289
|
}
|
|
1166
1290
|
const [{ handler }] = this._cachedSubscriptions[topic].splice(index, 1)
|
|
1167
|
-
this._eventEmitter.removeListener(id, handler)
|
|
1168
1291
|
if (this._cachedSubscriptions[topic].length === 0) {
|
|
1292
|
+
this._eventEmitter.removeListener(id, handler)
|
|
1169
1293
|
this._mqttUnsubscribe(topic)
|
|
1170
1294
|
delete this._cachedSubscriptions[topic]
|
|
1171
1295
|
return id
|
|
@@ -1241,14 +1365,14 @@ class VrpcClient extends EventEmitter {
|
|
|
1241
1365
|
// when no agent is specified, but an explicit class default exists give
|
|
1242
1366
|
// such instances priority in being found
|
|
1243
1367
|
if (!options.agent && this._agent !== '*') {
|
|
1244
|
-
const { classes, status } = this._agents[this._agent]
|
|
1368
|
+
const { classes, status } = this._agents[this._agent] || {}
|
|
1245
1369
|
if (!classes || status === 'offline') {
|
|
1246
1370
|
for (const className in classes) {
|
|
1247
1371
|
if (options.className && className !== options.className) continue
|
|
1248
1372
|
const { instances } = classes[className]
|
|
1249
1373
|
if (!instances) continue
|
|
1250
1374
|
if (instances.includes(instance)) {
|
|
1251
|
-
return { agent, className, instance }
|
|
1375
|
+
return { agent: this._agent, className, instance }
|
|
1252
1376
|
}
|
|
1253
1377
|
}
|
|
1254
1378
|
}
|