@camera.ui/rpc 1.0.3 → 1.0.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.
Files changed (32) hide show
  1. package/externals/nats.js/core/src/authenticator.ts +159 -0
  2. package/externals/nats.js/core/src/bench.ts +426 -0
  3. package/externals/nats.js/core/src/codec.ts +28 -0
  4. package/externals/nats.js/core/src/core.ts +1219 -0
  5. package/externals/nats.js/core/src/databuffer.ts +129 -0
  6. package/externals/nats.js/core/src/denobuffer.ts +248 -0
  7. package/externals/nats.js/core/src/encoders.ts +53 -0
  8. package/externals/nats.js/core/src/errors.ts +300 -0
  9. package/externals/nats.js/core/src/headers.ts +315 -0
  10. package/externals/nats.js/core/src/heartbeats.ts +114 -0
  11. package/externals/nats.js/core/src/idleheartbeat_monitor.ts +140 -0
  12. package/externals/nats.js/core/src/internal_mod.ts +167 -0
  13. package/externals/nats.js/core/src/ipparser.ts +215 -0
  14. package/externals/nats.js/core/src/mod.ts +113 -0
  15. package/externals/nats.js/core/src/msg.ts +120 -0
  16. package/externals/nats.js/core/src/muxsubscription.ts +111 -0
  17. package/externals/nats.js/core/src/nats.ts +650 -0
  18. package/externals/nats.js/core/src/nkeys.ts +1 -0
  19. package/externals/nats.js/core/src/nuid.ts +16 -0
  20. package/externals/nats.js/core/src/options.ts +202 -0
  21. package/externals/nats.js/core/src/parser.ts +756 -0
  22. package/externals/nats.js/core/src/protocol.ts +1304 -0
  23. package/externals/nats.js/core/src/queued_iterator.ts +171 -0
  24. package/externals/nats.js/core/src/request.ts +177 -0
  25. package/externals/nats.js/core/src/semver.ts +165 -0
  26. package/externals/nats.js/core/src/servers.ts +424 -0
  27. package/externals/nats.js/core/src/transport.ts +117 -0
  28. package/externals/nats.js/core/src/types.ts +17 -0
  29. package/externals/nats.js/core/src/util.ts +367 -0
  30. package/externals/nats.js/core/src/version.ts +2 -0
  31. package/externals/nats.js/core/src/ws_transport.ts +391 -0
  32. package/package.json +2 -1
@@ -0,0 +1,424 @@
1
+ /*
2
+ * Copyright 2018-2024 The NATS Authors
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License.
5
+ * You may obtain a copy of the License at
6
+ *
7
+ * http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software
10
+ * distributed under the License is distributed on an "AS IS" BASIS,
11
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ * See the License for the specific language governing permissions and
13
+ * limitations under the License.
14
+ */
15
+ import { getUrlParseFn } from "./transport.ts";
16
+ import { shuffle } from "./util.ts";
17
+ import { isIP } from "./ipparser.ts";
18
+ import type {
19
+ DnsResolveFn,
20
+ Server,
21
+ ServerInfo,
22
+ ServersChanged,
23
+ } from "./core.ts";
24
+ import { DEFAULT_PORT } from "./core.ts";
25
+ import { InvalidArgumentError } from "./errors.ts";
26
+
27
+ export function isIPV4OrHostname(hp: string): boolean {
28
+ // in the wild seeing IPv4s as IPv6s
29
+ // ::ffff:35.234.43.228 which incorrectly get mapped to IPv4 unless
30
+ // we add this test first
31
+ if (hp.indexOf("[") !== -1 || hp.indexOf("::") !== -1) {
32
+ return false;
33
+ }
34
+ if (hp.indexOf(".") !== -1) {
35
+ return true;
36
+ }
37
+ // if we have a plain hostname or host:port
38
+ if (hp.split(":").length <= 2) {
39
+ return true;
40
+ }
41
+ return false;
42
+ }
43
+
44
+ function isIPV6(hp: string) {
45
+ return !isIPV4OrHostname(hp);
46
+ }
47
+
48
+ function filterIpv6MappedToIpv4(hp: string): string {
49
+ const prefix = "::FFFF:";
50
+ const idx = hp.toUpperCase().indexOf(prefix);
51
+ if (idx !== -1 && hp.indexOf(".") !== -1) {
52
+ // we have something like: ::FFFF:127.0.0.1 or [::FFFF:127.0.0.1]:4222
53
+ let ip = hp.substring(idx + prefix.length);
54
+ ip = ip.replace("[", "");
55
+ return ip.replace("]", "");
56
+ }
57
+
58
+ return hp;
59
+ }
60
+
61
+ export function hostPort(
62
+ u: string,
63
+ ): { listen: string; hostname: string; port: number } {
64
+ u = u.trim();
65
+ // remove any protocol that may have been provided
66
+ if (u.match(/^(.*:\/\/)(.*)/m)) {
67
+ u = u.replace(/^(.*:\/\/)(.*)/gm, "$2");
68
+ }
69
+ // in web environments, URL may not be a living standard
70
+ // that means that protocols other than HTTP/S are not
71
+ // parsable correctly.
72
+
73
+ // the third complication is that we may have been given
74
+ // an IPv6 or worse IPv6 mapping an Ipv4
75
+ u = filterIpv6MappedToIpv4(u);
76
+
77
+ // we only wrap cases where they gave us a plain ipv6
78
+ // and we are not already bracketed
79
+ if (isIPV6(u) && u.indexOf("[") === -1) {
80
+ u = `[${u}]`;
81
+ }
82
+ // if we have ipv6, we expect port after ']:' otherwise after ':'
83
+ const op = isIPV6(u) ? u.match(/(]:)(\d+)/) : u.match(/(:)(\d+)/);
84
+ const port = op && op.length === 3 && op[1] && op[2]
85
+ ? parseInt(op[2])
86
+ : DEFAULT_PORT;
87
+
88
+ // the next complication is that new URL() may
89
+ // eat ports which match the protocol - so for example
90
+ // port 80 may be eliminated - so we flip the protocol
91
+ // so that it always yields a value
92
+ const protocol = port === 80 ? "https" : "http";
93
+ const url = new URL(`${protocol}://${u}`);
94
+ url.port = `${port}`;
95
+
96
+ let hostname = url.hostname;
97
+ // if we are bracketed, we need to rip it out
98
+ if (hostname.charAt(0) === "[") {
99
+ hostname = hostname.substring(1, hostname.length - 1);
100
+ }
101
+ const listen = url.host;
102
+
103
+ return { listen, hostname, port };
104
+ }
105
+
106
+ /**
107
+ * @hidden
108
+ */
109
+ export class ServerImpl implements Server {
110
+ src: string;
111
+ listen: string;
112
+ hostname: string;
113
+ port: number;
114
+ didConnect: boolean;
115
+ reconnects: number;
116
+ lastConnect: number;
117
+ gossiped: boolean;
118
+ tlsName: string;
119
+ resolves?: Server[];
120
+
121
+ constructor(u: string, gossiped = false) {
122
+ this.src = u;
123
+ this.tlsName = "";
124
+ const v = hostPort(u);
125
+ this.listen = v.listen;
126
+ this.hostname = v.hostname;
127
+ this.port = v.port;
128
+ this.didConnect = false;
129
+ this.reconnects = 0;
130
+ this.lastConnect = 0;
131
+ this.gossiped = gossiped;
132
+ }
133
+
134
+ toString(): string {
135
+ return this.listen;
136
+ }
137
+
138
+ async resolve(
139
+ opts: Partial<
140
+ {
141
+ fn: DnsResolveFn;
142
+ randomize: boolean;
143
+ resolve: boolean;
144
+ debug: boolean;
145
+ }
146
+ >,
147
+ ): Promise<Server[]> {
148
+ if (!opts.fn || opts.resolve === false) {
149
+ // we cannot resolve - transport doesn't support it
150
+ // or user opted out
151
+ // don't add - to resolves or we get a circ reference
152
+ return [this];
153
+ }
154
+
155
+ const buf: Server[] = [];
156
+ if (isIP(this.hostname)) {
157
+ // don't add - to resolves or we get a circ reference
158
+ return [this];
159
+ } else {
160
+ // resolve the hostname to ips
161
+ const ips = await opts.fn(this.hostname);
162
+ if (opts.debug) {
163
+ console.log(`resolve ${this.hostname} = ${ips.join(",")}`);
164
+ }
165
+
166
+ for (const ip of ips) {
167
+ // letting URL handle the details of representing IPV6 ip with a port, etc
168
+ // careful to make sure the protocol doesn't line with standard ports or they
169
+ // get swallowed
170
+ const proto = this.port === 80 ? "https" : "http";
171
+ // ipv6 won't be bracketed here, because it came from resolve
172
+ const url = new URL(`${proto}://${isIPV6(ip) ? "[" + ip + "]" : ip}`);
173
+ url.port = `${this.port}`;
174
+ const ss = new ServerImpl(url.host, false);
175
+ ss.tlsName = this.hostname;
176
+ buf.push(ss);
177
+ }
178
+ }
179
+ if (opts.randomize) {
180
+ shuffle(buf);
181
+ }
182
+ this.resolves = buf;
183
+ return buf;
184
+ }
185
+ }
186
+
187
+ /**
188
+ * @hidden
189
+ */
190
+ export class Servers {
191
+ private firstSelect: boolean;
192
+ private servers: ServerImpl[];
193
+ private currentServer!: ServerImpl;
194
+ private tlsName: string;
195
+ private randomize: boolean;
196
+
197
+ constructor(opts: Partial<{ randomize: boolean }> = {}) {
198
+ this.firstSelect = true;
199
+ this.servers = [];
200
+ this.tlsName = "";
201
+ this.randomize = opts.randomize || false;
202
+ }
203
+
204
+ /**
205
+ * Replace the server pool with the provided list of `host:port` entries.
206
+ *
207
+ * Throws `InvalidArgumentError` if `listens` is empty or not an array.
208
+ *
209
+ * Note: reconnect attempts continue to follow the configured reconnect
210
+ * policy, but if every entry in the new pool is unreachable the
211
+ * connection may be left unable to recover.
212
+ */
213
+ setServers(listens: string[]): void {
214
+ if (!Array.isArray(listens) || listens.length === 0) {
215
+ throw InvalidArgumentError.format("servers", "cannot be empty");
216
+ }
217
+ const urlParseFn = getUrlParseFn();
218
+ const existing = new Map<string, ServerImpl>();
219
+ for (const s of this.servers) existing.set(s.listen, s);
220
+
221
+ const merged: ServerImpl[] = [];
222
+ for (let hp of listens) {
223
+ hp = urlParseFn ? urlParseFn(hp) : hp;
224
+ const { listen } = hostPort(hp);
225
+ const surviving = existing.get(listen);
226
+ if (surviving) {
227
+ // user-supplied = not gossiped. otherwise a later cluster update()
228
+ // could remove an entry the user explicitly asked for.
229
+ surviving.gossiped = false;
230
+ // camera.ui fork patch.
231
+ // `existing.get(listen)` keys only on `host:port`, so a re-issued
232
+ // URL that differs only in its query string (e.g., a rotated auth
233
+ // token in `?token=…`) would be silently dropped — the recycled
234
+ // ServerImpl still carries the OLD `src`, and the next reconnect
235
+ // would dial that stale URL. Update `src` in place so the new query
236
+ // is what gets used by the WS transport on the next dial.
237
+ if (surviving.src !== hp) {
238
+ surviving.src = hp;
239
+ }
240
+ merged.push(surviving);
241
+ } else {
242
+ merged.push(new ServerImpl(hp));
243
+ }
244
+ }
245
+ if (this.randomize) shuffle(merged);
246
+
247
+ this.servers = merged;
248
+ if (
249
+ this.currentServer === undefined ||
250
+ !merged.includes(this.currentServer)
251
+ ) {
252
+ this.currentServer = merged[0];
253
+ this.firstSelect = true;
254
+ }
255
+ }
256
+
257
+ clear(): void {
258
+ this.servers.length = 0;
259
+ }
260
+
261
+ updateTLSName(): void {
262
+ const cs = this.getCurrentServer();
263
+ if (!isIP(cs.hostname)) {
264
+ this.tlsName = cs.hostname;
265
+ this.servers.forEach((s) => {
266
+ if (s.gossiped) {
267
+ s.tlsName = this.tlsName;
268
+ }
269
+ });
270
+ }
271
+ }
272
+
273
+ getCurrentServer(): ServerImpl {
274
+ return this.currentServer;
275
+ }
276
+
277
+ addServer(u: string, implicit = false): void {
278
+ const urlParseFn = getUrlParseFn();
279
+ u = urlParseFn ? urlParseFn(u) : u;
280
+ const s = new ServerImpl(u, implicit);
281
+ if (isIP(s.hostname)) {
282
+ s.tlsName = this.tlsName;
283
+ }
284
+ this.servers.push(s);
285
+ }
286
+
287
+ selectServer(): ServerImpl | undefined {
288
+ // allow using select without breaking the order of the servers
289
+ if (this.firstSelect) {
290
+ this.firstSelect = false;
291
+ return this.currentServer;
292
+ }
293
+ const t = this.servers.shift();
294
+ if (t) {
295
+ this.servers.push(t);
296
+ this.currentServer = t;
297
+ }
298
+ return t;
299
+ }
300
+
301
+ removeCurrentServer(): void {
302
+ this.removeServer(this.currentServer);
303
+ }
304
+
305
+ removeServer(server: ServerImpl | undefined): void {
306
+ if (server) {
307
+ const index = this.servers.indexOf(server);
308
+ this.servers.splice(index, 1);
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Returns a frozen snapshot of the server pool in natural order.
314
+ * Each entry is a defensive copy — callers cannot mutate pool state.
315
+ */
316
+ snapshot(): ReadonlyArray<Server> {
317
+ return Servers.freezeAll(this.servers);
318
+ }
319
+
320
+ /**
321
+ * Returns a frozen snapshot of the server pool with the current server
322
+ * (= next dial candidate) at index 0. Used to present the handler with
323
+ * the server the library would have selected.
324
+ */
325
+ snapshotForHandler(): ReadonlyArray<Server> {
326
+ const cur = this.currentServer;
327
+ if (!cur) return this.snapshot();
328
+ const idx = this.servers.indexOf(cur);
329
+ if (idx <= 0) return this.snapshot();
330
+ return Servers.freezeAll(
331
+ [cur, ...this.servers.slice(0, idx), ...this.servers.slice(idx + 1)],
332
+ );
333
+ }
334
+
335
+ private static freezeAll(arr: ServerImpl[]): ReadonlyArray<Server> {
336
+ return arr.map((s) =>
337
+ Object.freeze<Server>({
338
+ hostname: s.hostname,
339
+ port: s.port,
340
+ listen: s.listen,
341
+ src: s.src,
342
+ tlsName: s.tlsName,
343
+ reconnects: s.reconnects,
344
+ lastConnect: s.lastConnect,
345
+ gossiped: s.gossiped,
346
+ didConnect: s.didConnect,
347
+ })
348
+ );
349
+ }
350
+
351
+ find(server: Server): ServerImpl | undefined {
352
+ return this.servers.find((s) => s.listen === server.listen);
353
+ }
354
+
355
+ setCurrent(server: ServerImpl): void {
356
+ this.currentServer = server;
357
+ }
358
+
359
+ length(): number {
360
+ return this.servers.length;
361
+ }
362
+
363
+ next(): ServerImpl | undefined {
364
+ return this.servers.length ? this.servers[0] : undefined;
365
+ }
366
+
367
+ getServers(): ServerImpl[] {
368
+ return this.servers;
369
+ }
370
+
371
+ update(info: ServerInfo, encrypted?: boolean): ServersChanged {
372
+ const added: string[] = [];
373
+ let deleted: string[] = [];
374
+
375
+ const urlParseFn = getUrlParseFn();
376
+ const discovered = new Map<string, ServerImpl>();
377
+ if (info.connect_urls && info.connect_urls.length > 0) {
378
+ info.connect_urls.forEach((hp) => {
379
+ hp = urlParseFn ? urlParseFn(hp, encrypted) : hp;
380
+ const s = new ServerImpl(hp, true);
381
+ discovered.set(hp, s);
382
+ });
383
+ }
384
+ // remove gossiped servers that are no longer reported
385
+ const toDelete: number[] = [];
386
+ this.servers.forEach((s, index) => {
387
+ const u = s.listen;
388
+ if (
389
+ s.gossiped && this.currentServer.listen !== u &&
390
+ discovered.get(u) === undefined
391
+ ) {
392
+ // server was removed
393
+ toDelete.push(index);
394
+ }
395
+ // remove this entry from reported
396
+ discovered.delete(u);
397
+ });
398
+
399
+ // perform the deletion
400
+ toDelete.reverse();
401
+ toDelete.forEach((index) => {
402
+ const removed = this.servers.splice(index, 1);
403
+ deleted = deleted.concat(removed[0].listen);
404
+ });
405
+
406
+ // remaining servers are new
407
+ discovered.forEach((v, k) => {
408
+ this.servers.push(v);
409
+ added.push(k);
410
+ });
411
+
412
+ // shuffle the pool so reconnect picks distribute across the cluster.
413
+ // currentServer is held at the head — the live connection isn't
414
+ // disturbed, but rotation order through the rest is randomized.
415
+ if (this.randomize && added.length > 0) {
416
+ const cur = this.currentServer;
417
+ const others = this.servers.filter((s) => s !== cur);
418
+ shuffle(others);
419
+ this.servers = cur ? [cur, ...others] : others;
420
+ }
421
+
422
+ return { added, deleted };
423
+ }
424
+ }
@@ -0,0 +1,117 @@
1
+ /*
2
+ * Copyright 2020-2024 The NATS Authors
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License.
5
+ * You may obtain a copy of the License at
6
+ *
7
+ * http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software
10
+ * distributed under the License is distributed on an "AS IS" BASIS,
11
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ * See the License for the specific language governing permissions and
13
+ * limitations under the License.
14
+ */
15
+ import { TD } from "./encoders.ts";
16
+
17
+ import type {
18
+ ConnectionOptions,
19
+ DnsResolveFn,
20
+ Server,
21
+ URLParseFn,
22
+ } from "./core.ts";
23
+
24
+ import { DEFAULT_PORT } from "./core.ts";
25
+ import { DataBuffer } from "./databuffer.ts";
26
+
27
+ let transportConfig: TransportFactory;
28
+ export function setTransportFactory(config: TransportFactory): void {
29
+ transportConfig = config;
30
+ }
31
+
32
+ export function defaultPort(): number {
33
+ return transportConfig !== undefined &&
34
+ transportConfig.defaultPort !== undefined
35
+ ? transportConfig.defaultPort
36
+ : DEFAULT_PORT;
37
+ }
38
+
39
+ export function getUrlParseFn(): URLParseFn | undefined {
40
+ return transportConfig !== undefined && transportConfig.urlParseFn
41
+ ? transportConfig.urlParseFn
42
+ : undefined;
43
+ }
44
+
45
+ export function newTransport(): Transport {
46
+ if (!transportConfig || typeof transportConfig.factory !== "function") {
47
+ throw new Error("transport fn is not set");
48
+ }
49
+ return transportConfig.factory();
50
+ }
51
+
52
+ export function getResolveFn(): DnsResolveFn | undefined {
53
+ return transportConfig !== undefined && transportConfig.dnsResolveFn
54
+ ? transportConfig.dnsResolveFn
55
+ : undefined;
56
+ }
57
+
58
+ export interface TransportFactory {
59
+ factory?: () => Transport;
60
+ defaultPort?: number;
61
+ urlParseFn?: URLParseFn;
62
+ dnsResolveFn?: DnsResolveFn;
63
+ }
64
+
65
+ export interface Transport extends AsyncIterable<Uint8Array> {
66
+ readonly isClosed: boolean;
67
+ readonly lang: string;
68
+ readonly version: string;
69
+ readonly closeError?: Error;
70
+
71
+ connect(
72
+ server: Server,
73
+ opts: ConnectionOptions,
74
+ ): Promise<void>;
75
+
76
+ [Symbol.asyncIterator](): AsyncIterableIterator<Uint8Array>;
77
+
78
+ isEncrypted(): boolean;
79
+
80
+ send(frame: Uint8Array): void;
81
+
82
+ close(err?: Error): Promise<void>;
83
+
84
+ disconnect(): void;
85
+
86
+ closed(): Promise<void | Error>;
87
+
88
+ // this is here for websocket implementations as some implementations
89
+ // (firefox) throttle connections that then resolve later
90
+ discard(): void;
91
+ }
92
+
93
+ export const CR_LF = "\r\n";
94
+ export const CR_LF_LEN = CR_LF.length;
95
+ export const CRLF = DataBuffer.fromAscii(CR_LF);
96
+ export const CR = new Uint8Array(CRLF)[0]; // 13
97
+ export const LF = new Uint8Array(CRLF)[1]; // 10
98
+ export function protoLen(ba: Uint8Array): number {
99
+ for (let i = 0; i < ba.length; i++) {
100
+ const n = i + 1;
101
+ if (ba.byteLength > n && ba[i] === CR && ba[n] === LF) {
102
+ return n + 1;
103
+ }
104
+ }
105
+ return 0;
106
+ }
107
+
108
+ export function extractProtocolMessage(a: Uint8Array): string {
109
+ // protocol messages are ascii, so Uint8Array
110
+ const len = protoLen(a);
111
+ if (len > 0) {
112
+ const ba = new Uint8Array(a);
113
+ const out = ba.slice(0, len);
114
+ return TD.decode(out);
115
+ }
116
+ return "";
117
+ }
@@ -0,0 +1,17 @@
1
+ /*
2
+ * Copyright 2020-2023 The NATS Authors
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License.
5
+ * You may obtain a copy of the License at
6
+ *
7
+ * http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software
10
+ * distributed under the License is distributed on an "AS IS" BASIS,
11
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ * See the License for the specific language governing permissions and
13
+ * limitations under the License.
14
+ */
15
+ export type { Dispatcher, MsgHdrs, QueuedIterator } from "./core.ts";
16
+
17
+ export { Empty } from "./encoders.ts";