geonix 1.23.8 → 1.30.4
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/LICENSE.md +1 -1
- package/README.md +348 -4
- package/exports.js +0 -2
- package/index.d.ts +292 -237
- package/package.json +12 -11
- package/src/Codec.js +21 -8
- package/src/Connection.js +164 -53
- package/src/Crypto.js +117 -0
- package/src/Gateway.js +172 -87
- package/src/Logger.js +101 -11
- package/src/Registry.js +136 -18
- package/src/Remote.js +21 -8
- package/src/Request.js +140 -87
- package/src/RequestOptions.js +11 -8
- package/src/Service.js +176 -113
- package/src/Stream.js +78 -18
- package/src/Util.js +229 -188
- package/src/WebServer.js +29 -22
- package/.claude/settings.local.json +0 -10
- package/.vscode/settings.json +0 -11
- package/PROJECT.md +0 -164
- package/REVIEW.md +0 -372
package/src/Connection.js
CHANGED
|
@@ -4,93 +4,166 @@ import { Stream } from "./Stream.js";
|
|
|
4
4
|
import { webserver } from "./WebServer.js";
|
|
5
5
|
import { logger } from "./Logger.js";
|
|
6
6
|
import { decode, encode } from "./Codec.js";
|
|
7
|
+
import { encryptPayload, decryptPayload, encryptSubject, wrapSubscription } from "./Crypto.js";
|
|
7
8
|
|
|
8
9
|
// -------------------------------------------------------------------------------------------------
|
|
9
10
|
const CONNECTION_TIMEOUT = 10000;
|
|
10
11
|
|
|
11
12
|
const defaultRequestOptions = {
|
|
12
|
-
timeout: 300000
|
|
13
|
+
timeout: 300000,
|
|
13
14
|
};
|
|
14
15
|
|
|
16
|
+
const DEFAULT_CONNECTION_COUNT = 1;
|
|
17
|
+
|
|
15
18
|
const defaultConnectionOptions = {
|
|
16
19
|
timeout: CONNECTION_TIMEOUT,
|
|
17
20
|
reconnect: true,
|
|
18
|
-
debug: process.env.TRANSPORT_DEBUG === "true",
|
|
21
|
+
debug: (process.env.GX_TRANSPORT_DEBUG || process.env.TRANSPORT_DEBUG) === "true",
|
|
19
22
|
maxReconnectAttempts: 30,
|
|
20
23
|
pingInterval: 30000,
|
|
21
24
|
waitOnFirstConnect: true,
|
|
22
|
-
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const TRANSPORT_TIMEOUT = 60_000;
|
|
28
|
+
const WATCHDOG_INTERVAL = 1000;
|
|
29
|
+
|
|
30
|
+
// env read via a function so tests can set GX_TRANSPORT_TIMEOUT after this module is imported
|
|
31
|
+
const getTransportTimeout = () => {
|
|
32
|
+
const raw = parseInt(process.env.GX_TRANSPORT_TIMEOUT, 10);
|
|
33
|
+
return Number.isFinite(raw) ? raw : TRANSPORT_TIMEOUT;
|
|
23
34
|
};
|
|
24
35
|
// -------------------------------------------------------------------------------------------------
|
|
25
36
|
|
|
26
37
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* process while using just a single connection to the NATS server.
|
|
38
|
+
* Manages one or more NATS connections and exposes publish, subscribe, and request helpers.
|
|
39
|
+
* Instantiated as a singleton (`connection`) so multiple services in the same process share a
|
|
40
|
+
* single pool of connections. Optional subject-level HMAC encryption and payload AES-GCM
|
|
41
|
+
* encryption are activated by setting the `GX_SECRET` environment variable.
|
|
32
42
|
*/
|
|
33
43
|
class Connection {
|
|
34
|
-
|
|
35
44
|
#draining = false;
|
|
36
45
|
#closed = false;
|
|
37
46
|
#ready = false;
|
|
38
47
|
#connections = [];
|
|
39
48
|
#connectionRoundRobin = 0;
|
|
49
|
+
#disconnectedSince = Date.now();
|
|
50
|
+
#watchdogInterval = null;
|
|
40
51
|
|
|
41
52
|
#getConnection() {
|
|
42
|
-
this.#
|
|
43
|
-
return this.#connections[this.#connectionRoundRobin];
|
|
53
|
+
return this.#connections[this.#connectionRoundRobin++ % this.#connections.length];
|
|
44
54
|
}
|
|
45
55
|
|
|
46
56
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
57
|
+
* Connects to the NATS transport. Called automatically on module load via the singleton.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} [transport] - NATS URL, optionally including credentials and query-string
|
|
60
|
+
* options (e.g. `nats://user:pass@host:4222?connections=2`). Defaults to `GX_TRANSPORT`,
|
|
61
|
+
* `TRANSPORT`, or `nats://localhost`.
|
|
62
|
+
* @returns {Promise<void>}
|
|
49
63
|
*/
|
|
50
|
-
async start(transport = process.env.TRANSPORT || "nats://localhost") {
|
|
51
|
-
const
|
|
64
|
+
async start(transport = process.env.GX_TRANSPORT || process.env.TRANSPORT || "nats://localhost") {
|
|
65
|
+
const { connections: _connectionCount, ...natsOptions } = {
|
|
52
66
|
...defaultConnectionOptions,
|
|
53
|
-
...parseURL(transport)
|
|
67
|
+
...parseURL(transport),
|
|
54
68
|
};
|
|
69
|
+
const connectionCount = parseInt(_connectionCount) || DEFAULT_CONNECTION_COUNT;
|
|
70
|
+
|
|
71
|
+
this.#startWatchdog();
|
|
55
72
|
|
|
56
|
-
for (let i = 0; i <
|
|
57
|
-
this.#connections.push(await connect(
|
|
73
|
+
for (let i = 0; i < connectionCount; i++) {
|
|
74
|
+
this.#connections.push(await connect(natsOptions));
|
|
58
75
|
}
|
|
59
76
|
|
|
77
|
+
this.#disconnectedSince = null;
|
|
78
|
+
|
|
60
79
|
logger.info("gx.connection.connected");
|
|
61
80
|
|
|
62
81
|
this.#ready = true;
|
|
63
82
|
|
|
64
83
|
this.monitorStatus();
|
|
65
|
-
this.waitUntilClosed();
|
|
84
|
+
this.waitUntilClosed().catch((e) => logger.error("gx.connection.waitUntilClosed:", e));
|
|
66
85
|
}
|
|
67
86
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
87
|
+
// Polls the disconnect timestamp; exits the process if the transport has been
|
|
88
|
+
// unreachable longer than GX_TRANSPORT_TIMEOUT (default 60s, 0 disables).
|
|
89
|
+
#startWatchdog() {
|
|
90
|
+
if (this.#watchdogInterval) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this.#watchdogInterval = setInterval(() => {
|
|
95
|
+
const limit = getTransportTimeout();
|
|
96
|
+
if (limit === 0) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (this.#disconnectedSince === null) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const elapsed = Date.now() - this.#disconnectedSince;
|
|
104
|
+
if (elapsed >= limit) {
|
|
105
|
+
logger.error(
|
|
106
|
+
`gx.connection: transport unreachable for ${Math.round(elapsed / 1000)}s (limit ${limit}ms), exiting`,
|
|
107
|
+
);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
}, WATCHDOG_INTERVAL);
|
|
111
|
+
|
|
112
|
+
this.#watchdogInterval.unref?.();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Starts background logging of NATS status events for all connections.
|
|
117
|
+
* @returns {void}
|
|
118
|
+
*/
|
|
119
|
+
monitorStatus() {
|
|
120
|
+
for (const conn of this.#connections) {
|
|
121
|
+
(async () => {
|
|
122
|
+
for await (const event of conn.status()) {
|
|
123
|
+
if (event.type === "disconnect") {
|
|
124
|
+
if (this.#disconnectedSince === null) {
|
|
125
|
+
this.#disconnectedSince = Date.now();
|
|
126
|
+
}
|
|
127
|
+
} else if (event.type === "reconnect") {
|
|
128
|
+
this.#disconnectedSince = null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
logger.debug("gx.connection.status", JSON.stringify(event));
|
|
132
|
+
}
|
|
133
|
+
})().catch((e) => logger.error("gx.connection.status:", e));
|
|
71
134
|
}
|
|
72
135
|
}
|
|
73
136
|
|
|
74
137
|
/**
|
|
75
|
-
*
|
|
138
|
+
* Resolves once all NATS connections have been fully closed, then stops the web server and
|
|
139
|
+
* exits the process (exit code 0 on graceful drain, 1 on unexpected closure).
|
|
140
|
+
*
|
|
141
|
+
* @returns {Promise<void>}
|
|
76
142
|
*/
|
|
77
143
|
async waitUntilClosed() {
|
|
78
144
|
// wait for all connections to be closed
|
|
79
|
-
await Promise.all(this.#connections.map(connection => connection.closed()));
|
|
145
|
+
await Promise.all(this.#connections.map((connection) => connection.closed()));
|
|
80
146
|
|
|
81
147
|
this.#closed = true;
|
|
82
148
|
logger.info("gx.connection.closed");
|
|
83
149
|
|
|
150
|
+
if (this.#watchdogInterval) {
|
|
151
|
+
clearInterval(this.#watchdogInterval);
|
|
152
|
+
this.#watchdogInterval = null;
|
|
153
|
+
}
|
|
154
|
+
|
|
84
155
|
webserver.stop();
|
|
85
156
|
|
|
86
157
|
await sleep(5000);
|
|
87
158
|
|
|
88
159
|
logger.info("gx.terminate");
|
|
89
|
-
process.exit(1);
|
|
160
|
+
process.exit(this.#draining ? 0 : 1);
|
|
90
161
|
}
|
|
91
162
|
|
|
92
163
|
/**
|
|
93
|
-
*
|
|
164
|
+
* Resolves once the NATS connection pool is ready to publish and subscribe.
|
|
165
|
+
*
|
|
166
|
+
* @returns {Promise<void>}
|
|
94
167
|
*/
|
|
95
168
|
async waitUntilReady() {
|
|
96
169
|
while (!this.#ready) {
|
|
@@ -100,9 +173,9 @@ class Connection {
|
|
|
100
173
|
|
|
101
174
|
/**
|
|
102
175
|
* Publish JSON
|
|
103
|
-
*
|
|
104
|
-
* @param {string} subject
|
|
105
|
-
* @param {object} json
|
|
176
|
+
*
|
|
177
|
+
* @param {string} subject
|
|
178
|
+
* @param {object} json
|
|
106
179
|
* @returns void
|
|
107
180
|
*/
|
|
108
181
|
async publish(subject, json) {
|
|
@@ -114,17 +187,17 @@ class Connection {
|
|
|
114
187
|
|
|
115
188
|
// if payload is too big, convert it to Stream
|
|
116
189
|
if (payload.length > this.getMaxPayloadSize()) {
|
|
117
|
-
payload = encode(Stream(
|
|
190
|
+
payload = encode(Stream(Buffer.from(payload)));
|
|
118
191
|
}
|
|
119
192
|
|
|
120
|
-
await this.#getConnection().publish(subject, payload);
|
|
193
|
+
await this.#getConnection().publish(encryptSubject(subject), encryptPayload(payload));
|
|
121
194
|
}
|
|
122
195
|
|
|
123
196
|
/**
|
|
124
197
|
* Publish RAW
|
|
125
|
-
*
|
|
126
|
-
* @param {string} subject
|
|
127
|
-
* @param {string | Buffer} data
|
|
198
|
+
*
|
|
199
|
+
* @param {string} subject
|
|
200
|
+
* @param {string | Buffer} data
|
|
128
201
|
* @returns void
|
|
129
202
|
*/
|
|
130
203
|
async publishRaw(subject, data) {
|
|
@@ -132,21 +205,24 @@ class Connection {
|
|
|
132
205
|
return;
|
|
133
206
|
}
|
|
134
207
|
|
|
135
|
-
await this.#getConnection().publish(
|
|
208
|
+
await this.#getConnection().publish(
|
|
209
|
+
encryptSubject(subject),
|
|
210
|
+
encryptPayload(data != null ? Buffer.from(data) : Buffer.alloc(0)),
|
|
211
|
+
);
|
|
136
212
|
}
|
|
137
213
|
|
|
138
214
|
/**
|
|
139
215
|
* Request/Reply pattern on top of pub/sub
|
|
140
|
-
*
|
|
141
|
-
* @param {string} subject
|
|
142
|
-
* @param {object} json
|
|
143
|
-
* @param {object} options
|
|
216
|
+
*
|
|
217
|
+
* @param {string} subject
|
|
218
|
+
* @param {object} json
|
|
219
|
+
* @param {object} options
|
|
144
220
|
* @returns any
|
|
145
221
|
*/
|
|
146
222
|
async request(subject, json, opts = {}) {
|
|
147
223
|
const options = {
|
|
148
224
|
...defaultRequestOptions,
|
|
149
|
-
...opts
|
|
225
|
+
...opts,
|
|
150
226
|
};
|
|
151
227
|
|
|
152
228
|
const respondTo = `gx2.r.${picoid(16)}`;
|
|
@@ -159,24 +235,40 @@ class Connection {
|
|
|
159
235
|
}
|
|
160
236
|
|
|
161
237
|
const nc = this.#getConnection();
|
|
162
|
-
|
|
238
|
+
const response = await nc.subscribe(encryptSubject(respondTo), { max: 1, ...options });
|
|
163
239
|
|
|
164
|
-
await nc.publish(subject, payload);
|
|
240
|
+
await nc.publish(encryptSubject(subject), encryptPayload(payload));
|
|
165
241
|
|
|
166
242
|
const event = await getFirstItemFromAsyncIterable(response);
|
|
167
|
-
return decode(event.data);
|
|
243
|
+
return decode(decryptPayload(event.data));
|
|
168
244
|
}
|
|
169
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Subscribes to a NATS subject and returns an async-iterable subscription. If payload
|
|
248
|
+
* encryption is enabled each message is transparently decrypted before being yielded.
|
|
249
|
+
*
|
|
250
|
+
* @param {string} subject - NATS subject to subscribe to.
|
|
251
|
+
* @param {object} [options] - NATS subscription options (e.g. `queue`, `max`).
|
|
252
|
+
* @returns {Promise<AsyncIterable>} Subscription iterable.
|
|
253
|
+
*/
|
|
170
254
|
async subscribe(subject, options) {
|
|
171
|
-
return this.#getConnection().subscribe(subject, options);
|
|
255
|
+
return wrapSubscription(await this.#getConnection().subscribe(encryptSubject(subject), options));
|
|
172
256
|
}
|
|
173
257
|
|
|
174
|
-
|
|
258
|
+
/**
|
|
259
|
+
* Cancels an active subscription.
|
|
260
|
+
*
|
|
261
|
+
* @param {AsyncIterable} subscription - Subscription returned by {@link subscribe}.
|
|
262
|
+
* @returns {void}
|
|
263
|
+
*/
|
|
264
|
+
unsubscribe(subscription) {
|
|
175
265
|
subscription.unsubscribe();
|
|
176
266
|
}
|
|
177
267
|
|
|
178
268
|
/**
|
|
179
|
-
*
|
|
269
|
+
* Returns the maximum NATS message payload size in bytes as reported by the server.
|
|
270
|
+
* Falls back to 512 KB if the connection info is not yet available.
|
|
271
|
+
*
|
|
180
272
|
* @returns {number}
|
|
181
273
|
*/
|
|
182
274
|
getMaxPayloadSize() {
|
|
@@ -184,25 +276,44 @@ class Connection {
|
|
|
184
276
|
return nc?.info?.max_payload || 1024 * 512;
|
|
185
277
|
}
|
|
186
278
|
|
|
279
|
+
/**
|
|
280
|
+
* Returns `true` after all NATS connections have been fully closed.
|
|
281
|
+
*
|
|
282
|
+
* @returns {boolean}
|
|
283
|
+
*/
|
|
187
284
|
isClosed() {
|
|
188
285
|
return this.#closed;
|
|
189
286
|
}
|
|
190
287
|
|
|
288
|
+
/**
|
|
289
|
+
* Drains all active NATS connections, flushing pending messages before closing them.
|
|
290
|
+
*
|
|
291
|
+
* @returns {Promise<void>}
|
|
292
|
+
*/
|
|
191
293
|
async drain() {
|
|
192
294
|
this.#draining = true;
|
|
193
295
|
|
|
194
|
-
await Promise.all(this.#connections.map(connection => connection.drain()));
|
|
296
|
+
await Promise.all(this.#connections.map((connection) => connection.drain()));
|
|
195
297
|
}
|
|
196
|
-
|
|
197
298
|
}
|
|
198
299
|
|
|
300
|
+
/**
|
|
301
|
+
* Singleton {@link Connection} instance shared across the process. Started automatically on
|
|
302
|
+
* module load; callers should use `connection.waitUntilReady()` before publishing or subscribing.
|
|
303
|
+
*
|
|
304
|
+
* @type {Connection}
|
|
305
|
+
*/
|
|
199
306
|
export const connection = new Connection();
|
|
200
|
-
connection.start()
|
|
307
|
+
connection.start().catch((e) => {
|
|
308
|
+
logger.error("gx.connection.start:", e);
|
|
309
|
+
process.exit(1);
|
|
310
|
+
});
|
|
201
311
|
|
|
312
|
+
/**
|
|
313
|
+
* Initiates a graceful shutdown by draining all NATS connections.
|
|
314
|
+
*
|
|
315
|
+
* @returns {void}
|
|
316
|
+
*/
|
|
202
317
|
export const stopConnection = () => {
|
|
203
|
-
if (!connection) {
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
318
|
connection.drain();
|
|
208
319
|
};
|
package/src/Crypto.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes } from "crypto";
|
|
2
|
+
|
|
3
|
+
const _secret = process.env.GX_SECRET || null;
|
|
4
|
+
|
|
5
|
+
// Subject key is derived separately so HMAC-subject and AES-payload never share key material.
|
|
6
|
+
const _subjectKey = _secret
|
|
7
|
+
? createHash("sha256")
|
|
8
|
+
.update(_secret + "\x00subject")
|
|
9
|
+
.digest()
|
|
10
|
+
: null;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* AES-256-GCM key derived from `GX_SECRET`, or `null` when encryption is disabled.
|
|
14
|
+
* Used internally by {@link encryptPayload} and {@link decryptPayload}.
|
|
15
|
+
*
|
|
16
|
+
* @type {Buffer|null}
|
|
17
|
+
*/
|
|
18
|
+
export const _payloadKey = _secret
|
|
19
|
+
? createHash("sha256")
|
|
20
|
+
.update(_secret + "\x00payload")
|
|
21
|
+
.digest()
|
|
22
|
+
: null;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Encrypts `data` with AES-256-GCM using {@link _payloadKey}. Returns `data` unchanged when
|
|
26
|
+
* encryption is disabled. The output format is `[12-byte IV][16-byte auth tag][ciphertext]`.
|
|
27
|
+
*
|
|
28
|
+
* @param {Buffer|null} data - Plaintext bytes to encrypt.
|
|
29
|
+
* @returns {Buffer}
|
|
30
|
+
*/
|
|
31
|
+
export function encryptPayload(data) {
|
|
32
|
+
if (!_payloadKey) {
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
const buf = data != null ? Buffer.from(data) : Buffer.alloc(0);
|
|
36
|
+
const iv = randomBytes(12);
|
|
37
|
+
const cipher = createCipheriv("aes-256-gcm", _payloadKey, iv);
|
|
38
|
+
const encrypted = cipher.update(buf);
|
|
39
|
+
cipher.final();
|
|
40
|
+
const out = Buffer.allocUnsafe(12 + 16 + encrypted.length);
|
|
41
|
+
iv.copy(out, 0);
|
|
42
|
+
cipher.getAuthTag().copy(out, 12);
|
|
43
|
+
encrypted.copy(out, 28);
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Decrypts a buffer produced by {@link encryptPayload}. Returns `data` unchanged when
|
|
49
|
+
* encryption is disabled. Expects the layout `[12-byte IV][16-byte auth tag][ciphertext]`.
|
|
50
|
+
*
|
|
51
|
+
* @param {Buffer} data - Encrypted bytes to decrypt.
|
|
52
|
+
* @returns {Buffer}
|
|
53
|
+
*/
|
|
54
|
+
export function decryptPayload(data) {
|
|
55
|
+
if (!_payloadKey) {
|
|
56
|
+
return data;
|
|
57
|
+
}
|
|
58
|
+
const buf = Buffer.from(data);
|
|
59
|
+
const decipher = createDecipheriv("aes-256-gcm", _payloadKey, buf.subarray(0, 12));
|
|
60
|
+
decipher.setAuthTag(buf.subarray(12, 28));
|
|
61
|
+
const decrypted = decipher.update(buf.subarray(28));
|
|
62
|
+
decipher.final();
|
|
63
|
+
return decrypted;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Encrypts each dot-separated segment of a NATS subject using HMAC-SHA256, truncated to 32 hex
|
|
68
|
+
* characters. Wildcard tokens (`*`, `>`) are passed through unchanged so NATS subscription
|
|
69
|
+
* semantics are preserved. Returns the subject unchanged when encryption is disabled.
|
|
70
|
+
*
|
|
71
|
+
* @param {string} subject - Plain NATS subject string (e.g. `"gx2.service.abc"`).
|
|
72
|
+
* @returns {string} Encrypted subject string.
|
|
73
|
+
*/
|
|
74
|
+
export function encryptSubject(subject) {
|
|
75
|
+
if (!_subjectKey) {
|
|
76
|
+
return subject;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return subject
|
|
80
|
+
.split(".")
|
|
81
|
+
.map((seg) =>
|
|
82
|
+
seg === "*" || seg === ">" ? seg : createHmac("sha256", _subjectKey).update(seg).digest("hex").slice(0, 32),
|
|
83
|
+
)
|
|
84
|
+
.join(".");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Wraps a NATS subscription so that every incoming message's `data` field is transparently
|
|
89
|
+
* decrypted before being yielded. Returns the subscription unchanged when encryption is
|
|
90
|
+
* disabled.
|
|
91
|
+
*
|
|
92
|
+
* @param {object} sub - NATS subscription object with `[Symbol.asyncIterator]`, `drain`, and `unsubscribe`.
|
|
93
|
+
* @returns {object} Wrapped subscription with the same interface.
|
|
94
|
+
*/
|
|
95
|
+
export function wrapSubscription(sub) {
|
|
96
|
+
if (!_payloadKey) {
|
|
97
|
+
return sub;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
[Symbol.asyncIterator]() {
|
|
102
|
+
const iter = sub[Symbol.asyncIterator]();
|
|
103
|
+
return {
|
|
104
|
+
async next() {
|
|
105
|
+
const { value, done } = await iter.next();
|
|
106
|
+
if (done) {
|
|
107
|
+
return { value, done };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return { value: { ...value, data: decryptPayload(value.data) }, done: false };
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
drain: () => sub.drain(),
|
|
115
|
+
unsubscribe: () => sub.unsubscribe(),
|
|
116
|
+
};
|
|
117
|
+
}
|