vrpc 3.15.0 → 3.16.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 -7
- package/package.json +1 -1
- package/vrpc/VrpcAdapter.js +8 -4
- package/vrpc/VrpcBrowserAdapter.js +22 -5
- package/vrpc/VrpcClient.js +82 -11
package/package.json
CHANGED
package/vrpc/VrpcAdapter.js
CHANGED
|
@@ -1177,13 +1177,15 @@ class VrpcAdapter {
|
|
|
1177
1177
|
}
|
|
1178
1178
|
|
|
1179
1179
|
/**
|
|
1180
|
-
* Answers a promise twice: at once with `r: '__p__<f>-<n>'`, and
|
|
1181
|
-
* settled with a second answer under that id, `{ s, i, r }` or
|
|
1182
|
-
* `{ s, i, e }`.
|
|
1180
|
+
* Answers a promise twice: at once with `r: '__p__<f>-<nonce>-<n>'`, and
|
|
1181
|
+
* once it settled with a second answer under that id, `{ s, i, r }` or
|
|
1182
|
+
* `{ s, i, e }`. A client waits for the second answer under the bare id,
|
|
1183
|
+
* whichever agent sends it: the nonce keeps the ids of two agents apart,
|
|
1184
|
+
* whose counters may well stand at the same value.
|
|
1183
1185
|
*/
|
|
1184
1186
|
static _handlePromise (json, promise) {
|
|
1185
1187
|
const cid = VrpcAdapter._correlationId++ % Number.MAX_SAFE_INTEGER
|
|
1186
|
-
const id = `__p__${json.f}-${cid}`
|
|
1188
|
+
const id = `__p__${json.f}-${VrpcAdapter._promiseNonce}-${cid}`
|
|
1187
1189
|
const { s } = json
|
|
1188
1190
|
json.r = id
|
|
1189
1191
|
promise.then(
|
|
@@ -1391,6 +1393,8 @@ VrpcAdapter._functionRegistry = new Map()
|
|
|
1391
1393
|
VrpcAdapter._instances = new Map()
|
|
1392
1394
|
VrpcAdapter._emitter = new EventEmitter()
|
|
1393
1395
|
VrpcAdapter._correlationId = 0
|
|
1396
|
+
// tells this process's promise ids from those of every other agent
|
|
1397
|
+
VrpcAdapter._promiseNonce = nanoid(8)
|
|
1394
1398
|
// the event registrations of every context and their subscriptions
|
|
1395
1399
|
VrpcAdapter._registrations = new Registrations({
|
|
1396
1400
|
publish: message => VrpcAdapter._callback(message),
|
|
@@ -69,7 +69,9 @@ class VrpcAdapter {
|
|
|
69
69
|
* @param {Boolean} [options.withNew=true] If true, class will be constructed
|
|
70
70
|
* using the `new` operator
|
|
71
71
|
* @param {Object} [options.schema=null] If provided is used to validate ctor
|
|
72
|
-
* parameters (only works if registered code reflects a single class)
|
|
72
|
+
* parameters (only works if registered code reflects a single class). The
|
|
73
|
+
* browser bundle (`browser/vrpc.js`) does not validate: it warns once and
|
|
74
|
+
* constructs with the arguments as given
|
|
73
75
|
*/
|
|
74
76
|
static registerClass (
|
|
75
77
|
Klass,
|
|
@@ -912,6 +914,17 @@ class VrpcAdapter {
|
|
|
912
914
|
}
|
|
913
915
|
|
|
914
916
|
static _validate (schema, params) {
|
|
917
|
+
// the browser bundle leaves ajv out - a fifth of its size, for agents in
|
|
918
|
+
// a page, which are rare: there a schema is not checked
|
|
919
|
+
if (typeof Ajv !== 'function') {
|
|
920
|
+
if (!VrpcAdapter._warnedNoSchemas) {
|
|
921
|
+
VrpcAdapter._warnedNoSchemas = true
|
|
922
|
+
console.warn(
|
|
923
|
+
'vrpc: schema validation is not available in the browser build, constructor arguments are not checked'
|
|
924
|
+
)
|
|
925
|
+
}
|
|
926
|
+
return
|
|
927
|
+
}
|
|
915
928
|
if (!VrpcAdapter._ajv) {
|
|
916
929
|
VrpcAdapter._ajv = new Ajv({ useDefaults: true })
|
|
917
930
|
}
|
|
@@ -983,13 +996,15 @@ class VrpcAdapter {
|
|
|
983
996
|
}
|
|
984
997
|
|
|
985
998
|
/**
|
|
986
|
-
* Answers a promise twice: at once with `r: '__p__<f>-<n>'`, and
|
|
987
|
-
* settled with a second answer under that id, `{ s, i, r }` or
|
|
988
|
-
* `{ s, i, e }`.
|
|
999
|
+
* Answers a promise twice: at once with `r: '__p__<f>-<nonce>-<n>'`, and
|
|
1000
|
+
* once it settled with a second answer under that id, `{ s, i, r }` or
|
|
1001
|
+
* `{ s, i, e }`. A client waits for the second answer under the bare id,
|
|
1002
|
+
* whichever agent sends it: the nonce keeps the ids of two agents apart,
|
|
1003
|
+
* whose counters may well stand at the same value.
|
|
989
1004
|
*/
|
|
990
1005
|
static _handlePromise (json, promise) {
|
|
991
1006
|
const cid = VrpcAdapter._correlationId++ % Number.MAX_SAFE_INTEGER
|
|
992
|
-
const id = `__p__${json.f}-${cid}`
|
|
1007
|
+
const id = `__p__${json.f}-${VrpcAdapter._promiseNonce}-${cid}`
|
|
993
1008
|
const { s } = json
|
|
994
1009
|
json.r = id
|
|
995
1010
|
promise.then(
|
|
@@ -1157,6 +1172,8 @@ VrpcAdapter._functionRegistry = new Map()
|
|
|
1157
1172
|
VrpcAdapter._instances = new Map()
|
|
1158
1173
|
VrpcAdapter._emitter = new EventEmitter()
|
|
1159
1174
|
VrpcAdapter._correlationId = 0
|
|
1175
|
+
// tells this process's promise ids from those of every other agent
|
|
1176
|
+
VrpcAdapter._promiseNonce = nanoid(8)
|
|
1160
1177
|
// the event registrations of every context and their subscriptions
|
|
1161
1178
|
VrpcAdapter._registrations = new Registrations({
|
|
1162
1179
|
publish: message => VrpcAdapter._callback(message),
|
package/vrpc/VrpcClient.js
CHANGED
|
@@ -180,6 +180,9 @@ class VrpcClient extends EventEmitter {
|
|
|
180
180
|
this._pendingWithdrawals = new Map()
|
|
181
181
|
// the client's own inbox is subscribed (answers can arrive)
|
|
182
182
|
this._inboxReady = false
|
|
183
|
+
// the answers this client waits for ({ agent, second, lose, forget }),
|
|
184
|
+
// lost when the path an answer would travel breaks (see _loseAnswers)
|
|
185
|
+
this._waiting = new Set()
|
|
183
186
|
// labels callbacks carry towards the agent's list of registrations
|
|
184
187
|
this._subscriberLabels = new WeakMap()
|
|
185
188
|
if (log === 'console') {
|
|
@@ -323,6 +326,12 @@ class VrpcClient extends EventEmitter {
|
|
|
323
326
|
this.emit('error', err)
|
|
324
327
|
})
|
|
325
328
|
|
|
329
|
+
// a clean session keeps nothing for this connection while it is gone:
|
|
330
|
+
// an answer published meanwhile never arrives
|
|
331
|
+
this._client.on('close', () => {
|
|
332
|
+
this._loseAnswers(() => true, 'this client lost its connection')
|
|
333
|
+
})
|
|
334
|
+
|
|
326
335
|
this._client.stream.on('error', err => {
|
|
327
336
|
this.emit('error', err)
|
|
328
337
|
})
|
|
@@ -377,7 +386,7 @@ class VrpcClient extends EventEmitter {
|
|
|
377
386
|
}
|
|
378
387
|
})
|
|
379
388
|
|
|
380
|
-
this._client.on('message', (topic, message) => {
|
|
389
|
+
this._client.on('message', (topic, message, packet) => {
|
|
381
390
|
if (message.length === 0) return
|
|
382
391
|
try {
|
|
383
392
|
const tokens = topic.split('/')
|
|
@@ -398,8 +407,21 @@ class VrpcClient extends EventEmitter {
|
|
|
398
407
|
// the agent's protocol version (undefined before 3)
|
|
399
408
|
this._agents[agent].v = v
|
|
400
409
|
if (status === 'offline') {
|
|
410
|
+
this._loseAnswers(
|
|
411
|
+
w => w.second && w.agent === agent,
|
|
412
|
+
'the agent went offline'
|
|
413
|
+
)
|
|
401
414
|
this._loseAgent(agent, v)
|
|
402
415
|
} else {
|
|
416
|
+
// an agent announces itself from a new connection only (a
|
|
417
|
+
// retained announcement is old news a subscription brings):
|
|
418
|
+
// the promises its former connection was to answer are gone
|
|
419
|
+
if (!packet.retain) {
|
|
420
|
+
this._loseAnswers(
|
|
421
|
+
w => w.second && w.agent === agent,
|
|
422
|
+
'the agent connected anew'
|
|
423
|
+
)
|
|
424
|
+
}
|
|
403
425
|
this._redeclareTo(agent)
|
|
404
426
|
this._flushWithdrawals(agent)
|
|
405
427
|
}
|
|
@@ -1400,6 +1422,7 @@ class VrpcClient extends EventEmitter {
|
|
|
1400
1422
|
v: VRPC_PROTOCOL_VERSION
|
|
1401
1423
|
})
|
|
1402
1424
|
)
|
|
1425
|
+
this._loseAnswers(() => true, 'the client ended')
|
|
1403
1426
|
this._eventEmitter.removeAllListeners()
|
|
1404
1427
|
this.removeAllListeners()
|
|
1405
1428
|
// the agents withdraw what this connection held: nothing to declare
|
|
@@ -1604,12 +1627,16 @@ class VrpcClient extends EventEmitter {
|
|
|
1604
1627
|
await this._waitUntilClassIsOnline(agent, className)
|
|
1605
1628
|
this._mqttPublish(topic, JSON.stringify(json))
|
|
1606
1629
|
return new Promise((resolve, reject) => {
|
|
1607
|
-
const
|
|
1630
|
+
const call = `Proxy creation for class "${className}" on agent "${agent}" and domain "${this._domain}"`
|
|
1608
1631
|
const timer = setTimeout(() => {
|
|
1609
|
-
|
|
1610
|
-
reject(new Error(
|
|
1632
|
+
forget()
|
|
1633
|
+
reject(new Error(`${call} timed out (> ${this._timeout} ms)`))
|
|
1611
1634
|
}, this._timeout)
|
|
1612
|
-
|
|
1635
|
+
const lose = reason => {
|
|
1636
|
+
clearTimeout(timer)
|
|
1637
|
+
reject(new Error(`${call} lost its answer: ${reason}`))
|
|
1638
|
+
}
|
|
1639
|
+
const forget = this._awaitAnswer(i, { agent, lose }, ({ e, r }) => {
|
|
1613
1640
|
clearTimeout(timer)
|
|
1614
1641
|
if (e) {
|
|
1615
1642
|
// the agent answers a throwing constructor with { message, cause }
|
|
@@ -1813,27 +1840,33 @@ class VrpcClient extends EventEmitter {
|
|
|
1813
1840
|
|
|
1814
1841
|
async _handleAgentAnswer ({ i, c, f, a }, agent) {
|
|
1815
1842
|
return new Promise((resolve, reject) => {
|
|
1816
|
-
const
|
|
1843
|
+
const call = `Function call "${c}::${f}()" on agent "${agent}"`
|
|
1817
1844
|
const timer = setTimeout(() => {
|
|
1818
|
-
|
|
1819
|
-
reject(new Error(
|
|
1845
|
+
forget()
|
|
1846
|
+
reject(new Error(`${call} timed out (> ${this._timeout} ms)`))
|
|
1820
1847
|
}, this._timeout)
|
|
1848
|
+
const lose = reason => {
|
|
1849
|
+
clearTimeout(timer)
|
|
1850
|
+
reject(new Error(`${call} lost its answer: ${reason}`))
|
|
1851
|
+
}
|
|
1821
1852
|
// a protocol-4 agent that answers a declaring call without an error
|
|
1822
1853
|
// holds every subscription the call carried
|
|
1823
1854
|
const confirm = () => {
|
|
1824
1855
|
const info = this._agents[agent]
|
|
1825
1856
|
if (info && info.v >= 4) this._confirmSubscriptions(a)
|
|
1826
1857
|
}
|
|
1827
|
-
this.
|
|
1858
|
+
const forget = this._awaitAnswer(i, { agent, lose }, data => {
|
|
1828
1859
|
clearTimeout(timer)
|
|
1829
1860
|
if (data.e) {
|
|
1830
1861
|
const { message, cause } = VrpcClient._prepareError(data.e)
|
|
1831
1862
|
reject(new Error(`[vrpc ${agent}-${c}-${f}]: ${message}`, { cause }))
|
|
1832
1863
|
} else {
|
|
1833
1864
|
const ret = data.r
|
|
1834
|
-
// Handle functions returning a promise
|
|
1865
|
+
// Handle functions returning a promise: the result follows when
|
|
1866
|
+
// it settles, however long that takes - unless the path it would
|
|
1867
|
+
// travel breaks first
|
|
1835
1868
|
if (typeof ret === 'string' && ret.substring(0, 5) === '__p__') {
|
|
1836
|
-
this.
|
|
1869
|
+
this._awaitAnswer(ret, { agent, lose, second: true }, promiseData => {
|
|
1837
1870
|
if (promiseData.e) {
|
|
1838
1871
|
const { message, cause } = VrpcClient._prepareError(
|
|
1839
1872
|
promiseData.e
|
|
@@ -1855,6 +1888,44 @@ class VrpcClient extends EventEmitter {
|
|
|
1855
1888
|
})
|
|
1856
1889
|
}
|
|
1857
1890
|
|
|
1891
|
+
/**
|
|
1892
|
+
* Waits for the answer with id `id`.
|
|
1893
|
+
*
|
|
1894
|
+
* @param {String} id
|
|
1895
|
+
* @param {Object} waiter
|
|
1896
|
+
* @param {String} waiter.agent the agent that answers
|
|
1897
|
+
* @param {Function} waiter.lose called with a reason when the answer can
|
|
1898
|
+
* no longer arrive
|
|
1899
|
+
* @param {Boolean} [waiter.second] a promise's result, the second answer
|
|
1900
|
+
* to its call
|
|
1901
|
+
* @param {Function} onAnswer called with the answer
|
|
1902
|
+
* @returns {Function} stops waiting, without a call to `lose`
|
|
1903
|
+
*/
|
|
1904
|
+
_awaitAnswer (id, { agent, lose, second = false }, onAnswer) {
|
|
1905
|
+
const waiter = { agent, lose, second }
|
|
1906
|
+
const listener = data => {
|
|
1907
|
+
this._waiting.delete(waiter)
|
|
1908
|
+
onAnswer(data)
|
|
1909
|
+
}
|
|
1910
|
+
waiter.forget = () => {
|
|
1911
|
+
this._waiting.delete(waiter)
|
|
1912
|
+
this._eventEmitter.removeListener(id, listener)
|
|
1913
|
+
}
|
|
1914
|
+
this._waiting.add(waiter)
|
|
1915
|
+
this._eventEmitter.once(id, listener)
|
|
1916
|
+
return waiter.forget
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
// the answers that can no longer arrive: their calls reject at once, and
|
|
1920
|
+
// a late answer finds no listener
|
|
1921
|
+
_loseAnswers (selects, reason) {
|
|
1922
|
+
for (const waiter of Array.from(this._waiting)) {
|
|
1923
|
+
if (!selects(waiter)) continue
|
|
1924
|
+
waiter.forget()
|
|
1925
|
+
waiter.lose(reason)
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1858
1929
|
static _prepareError (rpcError) {
|
|
1859
1930
|
if (typeof rpcError === 'object') {
|
|
1860
1931
|
return { message: rpcError.message, cause: rpcError.cause }
|