@nxtedition/deepstream.io-client-js 25.1.3 → 25.6.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/package.json +5 -4
- package/src/client.js +2 -4
- package/src/event/event-handler.js +3 -3
- package/src/message/connection.js +40 -83
- package/src/message/message-builder.js +6 -81
- package/src/message/message-parser.js +39 -0
- package/src/record/record-handler.js +4 -14
- package/src/record/record.js +10 -26
- package/src/rpc/rpc-handler.js +4 -4
- 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 +5 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxtedition/deepstream.io-client-js",
|
|
3
|
-
"version": "25.1
|
|
3
|
+
"version": "25.6.1",
|
|
4
4
|
"description": "the javascript client for deepstream.io",
|
|
5
5
|
"homepage": "http://deepstream.io",
|
|
6
6
|
"type": "module",
|
|
@@ -75,14 +75,15 @@
|
|
|
75
75
|
"eslint-config-prettier": "^9.1.0",
|
|
76
76
|
"eslint-config-standard": "^17.1.0",
|
|
77
77
|
"eslint-plugin-import": "^2.29.1",
|
|
78
|
-
"eslint-plugin-n": "^17.
|
|
78
|
+
"eslint-plugin-n": "^17.10.1",
|
|
79
79
|
"eslint-plugin-node": "^11.1.0",
|
|
80
80
|
"eslint-plugin-promise": "^7.0.0",
|
|
81
|
-
"husky": "^9.1.
|
|
81
|
+
"husky": "^9.1.3",
|
|
82
82
|
"lint-staged": "^15.2.7",
|
|
83
83
|
"mitata": "^0.1.11",
|
|
84
84
|
"pinst": "^3.0.0",
|
|
85
|
-
"prettier": "^3.3.3"
|
|
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,10 +1,10 @@
|
|
|
1
1
|
import * as C from '../constants/constants.js'
|
|
2
2
|
import * as messageBuilder from '../message/message-builder.js'
|
|
3
|
-
import
|
|
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'
|
|
7
|
-
import rxjs from 'rxjs'
|
|
7
|
+
import * as rxjs from 'rxjs'
|
|
8
8
|
|
|
9
9
|
const EventHandler = function (options, connection, client) {
|
|
10
10
|
this._options = options
|
|
@@ -143,7 +143,7 @@ EventHandler.prototype._$handle = function (message) {
|
|
|
143
143
|
|
|
144
144
|
if (message.action === C.ACTIONS.EVENT) {
|
|
145
145
|
if (message.data && message.data.length === 2) {
|
|
146
|
-
this._emitter.emit(name, convertTyped(data, this._client))
|
|
146
|
+
this._emitter.emit(name, messageParser.convertTyped(data, this._client))
|
|
147
147
|
} else {
|
|
148
148
|
this._emitter.emit(name)
|
|
149
149
|
}
|
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
import * as utils from '../utils/utils.js'
|
|
2
|
-
import
|
|
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
|
-
import varint from 'varint'
|
|
9
7
|
|
|
10
|
-
const BrowserWebSocket = globalThis.WebSocket || globalThis.MozWebSocket
|
|
11
8
|
const NodeWebSocket = utils.isNode ? await import('ws').then((x) => x.default) : null
|
|
9
|
+
const BrowserWebSocket = globalThis.WebSocket || globalThis.MozWebSocket
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const Connection = function (client, url, options) {
|
|
11
|
+
export default function Connection(client, url, options) {
|
|
16
12
|
this._client = client
|
|
17
13
|
this._options = options
|
|
18
14
|
this._logger = options.logger
|
|
@@ -24,11 +20,17 @@ const Connection = function (client, url, options) {
|
|
|
24
20
|
this._tooManyAuthAttempts = false
|
|
25
21
|
this._connectionAuthenticationTimeout = false
|
|
26
22
|
this._challengeDenied = false
|
|
27
|
-
this.
|
|
28
|
-
|
|
23
|
+
this._message = {
|
|
24
|
+
raw: null,
|
|
25
|
+
topic: null,
|
|
26
|
+
action: null,
|
|
27
|
+
data: null,
|
|
28
|
+
}
|
|
29
|
+
this._decoder = new globalThis.TextDecoder()
|
|
29
30
|
this._recvQueue = new FixedQueue()
|
|
30
31
|
this._reconnectTimeout = null
|
|
31
32
|
this._reconnectionAttempt = 0
|
|
33
|
+
this._endpoint = null
|
|
32
34
|
|
|
33
35
|
this._processingRecv = false
|
|
34
36
|
this._recvMessages = this._recvMessages.bind(this)
|
|
@@ -37,8 +39,6 @@ const Connection = function (client, url, options) {
|
|
|
37
39
|
|
|
38
40
|
this._state = C.CONNECTION_STATE.CLOSED
|
|
39
41
|
|
|
40
|
-
this.hasher = HASHER
|
|
41
|
-
|
|
42
42
|
this._createEndpoint()
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -93,19 +93,19 @@ Connection.prototype._createEndpoint = function () {
|
|
|
93
93
|
this._endpoint = new NodeWebSocket(this._url, {
|
|
94
94
|
generateMask() {},
|
|
95
95
|
})
|
|
96
|
+
this._endpoint.binaryType = 'nodebuffer'
|
|
96
97
|
} else {
|
|
97
98
|
this._endpoint = new BrowserWebSocket(this._url)
|
|
98
99
|
this._endpoint.binaryType = 'arraybuffer'
|
|
99
100
|
}
|
|
101
|
+
|
|
100
102
|
this._corked = false
|
|
101
103
|
|
|
102
104
|
this._endpoint.onopen = this._onOpen.bind(this)
|
|
103
105
|
this._endpoint.onerror = this._onError.bind(this)
|
|
104
106
|
this._endpoint.onclose = this._onClose.bind(this)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
this._onMessage(data)
|
|
108
|
-
}
|
|
107
|
+
this._endpoint.onmessage = ({ data }) =>
|
|
108
|
+
this._onMessage(typeof data === 'string' ? data : this._decoder.decode(data))
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
Connection.prototype.send = function (message) {
|
|
@@ -113,7 +113,12 @@ Connection.prototype.send = function (message) {
|
|
|
113
113
|
|
|
114
114
|
if (message.length > maxPacketSize) {
|
|
115
115
|
const err = new Error(`Packet to big: ${message.length} > ${maxPacketSize}`)
|
|
116
|
-
this._client._$onError(
|
|
116
|
+
this._client._$onError(
|
|
117
|
+
C.TOPIC.CONNECTION,
|
|
118
|
+
C.EVENT.CONNECTION_ERROR,
|
|
119
|
+
err,
|
|
120
|
+
message.split(C.MESSAGE_PART_SEPERATOR).map((x) => x.slice(0, 256)),
|
|
121
|
+
)
|
|
117
122
|
return false
|
|
118
123
|
}
|
|
119
124
|
|
|
@@ -142,8 +147,8 @@ Connection.prototype.send = function (message) {
|
|
|
142
147
|
Connection.prototype._submit = function (message) {
|
|
143
148
|
const { maxPacketSize } = this._options
|
|
144
149
|
|
|
145
|
-
if (message.
|
|
146
|
-
const err = new Error(`Packet to big: ${message.
|
|
150
|
+
if (message.length > maxPacketSize) {
|
|
151
|
+
const err = new Error(`Packet to big: ${message.length} > ${maxPacketSize}`)
|
|
147
152
|
this._client._$onError(C.TOPIC.CONNECTION, C.EVENT.CONNECTION_ERROR, err)
|
|
148
153
|
return false
|
|
149
154
|
} else if (this._endpoint.readyState === this._endpoint.OPEN) {
|
|
@@ -161,7 +166,7 @@ Connection.prototype._sendAuthParams = function () {
|
|
|
161
166
|
this._setState(C.CONNECTION_STATE.AUTHENTICATING)
|
|
162
167
|
const authMessage = messageBuilder.getMsg(C.TOPIC.AUTH, C.ACTIONS.REQUEST, [
|
|
163
168
|
this._authParams,
|
|
164
|
-
'25.1
|
|
169
|
+
'25.6.1',
|
|
165
170
|
utils.isNode
|
|
166
171
|
? `Node/${process.version}`
|
|
167
172
|
: globalThis.navigator && globalThis.navigator.userAgent,
|
|
@@ -206,60 +211,13 @@ Connection.prototype._onClose = function () {
|
|
|
206
211
|
}
|
|
207
212
|
}
|
|
208
213
|
|
|
209
|
-
Connection.prototype._onMessage = function (
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
raw = new Uint8Array(raw)
|
|
214
|
+
Connection.prototype._onMessage = function (data) {
|
|
215
|
+
// Remove MESSAGE_SEPERATOR if exists.
|
|
216
|
+
if (data.charCodeAt(data.length - 1) === 30) {
|
|
217
|
+
data = data.slice(0, -1)
|
|
214
218
|
}
|
|
215
219
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const start = 0
|
|
219
|
-
|
|
220
|
-
let pos = start
|
|
221
|
-
|
|
222
|
-
let headerSize = 0
|
|
223
|
-
if (raw[pos] >= 128) {
|
|
224
|
-
headerSize = raw[pos] - 128
|
|
225
|
-
pos += headerSize
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// TODO (perf): Use numbers instead of string..
|
|
229
|
-
const topic = String.fromCharCode(raw[pos++])
|
|
230
|
-
pos++
|
|
231
|
-
|
|
232
|
-
let action = ''
|
|
233
|
-
while (pos < len && raw[pos] !== 31) {
|
|
234
|
-
// TODO (perf): Use numbers instead of string..
|
|
235
|
-
action += String.fromCharCode(raw[pos++])
|
|
236
|
-
}
|
|
237
|
-
pos++
|
|
238
|
-
|
|
239
|
-
// TODO (fix): Validate topic and action
|
|
240
|
-
|
|
241
|
-
let data
|
|
242
|
-
|
|
243
|
-
// TODO (fix): Don't stringify binary data...
|
|
244
|
-
|
|
245
|
-
if (headerSize > 0) {
|
|
246
|
-
data = []
|
|
247
|
-
let headerPos = start + 1
|
|
248
|
-
while (headerPos < headerSize) {
|
|
249
|
-
const len = varint.decode(raw, headerPos)
|
|
250
|
-
headerPos += varint.decode.bytes
|
|
251
|
-
if (len === 0) {
|
|
252
|
-
break
|
|
253
|
-
}
|
|
254
|
-
data.push(this._decoder.decode(raw.subarray(pos, pos + len - 1)))
|
|
255
|
-
pos += len
|
|
256
|
-
}
|
|
257
|
-
} else {
|
|
258
|
-
data =
|
|
259
|
-
pos < len ? this._decoder.decode(raw.subarray(pos, len)).split(C.MESSAGE_PART_SEPERATOR) : []
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
this._recvQueue.push({ topic, action, data })
|
|
220
|
+
this._recvQueue.push(data)
|
|
263
221
|
if (!this._processingRecv) {
|
|
264
222
|
this._processingRecv = true
|
|
265
223
|
this._schedule(this._recvMessages)
|
|
@@ -283,19 +241,20 @@ Connection.prototype._recvMessages = function (deadline) {
|
|
|
283
241
|
continue
|
|
284
242
|
}
|
|
285
243
|
|
|
286
|
-
if (
|
|
287
|
-
this.
|
|
288
|
-
continue
|
|
244
|
+
if (this._logger) {
|
|
245
|
+
this._logger.trace(message, 'receive')
|
|
289
246
|
}
|
|
290
247
|
|
|
291
|
-
|
|
248
|
+
messageParser.parseMessage(message, this._client, this._message)
|
|
249
|
+
|
|
250
|
+
this.emit('recv', this._message)
|
|
292
251
|
|
|
293
|
-
if (
|
|
294
|
-
this._handleConnectionResponse(
|
|
295
|
-
} else if (
|
|
296
|
-
this._handleAuthResponse(
|
|
252
|
+
if (this._message.topic === C.TOPIC.CONNECTION) {
|
|
253
|
+
this._handleConnectionResponse(this._message)
|
|
254
|
+
} else if (this._message.topic === C.TOPIC.AUTH) {
|
|
255
|
+
this._handleAuthResponse(this._message)
|
|
297
256
|
} else {
|
|
298
|
-
this._client._$onMessage(
|
|
257
|
+
this._client._$onMessage(this._message)
|
|
299
258
|
}
|
|
300
259
|
}
|
|
301
260
|
|
|
@@ -352,7 +311,7 @@ Connection.prototype._getAuthData = function (data) {
|
|
|
352
311
|
if (data === undefined) {
|
|
353
312
|
return null
|
|
354
313
|
} else {
|
|
355
|
-
return convertTyped(data, this._client)
|
|
314
|
+
return messageParser.convertTyped(data, this._client)
|
|
356
315
|
}
|
|
357
316
|
}
|
|
358
317
|
|
|
@@ -406,5 +365,3 @@ Connection.prototype._clearReconnect = function () {
|
|
|
406
365
|
}
|
|
407
366
|
this._reconnectionAttempt = 0
|
|
408
367
|
}
|
|
409
|
-
|
|
410
|
-
export default Connection
|
|
@@ -1,100 +1,25 @@
|
|
|
1
1
|
import * as C from '../constants/constants.js'
|
|
2
|
-
import varint from 'varint'
|
|
3
|
-
import * as utils from '../utils/utils.js'
|
|
4
2
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
let poolSize
|
|
8
|
-
let poolBuffer
|
|
9
|
-
let poolView
|
|
10
|
-
let poolOffset
|
|
11
|
-
|
|
12
|
-
function allocPool(size) {
|
|
13
|
-
poolSize = size || poolSize || 1024 * 1024
|
|
14
|
-
poolBuffer = utils.isNode
|
|
15
|
-
? globalThis.Buffer.allocUnsafe(poolSize)
|
|
16
|
-
: new Uint8Array(new ArrayBuffer(poolSize))
|
|
17
|
-
poolView = new DataView(poolBuffer.buffer)
|
|
18
|
-
poolOffset = 0
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function alignPool() {
|
|
22
|
-
// Ensure aligned slices
|
|
23
|
-
if (poolOffset & 0x7) {
|
|
24
|
-
poolOffset |= 0x7
|
|
25
|
-
poolOffset++
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function writeString(dst, str, offset) {
|
|
30
|
-
if (utils.isNode) {
|
|
31
|
-
return dst.write(str, offset)
|
|
32
|
-
} else {
|
|
33
|
-
const res = poolEncoder.encodeInto(str, new Uint8Array(dst.buffer, offset))
|
|
34
|
-
return res.written
|
|
35
|
-
}
|
|
36
|
-
}
|
|
3
|
+
const SEP = C.MESSAGE_PART_SEPERATOR
|
|
37
4
|
|
|
38
5
|
export function getMsg(topic, action, data) {
|
|
39
6
|
if (data && !(data instanceof Array)) {
|
|
40
7
|
throw new Error('data must be an array')
|
|
41
8
|
}
|
|
42
9
|
|
|
43
|
-
|
|
44
|
-
allocPool()
|
|
45
|
-
} else {
|
|
46
|
-
alignPool()
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const start = poolOffset
|
|
50
|
-
|
|
51
|
-
const headerSize = 8
|
|
52
|
-
poolBuffer[poolOffset++] = 128 + headerSize
|
|
53
|
-
let headerPos = poolOffset
|
|
54
|
-
poolOffset += headerSize - 1
|
|
55
|
-
|
|
56
|
-
poolBuffer[poolOffset++] = topic.charCodeAt(0)
|
|
57
|
-
poolBuffer[poolOffset++] = 31
|
|
58
|
-
for (let n = 0; n < action.length; n++) {
|
|
59
|
-
poolBuffer[poolOffset++] = action.charCodeAt(n)
|
|
60
|
-
}
|
|
10
|
+
const sendData = [topic, action]
|
|
61
11
|
|
|
62
12
|
if (data) {
|
|
63
13
|
for (let i = 0; i < data.length; i++) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (data[i] == null) {
|
|
67
|
-
poolBuffer[poolOffset++] = 31
|
|
68
|
-
len = 0
|
|
69
|
-
} else if (type === 'object') {
|
|
70
|
-
poolBuffer[poolOffset++] = 31
|
|
71
|
-
len = writeString(poolBuffer, JSON.stringify(data[i]), poolOffset)
|
|
72
|
-
} else if (type === 'bigint') {
|
|
73
|
-
poolBuffer[poolOffset++] = 31
|
|
74
|
-
poolView.setBigUint64(poolOffset, data[i], false)
|
|
75
|
-
len = 8
|
|
76
|
-
} else if (type === 'string') {
|
|
77
|
-
poolBuffer[poolOffset++] = 31
|
|
78
|
-
len = writeString(poolBuffer, data[i], poolOffset)
|
|
14
|
+
if (typeof data[i] === 'object') {
|
|
15
|
+
sendData.push(JSON.stringify(data[i]))
|
|
79
16
|
} else {
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
poolOffset += len
|
|
83
|
-
|
|
84
|
-
varint.encode(len + 1, poolBuffer, headerPos)
|
|
85
|
-
headerPos += varint.encode.bytes
|
|
86
|
-
if (headerPos - start >= headerSize) {
|
|
87
|
-
throw new Error(`header too large: ${headerPos - start} ${headerSize}`)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (poolOffset >= poolBuffer.length) {
|
|
91
|
-
allocPool(start === 0 ? poolSize * 2 : poolSize)
|
|
92
|
-
return getMsg(topic, action, data)
|
|
17
|
+
sendData.push(data[i])
|
|
93
18
|
}
|
|
94
19
|
}
|
|
95
20
|
}
|
|
96
21
|
|
|
97
|
-
return
|
|
22
|
+
return sendData.join(SEP)
|
|
98
23
|
}
|
|
99
24
|
|
|
100
25
|
export function typed(value) {
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import * as C from '../constants/constants.js'
|
|
2
2
|
|
|
3
|
+
const actions = {}
|
|
4
|
+
|
|
5
|
+
for (const key in C.ACTIONS) {
|
|
6
|
+
actions[C.ACTIONS[key]] = key
|
|
7
|
+
}
|
|
8
|
+
|
|
3
9
|
export function convertTyped(value, client) {
|
|
4
10
|
const type = value.charAt(0)
|
|
5
11
|
|
|
@@ -40,3 +46,36 @@ export function convertTyped(value, client) {
|
|
|
40
46
|
|
|
41
47
|
return undefined
|
|
42
48
|
}
|
|
49
|
+
|
|
50
|
+
export function parseMessage(message, client, result) {
|
|
51
|
+
const parts = message.split(C.MESSAGE_PART_SEPERATOR)
|
|
52
|
+
|
|
53
|
+
if (parts.length < 2) {
|
|
54
|
+
client._$onError(
|
|
55
|
+
C.TOPIC.ERROR,
|
|
56
|
+
C.EVENT.MESSAGE_PARSE_ERROR,
|
|
57
|
+
new Error('Insufficiant message parts'),
|
|
58
|
+
)
|
|
59
|
+
return null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (parts[0] === C.TOPIC.ERROR) {
|
|
63
|
+
client._$onError(C.TOPIC.ERROR, parts[1], new Error('Message error'), message)
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (actions[parts[1]] === undefined) {
|
|
68
|
+
client._$onError(
|
|
69
|
+
C.TOPIC.ERROR,
|
|
70
|
+
C.EVENT.MESSAGE_PARSE_ERROR,
|
|
71
|
+
new Error('Unknown action'),
|
|
72
|
+
message,
|
|
73
|
+
)
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
result.raw = message
|
|
78
|
+
result.topic = parts[0]
|
|
79
|
+
result.action = parts[1]
|
|
80
|
+
result.data = parts.splice(2)
|
|
81
|
+
}
|
|
@@ -2,12 +2,11 @@ import Record from './record.js'
|
|
|
2
2
|
import MulticastListener from '../utils/multicast-listener.js'
|
|
3
3
|
import UnicastListener from '../utils/unicast-listener.js'
|
|
4
4
|
import * as C from '../constants/constants.js'
|
|
5
|
-
import rxjs from 'rxjs'
|
|
5
|
+
import * as rxjs from 'rxjs'
|
|
6
6
|
import invariant from 'invariant'
|
|
7
7
|
import EventEmitter from 'component-emitter2'
|
|
8
8
|
import jsonPath from '@nxtedition/json-path'
|
|
9
9
|
import * as utils from '../utils/utils.js'
|
|
10
|
-
import rx from 'rxjs/operators'
|
|
11
10
|
import xuid from 'xuid'
|
|
12
11
|
import * as timers from '../utils/timers.js'
|
|
13
12
|
|
|
@@ -101,7 +100,6 @@ class RecordHandler {
|
|
|
101
100
|
this._pruning = new Set()
|
|
102
101
|
this._patching = new Map()
|
|
103
102
|
this._updating = new Map()
|
|
104
|
-
this._encoder = new TextEncoder()
|
|
105
103
|
|
|
106
104
|
this._connected = 0
|
|
107
105
|
this._stats = {
|
|
@@ -207,14 +205,6 @@ class RecordHandler {
|
|
|
207
205
|
}
|
|
208
206
|
}
|
|
209
207
|
|
|
210
|
-
getKey(name) {
|
|
211
|
-
if (name.length > 8) {
|
|
212
|
-
return this._connection.hasher.h64(name)
|
|
213
|
-
}
|
|
214
|
-
const buf = this._encoder.encode(name)
|
|
215
|
-
return buf.byteLength === 8 ? utils.readBigUInt64BE(buf) : this._connection.hasher.h64Raw(buf)
|
|
216
|
-
}
|
|
217
|
-
|
|
218
208
|
/**
|
|
219
209
|
* @param {string} name
|
|
220
210
|
* @returns {Record}
|
|
@@ -228,7 +218,7 @@ class RecordHandler {
|
|
|
228
218
|
let record = this._records.get(name)
|
|
229
219
|
|
|
230
220
|
if (!record) {
|
|
231
|
-
record = new Record(
|
|
221
|
+
record = new Record(name, this)
|
|
232
222
|
this._stats.records += 1
|
|
233
223
|
this._stats.created += 1
|
|
234
224
|
this._records.set(name, record)
|
|
@@ -481,7 +471,7 @@ class RecordHandler {
|
|
|
481
471
|
// TODO (fix): Missing sync..
|
|
482
472
|
return new Promise((resolve, reject) => {
|
|
483
473
|
this.observe(...args)
|
|
484
|
-
.pipe(
|
|
474
|
+
.pipe(rxjs.first())
|
|
485
475
|
.subscribe({
|
|
486
476
|
next: resolve,
|
|
487
477
|
error: reject,
|
|
@@ -497,7 +487,7 @@ class RecordHandler {
|
|
|
497
487
|
get2(...args) {
|
|
498
488
|
return new Promise((resolve, reject) => {
|
|
499
489
|
this.observe2(...args)
|
|
500
|
-
.pipe(
|
|
490
|
+
.pipe(rxjs.first())
|
|
501
491
|
.subscribe({
|
|
502
492
|
next: resolve,
|
|
503
493
|
error: reject,
|
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
|
|
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,12 +24,7 @@ class Record {
|
|
|
25
24
|
|
|
26
25
|
/** @type Map? */ this._updating = null
|
|
27
26
|
/** @type Array? */ this._patching = null
|
|
28
|
-
this._subscribed =
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** @type {bigint} */
|
|
32
|
-
get key() {
|
|
33
|
-
return this._key
|
|
27
|
+
this._subscribed = connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.SUBSCRIBE, [this._name])
|
|
34
28
|
}
|
|
35
29
|
|
|
36
30
|
/** @type {string} */
|
|
@@ -68,8 +62,7 @@ class Record {
|
|
|
68
62
|
if (this._refs === 1) {
|
|
69
63
|
this._handler._onPruning(this, false)
|
|
70
64
|
this._subscribed =
|
|
71
|
-
this._subscribed ||
|
|
72
|
-
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])
|
|
73
66
|
}
|
|
74
67
|
return this
|
|
75
68
|
}
|
|
@@ -331,8 +324,7 @@ class Record {
|
|
|
331
324
|
|
|
332
325
|
if (connected) {
|
|
333
326
|
this._subscribed =
|
|
334
|
-
this._refs > 0 &&
|
|
335
|
-
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])
|
|
336
328
|
|
|
337
329
|
if (this._updating) {
|
|
338
330
|
for (const update of this._updating.values()) {
|
|
@@ -357,7 +349,7 @@ class Record {
|
|
|
357
349
|
invariant(!this._updating, 'must not have updates')
|
|
358
350
|
|
|
359
351
|
if (this._subscribed) {
|
|
360
|
-
connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.UNSUBSCRIBE, [this.
|
|
352
|
+
connection.sendMsg(C.TOPIC.RECORD, C.ACTIONS.UNSUBSCRIBE, [this._name])
|
|
361
353
|
this._subscribed = false
|
|
362
354
|
}
|
|
363
355
|
|
|
@@ -379,7 +371,7 @@ class Record {
|
|
|
379
371
|
const prevVersion = this._version
|
|
380
372
|
const nextVersion = this._makeVersion(parseInt(prevVersion) + 1)
|
|
381
373
|
|
|
382
|
-
const update = [this.
|
|
374
|
+
const update = [this._name, nextVersion, jsonPath.stringify(nextData), prevVersion]
|
|
383
375
|
|
|
384
376
|
if (!this._updating) {
|
|
385
377
|
this._onUpdating(true)
|
|
@@ -434,14 +426,6 @@ class Record {
|
|
|
434
426
|
this._state = this._version.charAt(0) === 'I' ? C.RECORD_STATE.STALE : C.RECORD_STATE.SERVER
|
|
435
427
|
}
|
|
436
428
|
|
|
437
|
-
// if (hasProvider != null ) {
|
|
438
|
-
// this._state = convertTyped(hasProvider, this._handler._client)
|
|
439
|
-
// ? C.RECORD_STATE.PROVIDER
|
|
440
|
-
// : this._version.charAt(0) === 'I'
|
|
441
|
-
// ? C.RECORD_STATE.STALE
|
|
442
|
-
// : C.RECORD_STATE.SERVER
|
|
443
|
-
// }
|
|
444
|
-
|
|
445
429
|
if (this._state !== prevState || this._data !== prevData || this._version !== prevVersion) {
|
|
446
430
|
this._emitUpdate()
|
|
447
431
|
}
|
|
@@ -483,7 +467,7 @@ class Record {
|
|
|
483
467
|
const prevState = this._state
|
|
484
468
|
|
|
485
469
|
this._state =
|
|
486
|
-
hasProvider && convertTyped(hasProvider, this._handler._client)
|
|
470
|
+
hasProvider && messageParser.convertTyped(hasProvider, this._handler._client)
|
|
487
471
|
? C.RECORD_STATE.PROVIDER
|
|
488
472
|
: this._version.charAt(0) === 'I'
|
|
489
473
|
? C.RECORD_STATE.STALE
|
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
|
|
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
|
|
|
@@ -111,7 +111,7 @@ RpcHandler.prototype._respond = function (message) {
|
|
|
111
111
|
if (callback) {
|
|
112
112
|
let promise
|
|
113
113
|
try {
|
|
114
|
-
promise = Promise.resolve(callback(convertTyped(data, this._client), response))
|
|
114
|
+
promise = Promise.resolve(callback(messageParser.convertTyped(data, this._client), response))
|
|
115
115
|
} catch (err) {
|
|
116
116
|
promise = Promise.reject(err)
|
|
117
117
|
}
|
|
@@ -153,10 +153,10 @@ 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
|
-
rpc.callback(null, convertTyped(data, this._client))
|
|
159
|
+
rpc.callback(null, messageParser.convertTyped(data, this._client))
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
}
|
|
@@ -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'
|
|
@@ -175,13 +177,8 @@ export function removeAbortListener(signal, handler) {
|
|
|
175
177
|
}
|
|
176
178
|
}
|
|
177
179
|
|
|
178
|
-
|
|
179
|
-
const first = buf[offset]
|
|
180
|
-
const last = buf[offset + 7]
|
|
181
|
-
|
|
182
|
-
const hi = first * 2 ** 24 + buf[++offset] * 2 ** 16 + buf[++offset] * 2 ** 8 + buf[++offset]
|
|
183
|
-
|
|
184
|
-
const lo = buf[++offset] * 2 ** 24 + buf[++offset] * 2 ** 16 + buf[++offset] * 2 ** 8 + last
|
|
180
|
+
const HASHER = await xxhash()
|
|
185
181
|
|
|
186
|
-
|
|
182
|
+
export function h64ToString(str) {
|
|
183
|
+
return HASHER.h64ToString(str)
|
|
187
184
|
}
|