@nxtedition/deepstream.io-client-js 24.3.4 → 24.4.1
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/.husky/pre-commit +1 -3
- package/package.json +17 -16
- package/src/client.js +2 -4
- package/src/event/event-handler.js +1 -1
- package/src/message/connection.js +27 -32
- package/src/message/message-builder.js +6 -75
- package/src/message/message-parser.js +9 -19
- package/src/record/record-handler.js +45 -39
- package/src/record/record.js +12 -15
- package/src/rpc/rpc-handler.js +2 -2
- package/src/utils/multicast-listener.js +9 -15
- package/src/utils/timers.js +5 -4
- package/src/utils/unicast-listener.js +10 -14
- package/src/utils/utils.js +8 -0
package/.husky/pre-commit
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxtedition/deepstream.io-client-js",
|
|
3
|
-
"version": "24.
|
|
3
|
+
"version": "24.4.1",
|
|
4
4
|
"description": "the javascript client for deepstream.io",
|
|
5
5
|
"homepage": "http://deepstream.io",
|
|
6
6
|
"type": "module",
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
"ws": false
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
|
-
"_postinstall": "husky install",
|
|
22
21
|
"prepublishOnly": "pinst --disable",
|
|
23
|
-
"postpublish": "pinst --enable"
|
|
22
|
+
"postpublish": "pinst --enable",
|
|
23
|
+
"prepare": "husky"
|
|
24
24
|
},
|
|
25
25
|
"lint-staged": {
|
|
26
26
|
"*.{js,jsx,md,ts}": [
|
|
@@ -59,30 +59,31 @@
|
|
|
59
59
|
"/__tests__"
|
|
60
60
|
],
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@nxtedition/json-path": "^1.0.
|
|
63
|
-
"bufferutil": "^4.0.
|
|
62
|
+
"@nxtedition/json-path": "^1.0.8",
|
|
63
|
+
"bufferutil": "^4.0.8",
|
|
64
64
|
"component-emitter2": "^1.3.5",
|
|
65
65
|
"invariant": "^2.2.4",
|
|
66
66
|
"lodash.clonedeep": "^4.5.0",
|
|
67
|
-
"utf-8-validate": "^6.0.
|
|
67
|
+
"utf-8-validate": "^6.0.4",
|
|
68
68
|
"varint": "^6.0.0",
|
|
69
|
-
"ws": "^8.
|
|
70
|
-
"xuid": "^4.1.
|
|
69
|
+
"ws": "^8.18.0",
|
|
70
|
+
"xuid": "^4.1.3",
|
|
71
71
|
"xxhash-wasm": "^1.0.2"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
|
-
"eslint": "^8.
|
|
75
|
-
"eslint-config-prettier": "^
|
|
74
|
+
"eslint": "^8.0.0",
|
|
75
|
+
"eslint-config-prettier": "^9.1.0",
|
|
76
76
|
"eslint-config-standard": "^17.1.0",
|
|
77
|
-
"eslint-plugin-import": "^2.
|
|
78
|
-
"eslint-plugin-n": "^
|
|
77
|
+
"eslint-plugin-import": "^2.29.1",
|
|
78
|
+
"eslint-plugin-n": "^17.10.1",
|
|
79
79
|
"eslint-plugin-node": "^11.1.0",
|
|
80
|
-
"eslint-plugin-promise": "^
|
|
81
|
-
"husky": "^
|
|
82
|
-
"lint-staged": "^
|
|
80
|
+
"eslint-plugin-promise": "^7.0.0",
|
|
81
|
+
"husky": "^9.1.3",
|
|
82
|
+
"lint-staged": "^15.2.7",
|
|
83
83
|
"mitata": "^0.1.11",
|
|
84
84
|
"pinst": "^3.0.0",
|
|
85
|
-
"prettier": "^
|
|
85
|
+
"prettier": "^3.3.3",
|
|
86
|
+
"rxjs": "^7.8.1"
|
|
86
87
|
},
|
|
87
88
|
"peerDependencies": {
|
|
88
89
|
"rxjs": ">=6.x"
|
package/src/client.js
CHANGED
|
@@ -81,7 +81,7 @@ Client.prototype._$onMessage = function (message) {
|
|
|
81
81
|
this._$onError(
|
|
82
82
|
message.topic,
|
|
83
83
|
C.EVENT.MESSAGE_PARSE_ERROR,
|
|
84
|
-
`Received message for unknown topic ${message.topic}
|
|
84
|
+
`Received message for unknown topic ${message.topic}`,
|
|
85
85
|
)
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -127,12 +127,10 @@ Client.prototype._getOptions = function (options) {
|
|
|
127
127
|
return mergedOptions
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
function createDeepstream(url, options) {
|
|
130
|
+
export default function createDeepstream(url, options) {
|
|
131
131
|
return new Client(url, options)
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
Client.prototype.isSameOrNewer = utils.isSameOrNewer
|
|
135
135
|
Client.prototype.CONSTANTS = C
|
|
136
136
|
createDeepstream.CONSTANTS = C
|
|
137
|
-
|
|
138
|
-
export default createDeepstream
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as C from '../constants/constants.js'
|
|
2
2
|
import * as messageBuilder from '../message/message-builder.js'
|
|
3
|
-
import messageParser from '../message/message-parser.js'
|
|
3
|
+
import * as messageParser from '../message/message-parser.js'
|
|
4
4
|
import MulticastListener from '../utils/multicast-listener.js'
|
|
5
5
|
import UnicastListener from '../utils/unicast-listener.js'
|
|
6
6
|
import EventEmitter from 'component-emitter2'
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import * as utils from '../utils/utils.js'
|
|
2
|
-
import messageParser from './message-parser.js'
|
|
2
|
+
import * as messageParser from './message-parser.js'
|
|
3
3
|
import * as messageBuilder from './message-builder.js'
|
|
4
4
|
import * as C from '../constants/constants.js'
|
|
5
|
-
import xxhash from 'xxhash-wasm'
|
|
6
5
|
import FixedQueue from '../utils/fixed-queue.js'
|
|
7
6
|
import Emitter from 'component-emitter2'
|
|
8
7
|
|
|
9
|
-
const BrowserWebSocket = globalThis.WebSocket || globalThis.MozWebSocket
|
|
10
8
|
const NodeWebSocket = utils.isNode ? await import('ws').then((x) => x.default) : null
|
|
9
|
+
const BrowserWebSocket = globalThis.WebSocket || globalThis.MozWebSocket
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const Connection = function (client, url, options) {
|
|
11
|
+
export default function Connection(client, url, options) {
|
|
15
12
|
this._client = client
|
|
16
13
|
this._options = options
|
|
17
14
|
this._logger = options.logger
|
|
@@ -32,6 +29,7 @@ const Connection = function (client, url, options) {
|
|
|
32
29
|
this._recvQueue = new FixedQueue()
|
|
33
30
|
this._reconnectTimeout = null
|
|
34
31
|
this._reconnectionAttempt = 0
|
|
32
|
+
this._endpoint = null
|
|
35
33
|
|
|
36
34
|
this._processingRecv = false
|
|
37
35
|
this._recvMessages = this._recvMessages.bind(this)
|
|
@@ -40,8 +38,6 @@ const Connection = function (client, url, options) {
|
|
|
40
38
|
|
|
41
39
|
this._state = C.CONNECTION_STATE.CLOSED
|
|
42
40
|
|
|
43
|
-
this.hasher = HASHER
|
|
44
|
-
|
|
45
41
|
this._createEndpoint()
|
|
46
42
|
}
|
|
47
43
|
|
|
@@ -92,24 +88,19 @@ Connection.prototype.close = function () {
|
|
|
92
88
|
}
|
|
93
89
|
|
|
94
90
|
Connection.prototype._createEndpoint = function () {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
this._endpoint = new BrowserWebSocket(this._url)
|
|
101
|
-
this._endpoint.binaryType = 'arraybuffer'
|
|
102
|
-
}
|
|
91
|
+
this._endpoint = NodeWebSocket
|
|
92
|
+
? new NodeWebSocket(this._url, {
|
|
93
|
+
generateMask() {},
|
|
94
|
+
})
|
|
95
|
+
: new BrowserWebSocket(this._url)
|
|
103
96
|
this._corked = false
|
|
104
97
|
|
|
105
98
|
this._endpoint.onopen = this._onOpen.bind(this)
|
|
106
99
|
this._endpoint.onerror = this._onError.bind(this)
|
|
107
100
|
this._endpoint.onclose = this._onClose.bind(this)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
this._onMessage(typeof data === 'string' ? data : decoder.decode(data))
|
|
112
|
-
}
|
|
101
|
+
this._endpoint.onmessage = BrowserWebSocket
|
|
102
|
+
? ({ data }) => this._onMessage(typeof data === 'string' ? data : Buffer.from(data).toString())
|
|
103
|
+
: ({ data }) => this._onMessage(typeof data === 'string' ? data : data.toString())
|
|
113
104
|
}
|
|
114
105
|
|
|
115
106
|
Connection.prototype.send = function (message) {
|
|
@@ -121,7 +112,7 @@ Connection.prototype.send = function (message) {
|
|
|
121
112
|
C.TOPIC.CONNECTION,
|
|
122
113
|
C.EVENT.CONNECTION_ERROR,
|
|
123
114
|
err,
|
|
124
|
-
message.split(C.MESSAGE_PART_SEPERATOR).map((x) => x.slice(0, 256))
|
|
115
|
+
message.split(C.MESSAGE_PART_SEPERATOR).map((x) => x.slice(0, 256)),
|
|
125
116
|
)
|
|
126
117
|
return false
|
|
127
118
|
}
|
|
@@ -151,8 +142,8 @@ Connection.prototype.send = function (message) {
|
|
|
151
142
|
Connection.prototype._submit = function (message) {
|
|
152
143
|
const { maxPacketSize } = this._options
|
|
153
144
|
|
|
154
|
-
if (message.
|
|
155
|
-
const err = new Error(`Packet to big: ${message.
|
|
145
|
+
if (message.length > maxPacketSize) {
|
|
146
|
+
const err = new Error(`Packet to big: ${message.length} > ${maxPacketSize}`)
|
|
156
147
|
this._client._$onError(C.TOPIC.CONNECTION, C.EVENT.CONNECTION_ERROR, err)
|
|
157
148
|
return false
|
|
158
149
|
} else if (this._endpoint.readyState === this._endpoint.OPEN) {
|
|
@@ -170,7 +161,7 @@ Connection.prototype._sendAuthParams = function () {
|
|
|
170
161
|
this._setState(C.CONNECTION_STATE.AUTHENTICATING)
|
|
171
162
|
const authMessage = messageBuilder.getMsg(C.TOPIC.AUTH, C.ACTIONS.REQUEST, [
|
|
172
163
|
this._authParams,
|
|
173
|
-
'24.
|
|
164
|
+
'24.4.0',
|
|
174
165
|
utils.isNode
|
|
175
166
|
? `Node/${process.version}`
|
|
176
167
|
: globalThis.navigator && globalThis.navigator.userAgent,
|
|
@@ -276,7 +267,7 @@ Connection.prototype._handleConnectionResponse = function (message) {
|
|
|
276
267
|
} else if (message.action === C.ACTIONS.CHALLENGE) {
|
|
277
268
|
this._setState(C.CONNECTION_STATE.CHALLENGING)
|
|
278
269
|
this._submit(
|
|
279
|
-
messageBuilder.getMsg(C.TOPIC.CONNECTION, C.ACTIONS.CHALLENGE_RESPONSE, [this._url])
|
|
270
|
+
messageBuilder.getMsg(C.TOPIC.CONNECTION, C.ACTIONS.CHALLENGE_RESPONSE, [this._url]),
|
|
280
271
|
)
|
|
281
272
|
} else if (message.action === C.ACTIONS.REJECTION) {
|
|
282
273
|
this._challengeDenied = true
|
|
@@ -344,10 +335,16 @@ Connection.prototype._tryReconnect = function () {
|
|
|
344
335
|
|
|
345
336
|
if (this._reconnectionAttempt < this._options.maxReconnectAttempts) {
|
|
346
337
|
this._setState(C.CONNECTION_STATE.RECONNECTING)
|
|
347
|
-
this._reconnectTimeout = setTimeout(
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
338
|
+
this._reconnectTimeout = setTimeout(
|
|
339
|
+
() => {
|
|
340
|
+
this._reconnectTimeout = null
|
|
341
|
+
this._createEndpoint()
|
|
342
|
+
},
|
|
343
|
+
Math.min(
|
|
344
|
+
this._options.maxReconnectInterval,
|
|
345
|
+
this._options.reconnectIntervalIncrement * this._reconnectionAttempt,
|
|
346
|
+
),
|
|
347
|
+
)
|
|
351
348
|
this._reconnectionAttempt++
|
|
352
349
|
} else {
|
|
353
350
|
this._clearReconnect()
|
|
@@ -363,5 +360,3 @@ Connection.prototype._clearReconnect = function () {
|
|
|
363
360
|
}
|
|
364
361
|
this._reconnectionAttempt = 0
|
|
365
362
|
}
|
|
366
|
-
|
|
367
|
-
export default Connection
|
|
@@ -1,94 +1,25 @@
|
|
|
1
1
|
import * as C from '../constants/constants.js'
|
|
2
|
-
import varint from 'varint'
|
|
3
2
|
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
let poolSize
|
|
7
|
-
let poolBuffer
|
|
8
|
-
let poolView
|
|
9
|
-
let poolOffset
|
|
10
|
-
|
|
11
|
-
function reallocPool(size) {
|
|
12
|
-
poolSize = size || poolSize || 1024 * 1024
|
|
13
|
-
poolBuffer = new Uint8Array(new ArrayBuffer(poolSize))
|
|
14
|
-
poolView = new DataView(poolBuffer.buffer)
|
|
15
|
-
poolOffset = 0
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function alignPool() {
|
|
19
|
-
// Ensure aligned slices
|
|
20
|
-
if (poolOffset & 0x7) {
|
|
21
|
-
poolOffset |= 0x7
|
|
22
|
-
poolOffset++
|
|
23
|
-
}
|
|
24
|
-
}
|
|
3
|
+
const SEP = C.MESSAGE_PART_SEPERATOR
|
|
25
4
|
|
|
26
5
|
export function getMsg(topic, action, data) {
|
|
27
6
|
if (data && !(data instanceof Array)) {
|
|
28
7
|
throw new Error('data must be an array')
|
|
29
8
|
}
|
|
30
9
|
|
|
31
|
-
|
|
32
|
-
reallocPool()
|
|
33
|
-
} else {
|
|
34
|
-
alignPool()
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const start = poolOffset
|
|
38
|
-
|
|
39
|
-
const headerSize = 8
|
|
40
|
-
poolBuffer[poolOffset++] = 128 + headerSize
|
|
41
|
-
let headerPos = poolOffset
|
|
42
|
-
poolOffset += headerSize - 1
|
|
43
|
-
|
|
44
|
-
poolBuffer[poolOffset++] = topic.charCodeAt(0)
|
|
45
|
-
poolBuffer[poolOffset++] = 31
|
|
46
|
-
for (let n = 0; n < action.length; n++) {
|
|
47
|
-
poolBuffer[poolOffset++] = action.charCodeAt(n)
|
|
48
|
-
}
|
|
10
|
+
const sendData = [topic, action]
|
|
49
11
|
|
|
50
12
|
if (data) {
|
|
51
13
|
for (let i = 0; i < data.length; i++) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (data[i] == null) {
|
|
55
|
-
poolBuffer[poolOffset++] = 31
|
|
56
|
-
len = 0
|
|
57
|
-
} else if (type === 'object') {
|
|
58
|
-
poolBuffer[poolOffset++] = 31
|
|
59
|
-
const res = poolEncoder.encodeInto(
|
|
60
|
-
JSON.stringify(data[i]),
|
|
61
|
-
new Uint8Array(poolBuffer.buffer, poolOffset)
|
|
62
|
-
)
|
|
63
|
-
len = res.written
|
|
64
|
-
} else if (type === 'bigint') {
|
|
65
|
-
poolBuffer[poolOffset++] = 31
|
|
66
|
-
poolView.setBigUint64(poolOffset, data[i], false)
|
|
67
|
-
len = 8
|
|
68
|
-
} else if (type === 'string') {
|
|
69
|
-
poolBuffer[poolOffset++] = 31
|
|
70
|
-
const res = poolEncoder.encodeInto(data[i], new Uint8Array(poolBuffer.buffer, poolOffset))
|
|
71
|
-
len = res.written
|
|
14
|
+
if (typeof data[i] === 'object') {
|
|
15
|
+
sendData.push(JSON.stringify(data[i]))
|
|
72
16
|
} else {
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
poolOffset += len
|
|
77
|
-
|
|
78
|
-
varint.encode(len + 1, poolBuffer, headerPos)
|
|
79
|
-
headerPos += varint.encode.bytes
|
|
80
|
-
if (headerPos - start >= headerSize) {
|
|
81
|
-
throw new Error(`header too large: ${headerPos - start} ${headerSize}`)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (poolOffset >= poolBuffer.length) {
|
|
85
|
-
reallocPool(start === 0 ? poolSize * 2 : poolSize)
|
|
86
|
-
return getMsg(topic, action, data)
|
|
17
|
+
sendData.push(data[i])
|
|
87
18
|
}
|
|
88
19
|
}
|
|
89
20
|
}
|
|
90
21
|
|
|
91
|
-
return
|
|
22
|
+
return sendData.join(SEP)
|
|
92
23
|
}
|
|
93
24
|
|
|
94
25
|
export function typed(value) {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import * as C from '../constants/constants.js'
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
3
|
+
const actions = {}
|
|
4
|
+
|
|
5
|
+
for (const key in C.ACTIONS) {
|
|
6
|
+
actions[C.ACTIONS[key]] = key
|
|
5
7
|
}
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
export function convertTyped(value, client) {
|
|
8
10
|
const type = value.charAt(0)
|
|
9
11
|
|
|
10
12
|
if (type === C.TYPES.STRING) {
|
|
@@ -45,24 +47,14 @@ MessageParser.prototype.convertTyped = function (value, client) {
|
|
|
45
47
|
return undefined
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
const actions = {}
|
|
50
|
-
|
|
51
|
-
for (const key in C.ACTIONS) {
|
|
52
|
-
actions[C.ACTIONS[key]] = key
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return actions
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
MessageParser.prototype.parseMessage = function (message, client, result) {
|
|
50
|
+
export function parseMessage(message, client, result) {
|
|
59
51
|
const parts = message.split(C.MESSAGE_PART_SEPERATOR)
|
|
60
52
|
|
|
61
53
|
if (parts.length < 2) {
|
|
62
54
|
client._$onError(
|
|
63
55
|
C.TOPIC.ERROR,
|
|
64
56
|
C.EVENT.MESSAGE_PARSE_ERROR,
|
|
65
|
-
new Error('Insufficiant message parts')
|
|
57
|
+
new Error('Insufficiant message parts'),
|
|
66
58
|
)
|
|
67
59
|
return null
|
|
68
60
|
}
|
|
@@ -72,12 +64,12 @@ MessageParser.prototype.parseMessage = function (message, client, result) {
|
|
|
72
64
|
return null
|
|
73
65
|
}
|
|
74
66
|
|
|
75
|
-
if (
|
|
67
|
+
if (actions[parts[1]] === undefined) {
|
|
76
68
|
client._$onError(
|
|
77
69
|
C.TOPIC.ERROR,
|
|
78
70
|
C.EVENT.MESSAGE_PARSE_ERROR,
|
|
79
71
|
new Error('Unknown action'),
|
|
80
|
-
message
|
|
72
|
+
message,
|
|
81
73
|
)
|
|
82
74
|
return null
|
|
83
75
|
}
|
|
@@ -87,5 +79,3 @@ MessageParser.prototype.parseMessage = function (message, client, result) {
|
|
|
87
79
|
result.action = parts[1]
|
|
88
80
|
result.data = parts.splice(2)
|
|
89
81
|
}
|
|
90
|
-
|
|
91
|
-
export default new MessageParser()
|
|
@@ -49,8 +49,8 @@ function onTimeout(subscription) {
|
|
|
49
49
|
new Error(`timeout state: ${subscription.record.name} [${current}<${expected}]`),
|
|
50
50
|
{
|
|
51
51
|
code: 'ETIMEDOUT',
|
|
52
|
-
}
|
|
53
|
-
)
|
|
52
|
+
},
|
|
53
|
+
),
|
|
54
54
|
)
|
|
55
55
|
}
|
|
56
56
|
|
|
@@ -101,7 +101,6 @@ class RecordHandler {
|
|
|
101
101
|
this._pruning = new Set()
|
|
102
102
|
this._patching = new Map()
|
|
103
103
|
this._updating = new Map()
|
|
104
|
-
this._encoder = new TextEncoder()
|
|
105
104
|
|
|
106
105
|
this._connected = 0
|
|
107
106
|
this._stats = {
|
|
@@ -207,12 +206,6 @@ class RecordHandler {
|
|
|
207
206
|
}
|
|
208
207
|
}
|
|
209
208
|
|
|
210
|
-
getKey(name) {
|
|
211
|
-
return name.length <= 8 && this._encoder.encode(name).byteLength === 8
|
|
212
|
-
? name
|
|
213
|
-
: this._connection.hasher.h64(name)
|
|
214
|
-
}
|
|
215
|
-
|
|
216
209
|
/**
|
|
217
210
|
* @param {string} name
|
|
218
211
|
* @returns {Record}
|
|
@@ -220,13 +213,13 @@ class RecordHandler {
|
|
|
220
213
|
getRecord(name) {
|
|
221
214
|
invariant(
|
|
222
215
|
typeof name === 'string' && name.length > 0 && name !== '[object Object]',
|
|
223
|
-
`invalid name ${name}
|
|
216
|
+
`invalid name ${name}`,
|
|
224
217
|
)
|
|
225
218
|
|
|
226
219
|
let record = this._records.get(name)
|
|
227
220
|
|
|
228
221
|
if (!record) {
|
|
229
|
-
record = new Record(
|
|
222
|
+
record = new Record(name, this)
|
|
230
223
|
this._stats.records += 1
|
|
231
224
|
this._stats.created += 1
|
|
232
225
|
this._records.set(name, record)
|
|
@@ -294,17 +287,22 @@ class RecordHandler {
|
|
|
294
287
|
const patching = [...this._patching.values()]
|
|
295
288
|
await Promise.race([
|
|
296
289
|
Promise.all(
|
|
297
|
-
patching.map((callbacks) => new Promise((resolve) => callbacks.push(resolve)))
|
|
290
|
+
patching.map((callbacks) => new Promise((resolve) => callbacks.push(resolve))),
|
|
298
291
|
),
|
|
299
292
|
new Promise((resolve) => {
|
|
300
|
-
patchingTimeout = timers.setTimeout(
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
293
|
+
patchingTimeout = timers.setTimeout(
|
|
294
|
+
() => {
|
|
295
|
+
this._client._$onError(
|
|
296
|
+
C.TOPIC.RECORD,
|
|
297
|
+
C.EVENT.TIMEOUT,
|
|
298
|
+
Object.assign(new Error('sync patching timeout'), {
|
|
299
|
+
data: { patching, timeout },
|
|
300
|
+
}),
|
|
301
|
+
)
|
|
302
|
+
resolve(null)
|
|
303
|
+
},
|
|
304
|
+
timeout ?? 2 * 60e3,
|
|
305
|
+
)
|
|
308
306
|
}),
|
|
309
307
|
signalPromise,
|
|
310
308
|
]).finally(() => {
|
|
@@ -317,17 +315,22 @@ class RecordHandler {
|
|
|
317
315
|
const updating = [...this._updating.values()]
|
|
318
316
|
await Promise.race([
|
|
319
317
|
Promise.all(
|
|
320
|
-
updating.map((callbacks) => new Promise((resolve) => callbacks.push(resolve)))
|
|
318
|
+
updating.map((callbacks) => new Promise((resolve) => callbacks.push(resolve))),
|
|
321
319
|
),
|
|
322
320
|
new Promise((resolve) => {
|
|
323
|
-
updatingTimeout = timers.setTimeout(
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
321
|
+
updatingTimeout = timers.setTimeout(
|
|
322
|
+
() => {
|
|
323
|
+
this._client._$onError(
|
|
324
|
+
C.TOPIC.RECORD,
|
|
325
|
+
C.EVENT.TIMEOUT,
|
|
326
|
+
Object.assign(new Error('sync updating timeout'), {
|
|
327
|
+
data: { updating, timeout },
|
|
328
|
+
}),
|
|
329
|
+
)
|
|
330
|
+
resolve(null)
|
|
331
|
+
},
|
|
332
|
+
timeout ?? 2 * 60e3,
|
|
333
|
+
)
|
|
331
334
|
}),
|
|
332
335
|
signalPromise,
|
|
333
336
|
]).finally(() => {
|
|
@@ -343,14 +346,17 @@ class RecordHandler {
|
|
|
343
346
|
this._connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SYNC, [token])
|
|
344
347
|
}),
|
|
345
348
|
new Promise((resolve) => {
|
|
346
|
-
serverTimeout = timers.setTimeout(
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
349
|
+
serverTimeout = timers.setTimeout(
|
|
350
|
+
() => {
|
|
351
|
+
this._client._$onError(
|
|
352
|
+
C.TOPIC.RECORD,
|
|
353
|
+
C.EVENT.TIMEOUT,
|
|
354
|
+
Object.assign(new Error('sync server timeout'), { data: { token, timeout } }),
|
|
355
|
+
)
|
|
356
|
+
resolve(null)
|
|
357
|
+
},
|
|
358
|
+
timeout ?? 2 * 60e3,
|
|
359
|
+
)
|
|
354
360
|
}),
|
|
355
361
|
signalPromise,
|
|
356
362
|
]).finally(() => {
|
|
@@ -423,7 +429,7 @@ class RecordHandler {
|
|
|
423
429
|
timeout: 2 * 60e3,
|
|
424
430
|
dataOnly: true,
|
|
425
431
|
},
|
|
426
|
-
...args
|
|
432
|
+
...args,
|
|
427
433
|
)
|
|
428
434
|
}
|
|
429
435
|
|
|
@@ -499,7 +505,7 @@ class RecordHandler {
|
|
|
499
505
|
{
|
|
500
506
|
timeout: 2 * 60e3,
|
|
501
507
|
},
|
|
502
|
-
...args
|
|
508
|
+
...args,
|
|
503
509
|
)
|
|
504
510
|
}
|
|
505
511
|
|
package/src/record/record.js
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import jsonPath from '@nxtedition/json-path'
|
|
2
2
|
import * as utils from '../utils/utils.js'
|
|
3
3
|
import * as C from '../constants/constants.js'
|
|
4
|
-
import messageParser from '../message/message-parser.js'
|
|
4
|
+
import * as messageParser from '../message/message-parser.js'
|
|
5
5
|
import xuid from 'xuid'
|
|
6
6
|
import invariant from 'invariant'
|
|
7
7
|
import cloneDeep from 'lodash.clonedeep'
|
|
8
8
|
import * as timers from '../utils/timers.js'
|
|
9
9
|
|
|
10
|
-
// const encoder = new TextEncoder()
|
|
11
|
-
|
|
12
10
|
class Record {
|
|
13
11
|
static STATE = C.RECORD_STATE
|
|
14
12
|
|
|
15
|
-
constructor(
|
|
13
|
+
constructor(name, handler) {
|
|
14
|
+
const connection = handler._connection
|
|
15
|
+
|
|
16
16
|
this._handler = handler
|
|
17
17
|
this._name = name
|
|
18
|
-
this._key = key
|
|
19
18
|
this._version = ''
|
|
20
19
|
this._data = jsonPath.EMPTY
|
|
21
20
|
this._state = C.RECORD_STATE.VOID
|
|
@@ -25,7 +24,7 @@ class Record {
|
|
|
25
24
|
|
|
26
25
|
/** @type Map? */ this._updating = null
|
|
27
26
|
/** @type Array? */ this._patching = null
|
|
28
|
-
this._subscribed =
|
|
27
|
+
this._subscribed = connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [this._name])
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
/** @type {string} */
|
|
@@ -63,8 +62,7 @@ class Record {
|
|
|
63
62
|
if (this._refs === 1) {
|
|
64
63
|
this._handler._onPruning(this, false)
|
|
65
64
|
this._subscribed =
|
|
66
|
-
this._subscribed ||
|
|
67
|
-
connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [this._name, this._key])
|
|
65
|
+
this._subscribed || connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [this._name])
|
|
68
66
|
}
|
|
69
67
|
return this
|
|
70
68
|
}
|
|
@@ -251,7 +249,7 @@ class Record {
|
|
|
251
249
|
onDone(
|
|
252
250
|
Object.assign(new Error(`timeout ${this.name} [${current}<${expected}]`), {
|
|
253
251
|
code: 'ETIMEDOUT',
|
|
254
|
-
})
|
|
252
|
+
}),
|
|
255
253
|
)
|
|
256
254
|
}, timeout)
|
|
257
255
|
}
|
|
@@ -326,8 +324,7 @@ class Record {
|
|
|
326
324
|
|
|
327
325
|
if (connected) {
|
|
328
326
|
this._subscribed =
|
|
329
|
-
this._refs > 0 &&
|
|
330
|
-
connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [this._name, this._key])
|
|
327
|
+
this._refs > 0 && connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [this._name])
|
|
331
328
|
|
|
332
329
|
if (this._updating) {
|
|
333
330
|
for (const update of this._updating.values()) {
|
|
@@ -352,7 +349,7 @@ class Record {
|
|
|
352
349
|
invariant(!this._updating, 'must not have updates')
|
|
353
350
|
|
|
354
351
|
if (this._subscribed) {
|
|
355
|
-
connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.UNSUBSCRIBE, [this.
|
|
352
|
+
connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.UNSUBSCRIBE, [this._name])
|
|
356
353
|
this._subscribed = false
|
|
357
354
|
}
|
|
358
355
|
|
|
@@ -374,7 +371,7 @@ class Record {
|
|
|
374
371
|
const prevVersion = this._version
|
|
375
372
|
const nextVersion = this._makeVersion(parseInt(prevVersion) + 1)
|
|
376
373
|
|
|
377
|
-
const update = [this.
|
|
374
|
+
const update = [this._name, nextVersion, jsonPath.stringify(nextData), prevVersion]
|
|
378
375
|
|
|
379
376
|
if (!this._updating) {
|
|
380
377
|
this._onUpdating(true)
|
|
@@ -473,8 +470,8 @@ class Record {
|
|
|
473
470
|
hasProvider && messageParser.convertTyped(hasProvider, this._handler._client)
|
|
474
471
|
? C.RECORD_STATE.PROVIDER
|
|
475
472
|
: this._version.charAt(0) === 'I'
|
|
476
|
-
|
|
477
|
-
|
|
473
|
+
? C.RECORD_STATE.STALE
|
|
474
|
+
: C.RECORD_STATE.SERVER
|
|
478
475
|
|
|
479
476
|
if (this._state !== prevState) {
|
|
480
477
|
this._emitUpdate()
|
package/src/rpc/rpc-handler.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as C from '../constants/constants.js'
|
|
2
2
|
import RpcResponse from './rpc-response.js'
|
|
3
|
-
import messageParser from '../message/message-parser.js'
|
|
3
|
+
import * as messageParser from '../message/message-parser.js'
|
|
4
4
|
import * as messageBuilder from '../message/message-builder.js'
|
|
5
5
|
import xuid from 'xuid'
|
|
6
6
|
|
|
@@ -153,7 +153,7 @@ RpcHandler.prototype._$handle = function (message) {
|
|
|
153
153
|
rpcId: rpc.id,
|
|
154
154
|
rpcName: rpc.name,
|
|
155
155
|
rpcData: rpc.data,
|
|
156
|
-
})
|
|
156
|
+
}),
|
|
157
157
|
)
|
|
158
158
|
} else {
|
|
159
159
|
rpc.callback(null, messageParser.convertTyped(data, this._client))
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import * as rxjs from 'rxjs'
|
|
1
2
|
import * as C from '../constants/constants.js'
|
|
2
|
-
import
|
|
3
|
+
import { h64ToString } from '../utils/utils.js'
|
|
3
4
|
|
|
4
|
-
class Listener {
|
|
5
|
+
export default class Listener {
|
|
5
6
|
constructor(topic, pattern, callback, handler, { recursive = false, stringify = null } = {}) {
|
|
6
7
|
this._topic = topic
|
|
7
8
|
this._pattern = pattern
|
|
@@ -40,7 +41,7 @@ class Listener {
|
|
|
40
41
|
C.TOPIC.RECORD,
|
|
41
42
|
C.EVENT.NOT_CONNECTED,
|
|
42
43
|
new Error('received message while not connected'),
|
|
43
|
-
message
|
|
44
|
+
message,
|
|
44
45
|
)
|
|
45
46
|
return
|
|
46
47
|
}
|
|
@@ -56,7 +57,6 @@ class Listener {
|
|
|
56
57
|
// TODO (refactor): Move to class
|
|
57
58
|
const provider = {
|
|
58
59
|
name,
|
|
59
|
-
key: this._handler.getKey(name),
|
|
60
60
|
value$: null,
|
|
61
61
|
sending: false,
|
|
62
62
|
accepted: false,
|
|
@@ -69,7 +69,7 @@ class Listener {
|
|
|
69
69
|
if (this.connected && provider.accepted) {
|
|
70
70
|
this._connection.sendMsg(this._topic, C.ACTIONS.LISTEN_REJECT, [
|
|
71
71
|
this._pattern,
|
|
72
|
-
provider.
|
|
72
|
+
provider.name,
|
|
73
73
|
])
|
|
74
74
|
}
|
|
75
75
|
|
|
@@ -102,7 +102,7 @@ class Listener {
|
|
|
102
102
|
this._connection.sendMsg(
|
|
103
103
|
this._topic,
|
|
104
104
|
accepted ? C.ACTIONS.LISTEN_ACCEPT : C.ACTIONS.LISTEN_REJECT,
|
|
105
|
-
[this._pattern, provider.
|
|
105
|
+
[this._pattern, provider.name],
|
|
106
106
|
)
|
|
107
107
|
|
|
108
108
|
provider.version = null
|
|
@@ -152,13 +152,13 @@ class Listener {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
const body = typeof value !== 'string' ? this._stringify(value) : value
|
|
155
|
-
const hash =
|
|
155
|
+
const hash = h64ToString(body)
|
|
156
156
|
const version = `INF-${hash}`
|
|
157
157
|
|
|
158
158
|
if (provider.version !== version) {
|
|
159
159
|
provider.version = version
|
|
160
160
|
this._connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.UPDATE, [
|
|
161
|
-
provider.
|
|
161
|
+
provider.name,
|
|
162
162
|
version,
|
|
163
163
|
body,
|
|
164
164
|
])
|
|
@@ -219,11 +219,7 @@ class Listener {
|
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
_error(name, err) {
|
|
222
|
-
this._client._$onError(this._topic, C.EVENT.LISTENER_ERROR, err, [
|
|
223
|
-
this._pattern,
|
|
224
|
-
name,
|
|
225
|
-
this._handler.getKey(name),
|
|
226
|
-
])
|
|
222
|
+
this._client._$onError(this._topic, C.EVENT.LISTENER_ERROR, err, [this._pattern, name])
|
|
227
223
|
}
|
|
228
224
|
|
|
229
225
|
_reset() {
|
|
@@ -233,5 +229,3 @@ class Listener {
|
|
|
233
229
|
this._subscriptions.clear()
|
|
234
230
|
}
|
|
235
231
|
}
|
|
236
|
-
|
|
237
|
-
export default Listener
|
package/src/utils/timers.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
const fastNowInterval = 1e3
|
|
2
|
+
let fastNow = 0
|
|
2
3
|
let fastNowTimeout
|
|
3
4
|
|
|
4
5
|
const fastTimers = []
|
|
5
6
|
|
|
6
7
|
function onTimeout() {
|
|
7
|
-
fastNow
|
|
8
|
+
fastNow += fastNowInterval
|
|
8
9
|
|
|
9
10
|
let len = fastTimers.length
|
|
10
11
|
let idx = 0
|
|
@@ -41,7 +42,7 @@ function refreshTimeout() {
|
|
|
41
42
|
fastNowTimeout.refresh()
|
|
42
43
|
} else {
|
|
43
44
|
globalThis.clearTimeout(fastNowTimeout)
|
|
44
|
-
fastNowTimeout = globalThis.setTimeout(onTimeout,
|
|
45
|
+
fastNowTimeout = globalThis.setTimeout(onTimeout, fastNowInterval)
|
|
45
46
|
if (fastNowTimeout.unref) {
|
|
46
47
|
fastNowTimeout.unref()
|
|
47
48
|
}
|
|
@@ -80,7 +81,7 @@ class Timeout {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
export function setTimeout(callback, delay, opaque) {
|
|
83
|
-
return delay <
|
|
84
|
+
return delay < fastNowInterval
|
|
84
85
|
? globalThis.setTimeout(callback, delay, opaque)
|
|
85
86
|
: new Timeout(callback, delay, opaque)
|
|
86
87
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import * as rxjs from 'rxjs'
|
|
1
2
|
import * as C from '../constants/constants.js'
|
|
2
|
-
import
|
|
3
|
-
import rxjs from 'rxjs'
|
|
3
|
+
import { h64ToString } from '../utils/utils.js'
|
|
4
4
|
|
|
5
5
|
const PIPE = rxjs.pipe(
|
|
6
|
-
|
|
6
|
+
rxjs.map((value) => {
|
|
7
7
|
let data
|
|
8
8
|
if (value && typeof value === 'string') {
|
|
9
9
|
if (value.charAt(0) !== '{' && value.charAt(0) !== '[') {
|
|
@@ -18,10 +18,10 @@ const PIPE = rxjs.pipe(
|
|
|
18
18
|
|
|
19
19
|
return data
|
|
20
20
|
}),
|
|
21
|
-
|
|
21
|
+
rxjs.distinctUntilChanged(),
|
|
22
22
|
)
|
|
23
23
|
|
|
24
|
-
class Listener {
|
|
24
|
+
export default class Listener {
|
|
25
25
|
constructor(topic, pattern, callback, handler, opts) {
|
|
26
26
|
if (opts.recursive) {
|
|
27
27
|
throw new Error('invalid argument: recursive')
|
|
@@ -68,29 +68,27 @@ class Listener {
|
|
|
68
68
|
value$ = rxjs.throwError(() => err)
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
const key = this._handler.getKey(name)
|
|
72
|
-
|
|
73
71
|
if (value$) {
|
|
74
72
|
const subscription = value$.pipe(PIPE).subscribe({
|
|
75
73
|
next: (data) => {
|
|
76
74
|
if (data == null) {
|
|
77
|
-
this._connection.sendMsg(this._topic, C.ACTIONS.LISTEN_REJECT, [this._pattern,
|
|
75
|
+
this._connection.sendMsg(this._topic, C.ACTIONS.LISTEN_REJECT, [this._pattern, name])
|
|
78
76
|
this._subscriptions.delete(name)
|
|
79
77
|
subscription.unsubscribe()
|
|
80
78
|
} else {
|
|
81
|
-
const version = `INF-${
|
|
82
|
-
this._connection.sendMsg(this._topic, C.ACTIONS.UPDATE, [
|
|
79
|
+
const version = `INF-${h64ToString(data)}`
|
|
80
|
+
this._connection.sendMsg(this._topic, C.ACTIONS.UPDATE, [name, version, data])
|
|
83
81
|
}
|
|
84
82
|
},
|
|
85
83
|
error: (err) => {
|
|
86
84
|
this._error(name, err)
|
|
87
|
-
this._connection.sendMsg(this._topic, C.ACTIONS.LISTEN_REJECT, [this._pattern,
|
|
85
|
+
this._connection.sendMsg(this._topic, C.ACTIONS.LISTEN_REJECT, [this._pattern, name])
|
|
88
86
|
this._subscriptions.delete(name)
|
|
89
87
|
},
|
|
90
88
|
})
|
|
91
89
|
this._subscriptions.set(name, subscription)
|
|
92
90
|
} else {
|
|
93
|
-
this._connection.sendMsg(this._topic, C.ACTIONS.LISTEN_REJECT, [this._pattern,
|
|
91
|
+
this._connection.sendMsg(this._topic, C.ACTIONS.LISTEN_REJECT, [this._pattern, name])
|
|
94
92
|
}
|
|
95
93
|
} else if (message.action === C.ACTIONS.LISTEN_REJECT) {
|
|
96
94
|
const subscription = this._subscriptions.get(name)
|
|
@@ -128,5 +126,3 @@ class Listener {
|
|
|
128
126
|
this._connection.sendMsg(this._topic, C.ACTIONS.UNLISTEN, [this._pattern])
|
|
129
127
|
}
|
|
130
128
|
}
|
|
131
|
-
|
|
132
|
-
export default Listener
|
package/src/utils/utils.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import xxhash from 'xxhash-wasm'
|
|
2
|
+
|
|
1
3
|
const NODE_ENV = typeof process !== 'undefined' && process.env && process.env.NODE_ENV
|
|
2
4
|
export const isNode = typeof process !== 'undefined' && process.toString() === '[object process]'
|
|
3
5
|
export const isProduction = NODE_ENV === 'production'
|
|
@@ -174,3 +176,9 @@ export function removeAbortListener(signal, handler) {
|
|
|
174
176
|
}
|
|
175
177
|
}
|
|
176
178
|
}
|
|
179
|
+
|
|
180
|
+
const HASHER = await xxhash()
|
|
181
|
+
|
|
182
|
+
export function h64ToString(str) {
|
|
183
|
+
return HASHER.h64ToString(str)
|
|
184
|
+
}
|