@cratis/chronicle 1.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/ChronicleClient.ts +69 -60
  2. package/ChronicleOptions.ts +5 -6
  3. package/README.md +1 -1
  4. package/connection/ChronicleConnection.ts +37 -17
  5. package/connection/ChronicleConnectionString.spec.ts +227 -0
  6. package/connection/ChronicleConnectionString.ts +289 -35
  7. package/connection/ChronicleServerAddressResolver.spec.ts +41 -0
  8. package/connection/ChronicleServerAddressResolver.ts +33 -0
  9. package/connection/ChronicleSrvResolutionError.ts +13 -0
  10. package/connection/ChronicleSrvResolver.spec.ts +103 -0
  11. package/connection/ChronicleSrvResolver.ts +57 -0
  12. package/connection/ConnectionLifecycle.spec.ts +109 -0
  13. package/connection/ILoadBalancerStrategy.ts +16 -0
  14. package/connection/KernelKeepAlive.spec.ts +175 -0
  15. package/connection/KernelKeepAlive.ts +161 -0
  16. package/connection/LeastConnectionsLoadBalancerStrategy.spec.ts +159 -0
  17. package/connection/LeastConnectionsLoadBalancerStrategy.ts +117 -0
  18. package/connection/LoadBalancerMode.ts +25 -0
  19. package/connection/LoadBalancerStrategyFactory.ts +28 -0
  20. package/connection/RandomLoadBalancerStrategy.spec.ts +50 -0
  21. package/connection/RandomLoadBalancerStrategy.ts +19 -0
  22. package/connection/RoundRobinLoadBalancerStrategy.spec.ts +61 -0
  23. package/connection/RoundRobinLoadBalancerStrategy.ts +29 -0
  24. package/connection/TokenProvider.ts +8 -3
  25. package/connection/formatServerAddress.ts +16 -0
  26. package/dist/ChronicleClient.d.ts +8 -1
  27. package/dist/ChronicleClient.d.ts.map +1 -1
  28. package/dist/ChronicleClient.js +63 -56
  29. package/dist/ChronicleClient.js.map +1 -1
  30. package/dist/ChronicleOptions.d.ts +4 -2
  31. package/dist/ChronicleOptions.d.ts.map +1 -1
  32. package/dist/ChronicleOptions.js +5 -3
  33. package/dist/ChronicleOptions.js.map +1 -1
  34. package/dist/connection/ChronicleConnection.d.ts +4 -5
  35. package/dist/connection/ChronicleConnection.d.ts.map +1 -1
  36. package/dist/connection/ChronicleConnection.js +34 -12
  37. package/dist/connection/ChronicleConnection.js.map +1 -1
  38. package/dist/connection/ChronicleConnectionString.d.ts +77 -3
  39. package/dist/connection/ChronicleConnectionString.d.ts.map +1 -1
  40. package/dist/connection/ChronicleConnectionString.js +250 -32
  41. package/dist/connection/ChronicleConnectionString.js.map +1 -1
  42. package/dist/connection/ChronicleServerAddressResolver.d.ts +23 -0
  43. package/dist/connection/ChronicleServerAddressResolver.d.ts.map +1 -0
  44. package/dist/connection/ChronicleServerAddressResolver.js +32 -0
  45. package/dist/connection/ChronicleServerAddressResolver.js.map +1 -0
  46. package/dist/connection/ChronicleSrvResolutionError.d.ts +8 -0
  47. package/dist/connection/ChronicleSrvResolutionError.d.ts.map +1 -0
  48. package/dist/connection/ChronicleSrvResolutionError.js +13 -0
  49. package/dist/connection/ChronicleSrvResolutionError.js.map +1 -0
  50. package/dist/connection/ChronicleSrvResolver.d.ts +28 -0
  51. package/dist/connection/ChronicleSrvResolver.d.ts.map +1 -0
  52. package/dist/connection/ChronicleSrvResolver.js +51 -0
  53. package/dist/connection/ChronicleSrvResolver.js.map +1 -0
  54. package/dist/connection/ILoadBalancerStrategy.d.ts +13 -0
  55. package/dist/connection/ILoadBalancerStrategy.d.ts.map +1 -0
  56. package/dist/connection/ILoadBalancerStrategy.js +4 -0
  57. package/dist/connection/ILoadBalancerStrategy.js.map +1 -0
  58. package/dist/connection/KernelKeepAlive.d.ts +73 -0
  59. package/dist/connection/KernelKeepAlive.d.ts.map +1 -0
  60. package/dist/connection/KernelKeepAlive.js +115 -0
  61. package/dist/connection/KernelKeepAlive.js.map +1 -0
  62. package/dist/connection/LeastConnectionsLoadBalancerStrategy.d.ts +30 -0
  63. package/dist/connection/LeastConnectionsLoadBalancerStrategy.d.ts.map +1 -0
  64. package/dist/connection/LeastConnectionsLoadBalancerStrategy.js +95 -0
  65. package/dist/connection/LeastConnectionsLoadBalancerStrategy.js.map +1 -0
  66. package/dist/connection/LoadBalancerMode.d.ts +21 -0
  67. package/dist/connection/LoadBalancerMode.d.ts.map +1 -0
  68. package/dist/connection/LoadBalancerMode.js +24 -0
  69. package/dist/connection/LoadBalancerMode.js.map +1 -0
  70. package/dist/connection/LoadBalancerStrategyFactory.d.ts +11 -0
  71. package/dist/connection/LoadBalancerStrategyFactory.d.ts.map +1 -0
  72. package/dist/connection/LoadBalancerStrategyFactory.js +26 -0
  73. package/dist/connection/LoadBalancerStrategyFactory.js.map +1 -0
  74. package/dist/connection/RandomLoadBalancerStrategy.d.ts +9 -0
  75. package/dist/connection/RandomLoadBalancerStrategy.d.ts.map +1 -0
  76. package/dist/connection/RandomLoadBalancerStrategy.js +15 -0
  77. package/dist/connection/RandomLoadBalancerStrategy.js.map +1 -0
  78. package/dist/connection/RoundRobinLoadBalancerStrategy.d.ts +12 -0
  79. package/dist/connection/RoundRobinLoadBalancerStrategy.d.ts.map +1 -0
  80. package/dist/connection/RoundRobinLoadBalancerStrategy.js +22 -0
  81. package/dist/connection/RoundRobinLoadBalancerStrategy.js.map +1 -0
  82. package/dist/connection/TokenProvider.d.ts +2 -1
  83. package/dist/connection/TokenProvider.d.ts.map +1 -1
  84. package/dist/connection/TokenProvider.js +9 -3
  85. package/dist/connection/TokenProvider.js.map +1 -1
  86. package/dist/connection/formatServerAddress.d.ts +10 -0
  87. package/dist/connection/formatServerAddress.d.ts.map +1 -0
  88. package/dist/connection/formatServerAddress.js +14 -0
  89. package/dist/connection/formatServerAddress.js.map +1 -0
  90. package/dist/reactors/Reactors.d.ts +13 -0
  91. package/dist/reactors/Reactors.d.ts.map +1 -1
  92. package/dist/reactors/Reactors.js +42 -4
  93. package/dist/reactors/Reactors.js.map +1 -1
  94. package/dist/reducers/Reducers.d.ts +14 -0
  95. package/dist/reducers/Reducers.d.ts.map +1 -1
  96. package/dist/reducers/Reducers.js +43 -8
  97. package/dist/reducers/Reducers.js.map +1 -1
  98. package/dist/tsconfig.tsbuildinfo +1 -1
  99. package/package.json +8 -5
  100. package/reactors/Reactors.ts +53 -4
  101. package/reducers/Reducers.ts +55 -8
  102. package/vitest.config.ts +12 -0
@@ -0,0 +1,13 @@
1
+ import type { ChronicleServerAddress } from './ChronicleConnectionString';
2
+ /**
3
+ * Selects one server address from a list of candidates for a connect/reconnect attempt.
4
+ */
5
+ export interface ILoadBalancerStrategy {
6
+ /**
7
+ * Selects one address from the given candidates.
8
+ * @param candidates - The addresses to select from. Never empty.
9
+ * @returns A promise resolving to the selected address.
10
+ */
11
+ select(candidates: ChronicleServerAddress[]): Promise<ChronicleServerAddress>;
12
+ }
13
+ //# sourceMappingURL=ILoadBalancerStrategy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ILoadBalancerStrategy.d.ts","sourceRoot":"","sources":["../../connection/ILoadBalancerStrategy.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAE1E;;GAEG;AACH,MAAM,WAAW,qBAAqB;IAClC;;;;OAIG;IACH,MAAM,CAAC,UAAU,EAAE,sBAAsB,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;CACjF"}
@@ -0,0 +1,4 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+ export {};
4
+ //# sourceMappingURL=ILoadBalancerStrategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ILoadBalancerStrategy.js","sourceRoot":"","sources":["../../connection/ILoadBalancerStrategy.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,qGAAqG"}
@@ -0,0 +1,73 @@
1
+ import type { ConnectRequest } from '@cratis/chronicle.contracts';
2
+ /**
3
+ * Overrides for how long {@link KernelKeepAlive} waits before giving up on the kernel.
4
+ */
5
+ export interface KeepAliveTimeouts {
6
+ /** How long to wait for a keep-alive before treating the connection as dead. */
7
+ idleTimeoutMs?: number;
8
+ /** How long a single answer may take before the connection is considered dead. */
9
+ answerTimeoutMs?: number;
10
+ }
11
+ /**
12
+ * The subset of the connection service {@link KernelKeepAlive} needs, so it can be
13
+ * exercised without a live kernel.
14
+ */
15
+ export interface IKeepAliveConnections {
16
+ connect(request: ConnectRequest, options?: {
17
+ signal?: AbortSignal;
18
+ }): AsyncIterable<unknown>;
19
+ connectionKeepAlive(request: object, options?: {
20
+ signal?: AbortSignal;
21
+ }): Promise<unknown>;
22
+ }
23
+ /**
24
+ * Drives the client half of Chronicle's keep-alive ping-pong.
25
+ *
26
+ * The kernel pushes a `ConnectionKeepAlive` down the `Connect` server stream once
27
+ * per second, and for each one the client must call the separate unary
28
+ * `ConnectionKeepAlive` RPC back — that is what bumps `LastSeen` on the kernel and
29
+ * keeps the client registered. A client that stops answering is evicted, its
30
+ * observers are unsubscribed, and reactors and reducers go silent while appends
31
+ * keep working.
32
+ *
33
+ * Eviction does not close the `Connect` stream, so a dead session usually presents
34
+ * as a stream that simply goes quiet rather than one that errors. Both a gap
35
+ * between keep-alives and an ended stream therefore mean the same thing: the
36
+ * connection is gone and the client must reconnect.
37
+ */
38
+ export declare class KernelKeepAlive {
39
+ private readonly connections;
40
+ private readonly onConnectionLost;
41
+ /**
42
+ * How long to wait for a keep-alive before treating the connection as dead.
43
+ * The kernel emits one per second and evicts clients whose `LastSeen` falls
44
+ * more than five seconds behind, which is the threshold the C# client uses too.
45
+ */
46
+ static readonly defaultIdleTimeoutMs = 5000;
47
+ /** How long a single answer may take before the connection is considered dead. */
48
+ static readonly defaultAnswerTimeoutMs = 5000;
49
+ private readonly _logger;
50
+ private readonly _idleTimeoutMs;
51
+ private readonly _answerTimeoutMs;
52
+ /**
53
+ * Creates a new {@link KernelKeepAlive}.
54
+ * @param connections - The connection service to ping-pong with.
55
+ * @param onConnectionLost - Called once when the connection is found to be dead.
56
+ * @param timeouts - Overrides for the default timeouts.
57
+ */
58
+ constructor(connections: IKeepAliveConnections, onConnectionLost: (reason: string, error: unknown) => void, timeouts?: KeepAliveTimeouts);
59
+ /**
60
+ * Opens the `Connect` stream and answers the first keep-alive, establishing the
61
+ * client's registration with the kernel. Resolves once the kernel has been heard
62
+ * from; the ongoing ping-pong then continues in the background until the
63
+ * connection is lost or `signal` is aborted.
64
+ * @param request - The connect request identifying this client.
65
+ * @param signal - Aborted to stop the keep-alive entirely.
66
+ */
67
+ start(request: ConnectRequest, signal: AbortSignal): Promise<void>;
68
+ private run;
69
+ private answer;
70
+ private connectionLost;
71
+ private withTimeout;
72
+ }
73
+ //# sourceMappingURL=KernelKeepAlive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KernelKeepAlive.d.ts","sourceRoot":"","sources":["../../connection/KernelKeepAlive.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAC9B,gFAAgF;IAChF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kFAAkF;IAClF,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IAClC,OAAO,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IAC7F,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9F;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,eAAe;IAyBpB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAzBrC;;;;OAIG;IACH,MAAM,CAAC,QAAQ,CAAC,oBAAoB,QAAQ;IAE5C,kFAAkF;IAClF,MAAM,CAAC,QAAQ,CAAC,sBAAsB,QAAQ;IAE9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAErB;IAEH,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAE1C;;;;;OAKG;gBAEkB,WAAW,EAAE,qBAAqB,EAClC,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,EAC3E,QAAQ,GAAE,iBAAsB;IAMpC;;;;;;;OAOG;IACG,KAAK,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;YAqB1D,GAAG;YAuBH,MAAM;IAUpB,OAAO,CAAC,cAAc;YAQR,WAAW;CAiB5B"}
@@ -0,0 +1,115 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+ import { diag } from '@opentelemetry/api';
4
+ /**
5
+ * Drives the client half of Chronicle's keep-alive ping-pong.
6
+ *
7
+ * The kernel pushes a `ConnectionKeepAlive` down the `Connect` server stream once
8
+ * per second, and for each one the client must call the separate unary
9
+ * `ConnectionKeepAlive` RPC back — that is what bumps `LastSeen` on the kernel and
10
+ * keeps the client registered. A client that stops answering is evicted, its
11
+ * observers are unsubscribed, and reactors and reducers go silent while appends
12
+ * keep working.
13
+ *
14
+ * Eviction does not close the `Connect` stream, so a dead session usually presents
15
+ * as a stream that simply goes quiet rather than one that errors. Both a gap
16
+ * between keep-alives and an ended stream therefore mean the same thing: the
17
+ * connection is gone and the client must reconnect.
18
+ */
19
+ export class KernelKeepAlive {
20
+ connections;
21
+ onConnectionLost;
22
+ /**
23
+ * How long to wait for a keep-alive before treating the connection as dead.
24
+ * The kernel emits one per second and evicts clients whose `LastSeen` falls
25
+ * more than five seconds behind, which is the threshold the C# client uses too.
26
+ */
27
+ static defaultIdleTimeoutMs = 5000;
28
+ /** How long a single answer may take before the connection is considered dead. */
29
+ static defaultAnswerTimeoutMs = 5000;
30
+ _logger = diag.createComponentLogger({
31
+ namespace: '@cratis/chronicle/KernelKeepAlive'
32
+ });
33
+ _idleTimeoutMs;
34
+ _answerTimeoutMs;
35
+ /**
36
+ * Creates a new {@link KernelKeepAlive}.
37
+ * @param connections - The connection service to ping-pong with.
38
+ * @param onConnectionLost - Called once when the connection is found to be dead.
39
+ * @param timeouts - Overrides for the default timeouts.
40
+ */
41
+ constructor(connections, onConnectionLost, timeouts = {}) {
42
+ this.connections = connections;
43
+ this.onConnectionLost = onConnectionLost;
44
+ this._idleTimeoutMs = timeouts.idleTimeoutMs ?? KernelKeepAlive.defaultIdleTimeoutMs;
45
+ this._answerTimeoutMs = timeouts.answerTimeoutMs ?? KernelKeepAlive.defaultAnswerTimeoutMs;
46
+ }
47
+ /**
48
+ * Opens the `Connect` stream and answers the first keep-alive, establishing the
49
+ * client's registration with the kernel. Resolves once the kernel has been heard
50
+ * from; the ongoing ping-pong then continues in the background until the
51
+ * connection is lost or `signal` is aborted.
52
+ * @param request - The connect request identifying this client.
53
+ * @param signal - Aborted to stop the keep-alive entirely.
54
+ */
55
+ async start(request, signal) {
56
+ const stream = this.connections.connect(request, { signal });
57
+ const iterator = stream[Symbol.asyncIterator]();
58
+ // A kernel that accepts the stream but never sends anything must not hang
59
+ // the connect indefinitely — every caller awaiting the connection would
60
+ // block forever behind it.
61
+ const first = await this.withTimeout(iterator.next(), this._idleTimeoutMs, 'Timed out waiting for the first keep-alive from the kernel');
62
+ if (first.done) {
63
+ throw new Error('Connection service stream ended before sending first keep-alive');
64
+ }
65
+ await this.answer(first.value, signal);
66
+ void this.run(iterator, signal);
67
+ }
68
+ async run(iterator, signal) {
69
+ try {
70
+ while (!signal.aborted) {
71
+ const result = await this.withTimeout(iterator.next(), this._idleTimeoutMs, 'No keep-alive received from the kernel within the idle timeout');
72
+ if (result.done) {
73
+ this.connectionLost('keep-alive-stream-ended', new Error('Keep-alive stream ended'));
74
+ return;
75
+ }
76
+ await this.answer(result.value, signal);
77
+ }
78
+ }
79
+ catch (error) {
80
+ if (!signal.aborted) {
81
+ this.connectionLost('keep-alive-failed', error);
82
+ }
83
+ }
84
+ }
85
+ async answer(keepAlive, signal) {
86
+ // An answer that never returns would stop the loop draining the stream, which
87
+ // gets the client evicted just as surely as never answering at all.
88
+ await this.withTimeout(this.connections.connectionKeepAlive(keepAlive, { signal }), this._answerTimeoutMs, 'Timed out answering a kernel keep-alive');
89
+ }
90
+ connectionLost(reason, error) {
91
+ this._logger.warn('Kernel keep-alive lost the connection', {
92
+ reason,
93
+ error: error instanceof Error ? error.message : String(error)
94
+ });
95
+ this.onConnectionLost(reason, error);
96
+ }
97
+ async withTimeout(promise, timeoutMs, message) {
98
+ let handle;
99
+ try {
100
+ return await Promise.race([
101
+ promise,
102
+ new Promise((_, reject) => {
103
+ handle = setTimeout(() => reject(new Error(message)), timeoutMs);
104
+ handle.unref?.();
105
+ })
106
+ ]);
107
+ }
108
+ finally {
109
+ if (handle) {
110
+ clearTimeout(handle);
111
+ }
112
+ }
113
+ }
114
+ }
115
+ //# sourceMappingURL=KernelKeepAlive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KernelKeepAlive.js","sourceRoot":"","sources":["../../connection/KernelKeepAlive.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { ChronicleServerAddress } from './ChronicleConnectionString';
2
+ import type { ILoadBalancerStrategy } from './ILoadBalancerStrategy';
3
+ /**
4
+ * Selects the least-loaded candidate by probing each one's current connection count over
5
+ * HTTP and picking the minimum, breaking ties randomly. This is the default strategy.
6
+ *
7
+ * Before every probe attempt (not just the first), waits a random jitter to avoid a
8
+ * thundering herd of clients probing every candidate at the exact same instant. A single
9
+ * candidate is returned immediately without probing, since there is nothing to choose
10
+ * between.
11
+ */
12
+ export declare class LeastConnectionsLoadBalancerStrategy implements ILoadBalancerStrategy {
13
+ private readonly _skipTlsValidation;
14
+ private readonly _maxJitterMs;
15
+ private readonly _dispatcher;
16
+ /**
17
+ * Initializes a new instance of {@link LeastConnectionsLoadBalancerStrategy}.
18
+ * @param skipTlsValidation - Whether to skip TLS certificate validation on the
19
+ * `/connections/count` and `/connections/reserve` probe requests, matching the toggle
20
+ * used for the gRPC channel itself.
21
+ * @param maxJitterMs - The maximum jitter, in milliseconds, to wait before every probe
22
+ * attempt. Defaults to 250ms. 0 disables jitter entirely.
23
+ */
24
+ constructor(_skipTlsValidation: boolean, _maxJitterMs?: number);
25
+ select(candidates: ChronicleServerAddress[]): Promise<ChronicleServerAddress>;
26
+ private jitter;
27
+ private probeConnectionCount;
28
+ private reserve;
29
+ }
30
+ //# sourceMappingURL=LeastConnectionsLoadBalancerStrategy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LeastConnectionsLoadBalancerStrategy.d.ts","sourceRoot":"","sources":["../../connection/LeastConnectionsLoadBalancerStrategy.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAE1E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAWrE;;;;;;;;GAQG;AACH,qBAAa,oCAAqC,YAAW,qBAAqB;IAY1E,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY;IAZjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IAEpC;;;;;;;OAOG;gBAEkB,kBAAkB,EAAE,OAAO,EAC3B,YAAY,GAAE,MAA8B;IAK3D,MAAM,CAAC,UAAU,EAAE,sBAAsB,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC;YA2BrE,MAAM;YAaN,oBAAoB;YAqBpB,OAAO;CAWxB"}
@@ -0,0 +1,95 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+ import { Agent } from 'undici';
4
+ import { formatServerAddress } from './formatServerAddress';
5
+ const DEFAULT_MAX_JITTER_MS = 250;
6
+ const PROBE_TIMEOUT_MS = 2000;
7
+ /**
8
+ * Selects the least-loaded candidate by probing each one's current connection count over
9
+ * HTTP and picking the minimum, breaking ties randomly. This is the default strategy.
10
+ *
11
+ * Before every probe attempt (not just the first), waits a random jitter to avoid a
12
+ * thundering herd of clients probing every candidate at the exact same instant. A single
13
+ * candidate is returned immediately without probing, since there is nothing to choose
14
+ * between.
15
+ */
16
+ export class LeastConnectionsLoadBalancerStrategy {
17
+ _skipTlsValidation;
18
+ _maxJitterMs;
19
+ _dispatcher;
20
+ /**
21
+ * Initializes a new instance of {@link LeastConnectionsLoadBalancerStrategy}.
22
+ * @param skipTlsValidation - Whether to skip TLS certificate validation on the
23
+ * `/connections/count` and `/connections/reserve` probe requests, matching the toggle
24
+ * used for the gRPC channel itself.
25
+ * @param maxJitterMs - The maximum jitter, in milliseconds, to wait before every probe
26
+ * attempt. Defaults to 250ms. 0 disables jitter entirely.
27
+ */
28
+ constructor(_skipTlsValidation, _maxJitterMs = DEFAULT_MAX_JITTER_MS) {
29
+ this._skipTlsValidation = _skipTlsValidation;
30
+ this._maxJitterMs = _maxJitterMs;
31
+ this._dispatcher = new Agent({ connect: { rejectUnauthorized: !this._skipTlsValidation } });
32
+ }
33
+ async select(candidates) {
34
+ if (candidates.length === 0) {
35
+ throw new Error('Cannot select a server address from an empty candidate list');
36
+ }
37
+ if (candidates.length === 1) {
38
+ return candidates[0];
39
+ }
40
+ await this.jitter();
41
+ const connectionCounts = await Promise.all(candidates.map(candidate => this.probeConnectionCount(candidate)));
42
+ const minimumCount = Math.min(...connectionCounts);
43
+ const minimumIndices = connectionCounts.reduce((indices, count, index) => {
44
+ if (count === minimumCount) {
45
+ indices.push(index);
46
+ }
47
+ return indices;
48
+ }, []);
49
+ const selected = candidates[minimumIndices[Math.floor(Math.random() * minimumIndices.length)]];
50
+ await this.reserve(selected);
51
+ return selected;
52
+ }
53
+ async jitter() {
54
+ if (this._maxJitterMs <= 0) {
55
+ return;
56
+ }
57
+ const delayMs = Math.floor(Math.random() * this._maxJitterMs);
58
+ if (delayMs <= 0) {
59
+ return;
60
+ }
61
+ await new Promise(resolve => setTimeout(resolve, delayMs));
62
+ }
63
+ async probeConnectionCount(candidate) {
64
+ try {
65
+ const response = await fetch(`https://${formatServerAddress(candidate)}/connections/count`, {
66
+ signal: AbortSignal.timeout(PROBE_TIMEOUT_MS),
67
+ dispatcher: this._dispatcher
68
+ });
69
+ if (!response.ok) {
70
+ return Number.MAX_SAFE_INTEGER;
71
+ }
72
+ const body = (await response.text()).trim();
73
+ const count = Number(body);
74
+ return body.length > 0 && Number.isFinite(count) ? count : Number.MAX_SAFE_INTEGER;
75
+ }
76
+ catch {
77
+ // A candidate that cannot be reached or answers with garbage is never preferred
78
+ // over one that responds normally, but must not fail the whole selection round.
79
+ return Number.MAX_SAFE_INTEGER;
80
+ }
81
+ }
82
+ async reserve(candidate) {
83
+ try {
84
+ await fetch(`https://${formatServerAddress(candidate)}/connections/reserve`, {
85
+ method: 'POST',
86
+ signal: AbortSignal.timeout(PROBE_TIMEOUT_MS),
87
+ dispatcher: this._dispatcher
88
+ });
89
+ }
90
+ catch {
91
+ // Best-effort reservation; the selected server is still used even if this fails.
92
+ }
93
+ }
94
+ }
95
+ //# sourceMappingURL=LeastConnectionsLoadBalancerStrategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LeastConnectionsLoadBalancerStrategy.js","sourceRoot":"","sources":["../../connection/LeastConnectionsLoadBalancerStrategy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The strategy used to select one server address from a multi-host connection string
3
+ * (or a resolved set of `chronicle+srv://` addresses) for each connect/reconnect attempt.
4
+ */
5
+ export declare enum LoadBalancerMode {
6
+ /**
7
+ * Probes every candidate's current connection count and picks the least-loaded one,
8
+ * breaking ties randomly. The default strategy.
9
+ */
10
+ LeastConnections = "least-connections",
11
+ /**
12
+ * Cycles through candidates in order, starting from a random offset chosen once per
13
+ * strategy instance.
14
+ */
15
+ RoundRobin = "round-robin",
16
+ /**
17
+ * Picks a uniformly random candidate on every call.
18
+ */
19
+ Random = "random"
20
+ }
21
+ //# sourceMappingURL=LoadBalancerMode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LoadBalancerMode.d.ts","sourceRoot":"","sources":["../../connection/LoadBalancerMode.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,oBAAY,gBAAgB;IACxB;;;OAGG;IACH,gBAAgB,sBAAsB;IAEtC;;;OAGG;IACH,UAAU,gBAAgB;IAE1B;;OAEG;IACH,MAAM,WAAW;CACpB"}
@@ -0,0 +1,24 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+ /**
4
+ * The strategy used to select one server address from a multi-host connection string
5
+ * (or a resolved set of `chronicle+srv://` addresses) for each connect/reconnect attempt.
6
+ */
7
+ export var LoadBalancerMode;
8
+ (function (LoadBalancerMode) {
9
+ /**
10
+ * Probes every candidate's current connection count and picks the least-loaded one,
11
+ * breaking ties randomly. The default strategy.
12
+ */
13
+ LoadBalancerMode["LeastConnections"] = "least-connections";
14
+ /**
15
+ * Cycles through candidates in order, starting from a random offset chosen once per
16
+ * strategy instance.
17
+ */
18
+ LoadBalancerMode["RoundRobin"] = "round-robin";
19
+ /**
20
+ * Picks a uniformly random candidate on every call.
21
+ */
22
+ LoadBalancerMode["Random"] = "random";
23
+ })(LoadBalancerMode || (LoadBalancerMode = {}));
24
+ //# sourceMappingURL=LoadBalancerMode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LoadBalancerMode.js","sourceRoot":"","sources":["../../connection/LoadBalancerMode.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,qGAAqG;AAErG;;;GAGG;AACH,MAAM,CAAN,IAAY,gBAiBX;AAjBD,WAAY,gBAAgB;IACxB;;;OAGG;IACH,0DAAsC,CAAA;IAEtC;;;OAGG;IACH,8CAA0B,CAAA;IAE1B;;OAEG;IACH,qCAAiB,CAAA;AACrB,CAAC,EAjBW,gBAAgB,KAAhB,gBAAgB,QAiB3B"}
@@ -0,0 +1,11 @@
1
+ import type { ILoadBalancerStrategy } from './ILoadBalancerStrategy';
2
+ import { LoadBalancerMode } from './LoadBalancerMode';
3
+ /**
4
+ * Creates the {@link ILoadBalancerStrategy} instance matching a {@link LoadBalancerMode}.
5
+ * @param mode - The load balancer mode to create a strategy for.
6
+ * @param skipTlsValidation - Whether to skip TLS certificate validation for strategies that
7
+ * probe candidates over HTTP (currently only {@link LoadBalancerMode.LeastConnections}).
8
+ * @returns The load balancer strategy instance.
9
+ */
10
+ export declare function createLoadBalancerStrategy(mode: LoadBalancerMode, skipTlsValidation: boolean): ILoadBalancerStrategy;
11
+ //# sourceMappingURL=LoadBalancerStrategyFactory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LoadBalancerStrategyFactory.d.ts","sourceRoot":"","sources":["../../connection/LoadBalancerStrategyFactory.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAErE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAItD;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,OAAO,GAAG,qBAAqB,CAWpH"}
@@ -0,0 +1,26 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+ import { LeastConnectionsLoadBalancerStrategy } from './LeastConnectionsLoadBalancerStrategy';
4
+ import { LoadBalancerMode } from './LoadBalancerMode';
5
+ import { RandomLoadBalancerStrategy } from './RandomLoadBalancerStrategy';
6
+ import { RoundRobinLoadBalancerStrategy } from './RoundRobinLoadBalancerStrategy';
7
+ /**
8
+ * Creates the {@link ILoadBalancerStrategy} instance matching a {@link LoadBalancerMode}.
9
+ * @param mode - The load balancer mode to create a strategy for.
10
+ * @param skipTlsValidation - Whether to skip TLS certificate validation for strategies that
11
+ * probe candidates over HTTP (currently only {@link LoadBalancerMode.LeastConnections}).
12
+ * @returns The load balancer strategy instance.
13
+ */
14
+ export function createLoadBalancerStrategy(mode, skipTlsValidation) {
15
+ switch (mode) {
16
+ case LoadBalancerMode.RoundRobin:
17
+ return new RoundRobinLoadBalancerStrategy();
18
+ case LoadBalancerMode.Random:
19
+ return new RandomLoadBalancerStrategy();
20
+ case LoadBalancerMode.LeastConnections:
21
+ return new LeastConnectionsLoadBalancerStrategy(skipTlsValidation);
22
+ default:
23
+ throw new Error(`Unknown load balancer mode: ${mode}`);
24
+ }
25
+ }
26
+ //# sourceMappingURL=LoadBalancerStrategyFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LoadBalancerStrategyFactory.js","sourceRoot":"","sources":["../../connection/LoadBalancerStrategyFactory.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,qGAAqG;AAGrG,OAAO,EAAE,oCAAoC,EAAE,MAAM,wCAAwC,CAAC;AAC9F,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,8BAA8B,EAAE,MAAM,kCAAkC,CAAC;AAElF;;;;;;GAMG;AACH,MAAM,UAAU,0BAA0B,CAAC,IAAsB,EAAE,iBAA0B;IACzF,QAAQ,IAAI,EAAE,CAAC;QACX,KAAK,gBAAgB,CAAC,UAAU;YAC5B,OAAO,IAAI,8BAA8B,EAAE,CAAC;QAChD,KAAK,gBAAgB,CAAC,MAAM;YACxB,OAAO,IAAI,0BAA0B,EAAE,CAAC;QAC5C,KAAK,gBAAgB,CAAC,gBAAgB;YAClC,OAAO,IAAI,oCAAoC,CAAC,iBAAiB,CAAC,CAAC;QACvE;YACI,MAAM,IAAI,KAAK,CAAC,+BAA+B,IAAI,EAAE,CAAC,CAAC;IAC/D,CAAC;AACL,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { ChronicleServerAddress } from './ChronicleConnectionString';
2
+ import type { ILoadBalancerStrategy } from './ILoadBalancerStrategy';
3
+ /**
4
+ * Selects a uniformly random candidate on every call.
5
+ */
6
+ export declare class RandomLoadBalancerStrategy implements ILoadBalancerStrategy {
7
+ select(candidates: ChronicleServerAddress[]): Promise<ChronicleServerAddress>;
8
+ }
9
+ //# sourceMappingURL=RandomLoadBalancerStrategy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RandomLoadBalancerStrategy.d.ts","sourceRoot":"","sources":["../../connection/RandomLoadBalancerStrategy.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAErE;;GAEG;AACH,qBAAa,0BAA2B,YAAW,qBAAqB;IAC9D,MAAM,CAAC,UAAU,EAAE,sBAAsB,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC;CAQtF"}
@@ -0,0 +1,15 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+ /**
4
+ * Selects a uniformly random candidate on every call.
5
+ */
6
+ export class RandomLoadBalancerStrategy {
7
+ async select(candidates) {
8
+ if (candidates.length === 0) {
9
+ throw new Error('Cannot select a server address from an empty candidate list');
10
+ }
11
+ const index = Math.floor(Math.random() * candidates.length);
12
+ return candidates[index];
13
+ }
14
+ }
15
+ //# sourceMappingURL=RandomLoadBalancerStrategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RandomLoadBalancerStrategy.js","sourceRoot":"","sources":["../../connection/RandomLoadBalancerStrategy.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,qGAAqG;AAKrG;;GAEG;AACH,MAAM,OAAO,0BAA0B;IACnC,KAAK,CAAC,MAAM,CAAC,UAAoC;QAC7C,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;QACnF,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;QAC5D,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;CACJ"}
@@ -0,0 +1,12 @@
1
+ import type { ChronicleServerAddress } from './ChronicleConnectionString';
2
+ import type { ILoadBalancerStrategy } from './ILoadBalancerStrategy';
3
+ /**
4
+ * Cycles through candidates in order, starting from a random offset chosen once per
5
+ * strategy instance so that multiple clients don't all start at the same candidate.
6
+ */
7
+ export declare class RoundRobinLoadBalancerStrategy implements ILoadBalancerStrategy {
8
+ private _startOffset?;
9
+ private _callCount;
10
+ select(candidates: ChronicleServerAddress[]): Promise<ChronicleServerAddress>;
11
+ }
12
+ //# sourceMappingURL=RoundRobinLoadBalancerStrategy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RoundRobinLoadBalancerStrategy.d.ts","sourceRoot":"","sources":["../../connection/RoundRobinLoadBalancerStrategy.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAErE;;;GAGG;AACH,qBAAa,8BAA+B,YAAW,qBAAqB;IACxE,OAAO,CAAC,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,UAAU,CAAK;IAEjB,MAAM,CAAC,UAAU,EAAE,sBAAsB,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC;CActF"}
@@ -0,0 +1,22 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+ /**
4
+ * Cycles through candidates in order, starting from a random offset chosen once per
5
+ * strategy instance so that multiple clients don't all start at the same candidate.
6
+ */
7
+ export class RoundRobinLoadBalancerStrategy {
8
+ _startOffset;
9
+ _callCount = 0;
10
+ async select(candidates) {
11
+ if (candidates.length === 0) {
12
+ throw new Error('Cannot select a server address from an empty candidate list');
13
+ }
14
+ if (this._startOffset === undefined) {
15
+ this._startOffset = Math.floor(Math.random() * candidates.length);
16
+ }
17
+ const index = (this._startOffset + this._callCount) % candidates.length;
18
+ this._callCount++;
19
+ return candidates[index];
20
+ }
21
+ }
22
+ //# sourceMappingURL=RoundRobinLoadBalancerStrategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RoundRobinLoadBalancerStrategy.js","sourceRoot":"","sources":["../../connection/RoundRobinLoadBalancerStrategy.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,qGAAqG;AAKrG;;;GAGG;AACH,MAAM,OAAO,8BAA8B;IAC/B,YAAY,CAAU;IACtB,UAAU,GAAG,CAAC,CAAC;IAEvB,KAAK,CAAC,MAAM,CAAC,UAAoC;QAC7C,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;QACnF,CAAC;QAED,IAAI,IAAI,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;YAClC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;QACtE,CAAC;QAED,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC;QACxE,IAAI,CAAC,UAAU,EAAE,CAAC;QAElB,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;CACJ"}
@@ -27,10 +27,11 @@ export declare class OAuthTokenProvider implements ITokenProvider {
27
27
  private readonly _tokenEndpoint;
28
28
  private readonly _clientId;
29
29
  private readonly _clientSecret;
30
+ private readonly _skipTlsValidation;
30
31
  private _accessToken?;
31
32
  private _tokenExpiry;
32
33
  private _refreshPromise?;
33
- constructor(_tokenEndpoint: string, _clientId: string, _clientSecret: string);
34
+ constructor(_tokenEndpoint: string, _clientId: string, _clientSecret: string, _skipTlsValidation?: boolean);
34
35
  getAccessToken(): Promise<string | undefined>;
35
36
  refresh(): Promise<string | undefined>;
36
37
  private fetchAccessToken;
@@ -1 +1 @@
1
- {"version":3,"file":"TokenProvider.d.ts","sourceRoot":"","sources":["../../connection/TokenProvider.ts"],"names":[],"mappings":"AASA;;GAEG;AACH,MAAM,WAAW,cAAc;IAC3B;;;OAGG;IACH,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAE9C;;;OAGG;IACH,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CAC1C;AAED;;GAEG;AACH,qBAAa,iBAAkB,YAAW,cAAc;IAC9C,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAI7C,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;CAG/C;AAOD;;GAEG;AACH,qBAAa,kBAAmB,YAAW,cAAc;IAMjD,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAPlC,OAAO,CAAC,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,eAAe,CAAC,CAA8B;gBAGjC,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM;IAGpC,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAiB7C,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;YAM9B,gBAAgB;CAmDjC;AAED,MAAM,MAAM,aAAa,GAAG,cAAc,CAAC"}
1
+ {"version":3,"file":"TokenProvider.d.ts","sourceRoot":"","sources":["../../connection/TokenProvider.ts"],"names":[],"mappings":"AASA;;GAEG;AACH,MAAM,WAAW,cAAc;IAC3B;;;OAGG;IACH,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAE9C;;;OAGG;IACH,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CAC1C;AAED;;GAEG;AACH,qBAAa,iBAAkB,YAAW,cAAc;IAC9C,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAI7C,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;CAG/C;AAOD;;GAEG;AACH,qBAAa,kBAAmB,YAAW,cAAc;IAMjD,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IARvC,OAAO,CAAC,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,eAAe,CAAC,CAA8B;gBAGjC,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,kBAAkB,GAAE,OAAc;IAGjD,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAiB7C,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;YAM9B,gBAAgB;CAuDjC;AAED,MAAM,MAAM,aAAa,GAAG,cAAc,CAAC"}
@@ -22,13 +22,15 @@ export class OAuthTokenProvider {
22
22
  _tokenEndpoint;
23
23
  _clientId;
24
24
  _clientSecret;
25
+ _skipTlsValidation;
25
26
  _accessToken;
26
27
  _tokenExpiry = new Date(0);
27
28
  _refreshPromise;
28
- constructor(_tokenEndpoint, _clientId, _clientSecret) {
29
+ constructor(_tokenEndpoint, _clientId, _clientSecret, _skipTlsValidation = true) {
29
30
  this._tokenEndpoint = _tokenEndpoint;
30
31
  this._clientId = _clientId;
31
32
  this._clientSecret = _clientSecret;
33
+ this._skipTlsValidation = _skipTlsValidation;
32
34
  }
33
35
  async getAccessToken() {
34
36
  if (this._accessToken && new Date() < this._tokenExpiry) {
@@ -58,13 +60,17 @@ export class OAuthTokenProvider {
58
60
  const body = params.toString();
59
61
  return new Promise((resolve, reject) => {
60
62
  const url = new URL(this._tokenEndpoint);
61
- const httpModule = url.protocol === 'https:' ? https : http;
63
+ const isHttps = url.protocol === 'https:';
64
+ const httpModule = isHttps ? https : http;
62
65
  const req = httpModule.request(url, {
63
66
  method: 'POST',
64
67
  headers: {
65
68
  'Content-Type': 'application/x-www-form-urlencoded',
66
69
  'Content-Length': Buffer.byteLength(body)
67
- }
70
+ },
71
+ // Chain validation is skipped only when skipTlsValidation is explicitly set,
72
+ // matching the gRPC channel's credentials for the same connection string.
73
+ ...(isHttps && this._skipTlsValidation ? { rejectUnauthorized: false } : {})
68
74
  }, response => {
69
75
  let data = '';
70
76
  response.on('data', chunk => {
@@ -1 +1 @@
1
- {"version":3,"file":"TokenProvider.js","sourceRoot":"","sources":["../../connection/TokenProvider.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"TokenProvider.js","sourceRoot":"","sources":["../../connection/TokenProvider.ts"],"names":[],"mappings":"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"}