@cratis/arc 20.1.4 → 20.1.6
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/dist/cjs/queries/HubConnectionKeepAlive.d.ts +2 -1
- package/dist/cjs/queries/HubConnectionKeepAlive.d.ts.map +1 -1
- package/dist/cjs/queries/HubConnectionKeepAlive.js +4 -2
- package/dist/cjs/queries/HubConnectionKeepAlive.js.map +1 -1
- package/dist/cjs/queries/ReconnectPolicy.d.ts.map +1 -1
- package/dist/cjs/queries/ReconnectPolicy.js +1 -0
- package/dist/cjs/queries/ReconnectPolicy.js.map +1 -1
- package/dist/cjs/queries/ServerSentEventHubConnection.d.ts.map +1 -1
- package/dist/cjs/queries/ServerSentEventHubConnection.js +10 -3
- package/dist/cjs/queries/ServerSentEventHubConnection.js.map +1 -1
- package/dist/cjs/queries/for_HubConnectionKeepAlive/when_started_with_custom_idle_threshold/respects_threshold.d.ts +2 -0
- package/dist/cjs/queries/for_HubConnectionKeepAlive/when_started_with_custom_idle_threshold/respects_threshold.d.ts.map +1 -0
- package/dist/cjs/queries/for_ReconnectPolicy/when_scheduling/and_rescheduled_before_first_fires/cancels_previous_timer.d.ts +2 -0
- package/dist/cjs/queries/for_ReconnectPolicy/when_scheduling/and_rescheduled_before_first_fires/cancels_previous_timer.d.ts.map +1 -0
- package/dist/cjs/queries/for_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.d.ts +2 -0
- package/dist/cjs/queries/for_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.d.ts.map +1 -0
- package/dist/esm/queries/HubConnectionKeepAlive.d.ts +2 -1
- package/dist/esm/queries/HubConnectionKeepAlive.d.ts.map +1 -1
- package/dist/esm/queries/HubConnectionKeepAlive.js +4 -2
- package/dist/esm/queries/HubConnectionKeepAlive.js.map +1 -1
- package/dist/esm/queries/ReconnectPolicy.d.ts.map +1 -1
- package/dist/esm/queries/ReconnectPolicy.js +1 -0
- package/dist/esm/queries/ReconnectPolicy.js.map +1 -1
- package/dist/esm/queries/ServerSentEventHubConnection.d.ts.map +1 -1
- package/dist/esm/queries/ServerSentEventHubConnection.js +10 -3
- package/dist/esm/queries/ServerSentEventHubConnection.js.map +1 -1
- package/dist/esm/queries/for_HubConnectionKeepAlive/when_started_with_custom_idle_threshold/respects_threshold.d.ts +2 -0
- package/dist/esm/queries/for_HubConnectionKeepAlive/when_started_with_custom_idle_threshold/respects_threshold.d.ts.map +1 -0
- package/dist/esm/queries/for_HubConnectionKeepAlive/when_started_with_custom_idle_threshold/respects_threshold.js +33 -0
- package/dist/esm/queries/for_HubConnectionKeepAlive/when_started_with_custom_idle_threshold/respects_threshold.js.map +1 -0
- package/dist/esm/queries/for_ReconnectPolicy/when_scheduling/and_rescheduled_before_first_fires/cancels_previous_timer.d.ts +2 -0
- package/dist/esm/queries/for_ReconnectPolicy/when_scheduling/and_rescheduled_before_first_fires/cancels_previous_timer.d.ts.map +1 -0
- package/dist/esm/queries/for_ReconnectPolicy/when_scheduling/and_rescheduled_before_first_fires/cancels_previous_timer.js +24 -0
- package/dist/esm/queries/for_ReconnectPolicy/when_scheduling/and_rescheduled_before_first_fires/cancels_previous_timer.js.map +1 -0
- package/dist/esm/queries/for_ServerSentEventHubConnection/when_keep_alive_is_idle/triggers_reconnect.js +1 -1
- package/dist/esm/queries/for_ServerSentEventHubConnection/when_keep_alive_is_idle/triggers_reconnect.js.map +1 -1
- package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.d.ts +2 -0
- package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.d.ts.map +1 -0
- package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.js +33 -0
- package/dist/esm/queries/for_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.js.map +1 -0
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/queries/HubConnectionKeepAlive.ts +20 -6
- package/queries/ReconnectPolicy.ts +4 -0
- package/queries/ServerSentEventHubConnection.ts +17 -4
- package/queries/for_HubConnectionKeepAlive/when_started_with_custom_idle_threshold/respects_threshold.ts +46 -0
- package/queries/for_ReconnectPolicy/when_scheduling/and_rescheduled_before_first_fires/cancels_previous_timer.ts +36 -0
- package/queries/for_ServerSentEventHubConnection/when_keep_alive_is_idle/triggers_reconnect.ts +5 -2
- package/queries/for_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.ts +51 -0
|
@@ -3,7 +3,8 @@ export declare class HubConnectionKeepAlive {
|
|
|
3
3
|
private readonly _onIdle;
|
|
4
4
|
private _lastActivityTime;
|
|
5
5
|
private _timer?;
|
|
6
|
-
|
|
6
|
+
private readonly _idleThresholdMs;
|
|
7
|
+
constructor(_intervalMs: number, _onIdle: () => void, idleThresholdMs?: number);
|
|
7
8
|
start(): void;
|
|
8
9
|
stop(): void;
|
|
9
10
|
recordActivity(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HubConnectionKeepAlive.d.ts","sourceRoot":"","sources":["../../../queries/HubConnectionKeepAlive.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"HubConnectionKeepAlive.d.ts","sourceRoot":"","sources":["../../../queries/HubConnectionKeepAlive.ts"],"names":[],"mappings":"AAqBA,qBAAa,sBAAsB;IAgB3B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAhB5B,OAAO,CAAC,iBAAiB,CAAc;IACvC,OAAO,CAAC,MAAM,CAAC,CAAiC;IAChD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;gBAarB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,IAAI,EACpC,eAAe,CAAC,EAAE,MAAM;IAQ5B,KAAK,IAAI,IAAI;IAab,IAAI,IAAI,IAAI;IAWZ,cAAc,IAAI,IAAI;CAGzB"}
|
|
@@ -5,15 +5,17 @@ class HubConnectionKeepAlive {
|
|
|
5
5
|
_onIdle;
|
|
6
6
|
_lastActivityTime = Date.now();
|
|
7
7
|
_timer;
|
|
8
|
-
|
|
8
|
+
_idleThresholdMs;
|
|
9
|
+
constructor(_intervalMs, _onIdle, idleThresholdMs) {
|
|
9
10
|
this._intervalMs = _intervalMs;
|
|
10
11
|
this._onIdle = _onIdle;
|
|
12
|
+
this._idleThresholdMs = idleThresholdMs ?? _intervalMs;
|
|
11
13
|
}
|
|
12
14
|
start() {
|
|
13
15
|
this.stop();
|
|
14
16
|
this._lastActivityTime = Date.now();
|
|
15
17
|
this._timer = setInterval(() => {
|
|
16
|
-
if (Date.now() - this._lastActivityTime >= this.
|
|
18
|
+
if (Date.now() - this._lastActivityTime >= this._idleThresholdMs) {
|
|
17
19
|
this._onIdle();
|
|
18
20
|
}
|
|
19
21
|
}, this._intervalMs);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HubConnectionKeepAlive.js","sources":["../../../queries/HubConnectionKeepAlive.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\n/**\n * Manages keep-alive behavior for hub connections (both WebSocket and Server-Sent Events).\n *\n * Records connection activity (any message received or sent). An interval fires every\n * {@link intervalMs} milliseconds; if no activity has been recorded since the last
|
|
1
|
+
{"version":3,"file":"HubConnectionKeepAlive.js","sources":["../../../queries/HubConnectionKeepAlive.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\n/**\n * Manages keep-alive behavior for hub connections (both WebSocket and Server-Sent Events).\n *\n * Records connection activity (any message received or sent). An interval fires every\n * {@link intervalMs} milliseconds; if no activity has been recorded since the last\n * {@link idleThresholdMs} milliseconds the provided {@link onIdle} callback is invoked —\n * the caller decides whether to send a ping, trigger a reconnect, or take some other action.\n *\n * The idle threshold defaults to the check interval but can be set higher to tolerate\n * network latency between the server's keep-alive ping and the client's idle check.\n * For SSE connections, where the idle callback triggers a reconnect, a tolerance of\n * 1.5× the server's keep-alive interval prevents spurious reconnects caused by the\n * client's timer firing just before the server's ping arrives.\n *\n * Both {@link WebSocketHubConnection} and {@link ServerSentEventHubConnection} own one instance\n * of this class so the keep-alive logic is written once and behaves identically for both\n * transports.\n */\nexport class HubConnectionKeepAlive {\n private _lastActivityTime = Date.now();\n private _timer?: ReturnType<typeof setInterval>;\n private readonly _idleThresholdMs: number;\n\n /**\n * Initializes a new instance of {@link HubConnectionKeepAlive}.\n * @param {number} intervalMs How often (in milliseconds) to check for idle connections.\n * @param {() => void} onIdle Callback invoked when the interval fires and no activity has\n * been recorded within the idle threshold.\n * @param {number} idleThresholdMs Optional. How long (in milliseconds) without activity\n * before the connection is considered idle. Defaults to {@link intervalMs}. Set this\n * higher than {@link intervalMs} when the peer sends keep-alive messages on a similar\n * cadence to account for network latency and timer jitter.\n */\n constructor(\n private readonly _intervalMs: number,\n private readonly _onIdle: () => void,\n idleThresholdMs?: number,\n ) {\n this._idleThresholdMs = idleThresholdMs ?? _intervalMs;\n }\n\n /**\n * Start the keep-alive timer. Safe to call multiple times — a running timer is stopped first.\n */\n start(): void {\n this.stop();\n this._lastActivityTime = Date.now();\n this._timer = setInterval(() => {\n if (Date.now() - this._lastActivityTime >= this._idleThresholdMs) {\n this._onIdle();\n }\n }, this._intervalMs);\n }\n\n /**\n * Stop the keep-alive timer.\n */\n stop(): void {\n if (this._timer !== undefined) {\n clearInterval(this._timer);\n this._timer = undefined;\n }\n }\n\n /**\n * Record that the connection has been active (message sent or received).\n * Resets the idle timer so that a keep-alive is not sent while data is already flowing.\n */\n recordActivity(): void {\n this._lastActivityTime = Date.now();\n }\n}\n"],"names":[],"mappings":";;MAqBa,sBAAsB,CAAA;AAgBV,IAAA,WAAA;AACA,IAAA,OAAA;AAhBb,IAAA,iBAAiB,GAAG,IAAI,CAAC,GAAG,EAAE;AAC9B,IAAA,MAAM;AACG,IAAA,gBAAgB;AAYjC,IAAA,WAAA,CACqB,WAAmB,EACnB,OAAmB,EACpC,eAAwB,EAAA;QAFP,IAAA,CAAA,WAAW,GAAX,WAAW;QACX,IAAA,CAAA,OAAO,GAAP,OAAO;AAGxB,QAAA,IAAI,CAAC,gBAAgB,GAAG,eAAe,IAAI,WAAW;IAC1D;IAKA,KAAK,GAAA;QACD,IAAI,CAAC,IAAI,EAAE;AACX,QAAA,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,GAAG,EAAE;AACnC,QAAA,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,MAAK;AAC3B,YAAA,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,iBAAiB,IAAI,IAAI,CAAC,gBAAgB,EAAE;gBAC9D,IAAI,CAAC,OAAO,EAAE;YAClB;AACJ,QAAA,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC;IACxB;IAKA,IAAI,GAAA;AACA,QAAA,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE;AAC3B,YAAA,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC;AAC1B,YAAA,IAAI,CAAC,MAAM,GAAG,SAAS;QAC3B;IACJ;IAMA,cAAc,GAAA;AACV,QAAA,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,GAAG,EAAE;IACvC;AACH;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReconnectPolicy.d.ts","sourceRoot":"","sources":["../../../queries/ReconnectPolicy.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAczE,qBAAa,eAAgB,YAAW,gBAAgB;IAYhD,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAdhC,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,MAAM,CAAC,CAAgC;gBAU1B,YAAY,GAAE,MAAY,EAC1B,eAAe,GAAE,MAAY,EAC7B,YAAY,GAAE,MAAY,EAC1B,WAAW,GAAE,MAAe;IAIjD,IAAI,OAAO,IAAI,MAAM,CAEpB;IAGD,QAAQ,CAAC,WAAW,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;
|
|
1
|
+
{"version":3,"file":"ReconnectPolicy.d.ts","sourceRoot":"","sources":["../../../queries/ReconnectPolicy.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAczE,qBAAa,eAAgB,YAAW,gBAAgB;IAYhD,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAdhC,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,MAAM,CAAC,CAAgC;gBAU1B,YAAY,GAAE,MAAY,EAC1B,eAAe,GAAE,MAAY,EAC7B,YAAY,GAAE,MAAY,EAC1B,WAAW,GAAE,MAAe;IAIjD,IAAI,OAAO,IAAI,MAAM,CAEpB;IAGD,QAAQ,CAAC,WAAW,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;IAkBhE,KAAK,IAAI,IAAI;IAMb,MAAM,IAAI,IAAI;CAMjB"}
|
|
@@ -21,6 +21,7 @@ class ReconnectPolicy {
|
|
|
21
21
|
console.log(`Reconnect: abandoned after ${this._maxAttempts} attempts for '${label}'`);
|
|
22
22
|
return false;
|
|
23
23
|
}
|
|
24
|
+
this.cancel();
|
|
24
25
|
this._attempt++;
|
|
25
26
|
const delay = Math.min(this._initialDelayMs + this._delayStepMs * this._attempt, this._maxDelayMs);
|
|
26
27
|
console.log(`Reconnect: attempt ${this._attempt} in ${delay}ms for '${label}'`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReconnectPolicy.js","sources":["../../../queries/ReconnectPolicy.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { IReconnectPolicy, ReconnectCallback } from './IReconnectPolicy';\n\n/**\n * Default exponential back-off reconnect policy shared by all observable query connections.\n *\n * Delay formula: `Math.min(initialDelayMs + delayStepMs * attempt, maxDelayMs)`\n *\n * With the default parameters:\n * - Attempt 1 → 1 000 ms\n * - Attempt 2 → 1 500 ms\n * - …\n * - Attempt 19+ → 10 000 ms (capped)\n * - After 100 attempts → abandoned (returns `false`)\n */\nexport class ReconnectPolicy implements IReconnectPolicy {\n private _attempt = 0;\n private _timer?: ReturnType<typeof setTimeout>;\n\n /**\n * Initializes a new instance of {@link ReconnectPolicy}.\n * @param {number} maxAttempts Maximum number of reconnect attempts before giving up (default: 100).\n * @param {number} initialDelayMs Base delay in milliseconds added to every attempt (default: 500).\n * @param {number} delayStepMs Additional delay per attempt in milliseconds (default: 500).\n * @param {number} maxDelayMs Upper bound on the computed delay in milliseconds (default: 10 000).\n */\n constructor(\n private readonly _maxAttempts: number = 100,\n private readonly _initialDelayMs: number = 500,\n private readonly _delayStepMs: number = 500,\n private readonly _maxDelayMs: number = 10_000,\n ) {}\n\n /** @inheritdoc */\n get attempt(): number {\n return this._attempt;\n }\n\n /** @inheritdoc */\n schedule(onReconnect: ReconnectCallback, label: string): boolean {\n if (this._attempt >= this._maxAttempts) {\n console.log(`Reconnect: abandoned after ${this._maxAttempts} attempts for '${label}'`);\n return false;\n }\n\n this._attempt++;\n const delay = Math.min(this._initialDelayMs + this._delayStepMs * this._attempt, this._maxDelayMs);\n console.log(`Reconnect: attempt ${this._attempt} in ${delay}ms for '${label}'`);\n this._timer = setTimeout(onReconnect, delay);\n return true;\n }\n\n /** @inheritdoc */\n reset(): void {\n this._attempt = 0;\n this.cancel();\n }\n\n /** @inheritdoc */\n cancel(): void {\n if (this._timer) {\n clearTimeout(this._timer);\n this._timer = undefined;\n }\n }\n}\n"],"names":[],"mappings":";;MAiBa,eAAe,CAAA;AAYH,IAAA,YAAA;AACA,IAAA,eAAA;AACA,IAAA,YAAA;AACA,IAAA,WAAA;IAdb,QAAQ,GAAG,CAAC;AACZ,IAAA,MAAM;IASd,WAAA,CACqB,YAAA,GAAuB,GAAG,EAC1B,eAAA,GAA0B,GAAG,EAC7B,YAAA,GAAuB,GAAG,EAC1B,WAAA,GAAsB,MAAM,EAAA;QAH5B,IAAA,CAAA,YAAY,GAAZ,YAAY;QACZ,IAAA,CAAA,eAAe,GAAf,eAAe;QACf,IAAA,CAAA,YAAY,GAAZ,YAAY;QACZ,IAAA,CAAA,WAAW,GAAX,WAAW;IAC7B;AAGH,IAAA,IAAI,OAAO,GAAA;QACP,OAAO,IAAI,CAAC,QAAQ;IACxB;IAGA,QAAQ,CAAC,WAA8B,EAAE,KAAa,EAAA;QAClD,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,YAAY,EAAE;YACpC,OAAO,CAAC,GAAG,CAAC,CAAA,2BAAA,EAA8B,IAAI,CAAC,YAAY,CAAA,eAAA,EAAkB,KAAK,CAAA,CAAA,CAAG,CAAC;AACtF,YAAA,OAAO,KAAK;QAChB;
|
|
1
|
+
{"version":3,"file":"ReconnectPolicy.js","sources":["../../../queries/ReconnectPolicy.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { IReconnectPolicy, ReconnectCallback } from './IReconnectPolicy';\n\n/**\n * Default exponential back-off reconnect policy shared by all observable query connections.\n *\n * Delay formula: `Math.min(initialDelayMs + delayStepMs * attempt, maxDelayMs)`\n *\n * With the default parameters:\n * - Attempt 1 → 1 000 ms\n * - Attempt 2 → 1 500 ms\n * - …\n * - Attempt 19+ → 10 000 ms (capped)\n * - After 100 attempts → abandoned (returns `false`)\n */\nexport class ReconnectPolicy implements IReconnectPolicy {\n private _attempt = 0;\n private _timer?: ReturnType<typeof setTimeout>;\n\n /**\n * Initializes a new instance of {@link ReconnectPolicy}.\n * @param {number} maxAttempts Maximum number of reconnect attempts before giving up (default: 100).\n * @param {number} initialDelayMs Base delay in milliseconds added to every attempt (default: 500).\n * @param {number} delayStepMs Additional delay per attempt in milliseconds (default: 500).\n * @param {number} maxDelayMs Upper bound on the computed delay in milliseconds (default: 10 000).\n */\n constructor(\n private readonly _maxAttempts: number = 100,\n private readonly _initialDelayMs: number = 500,\n private readonly _delayStepMs: number = 500,\n private readonly _maxDelayMs: number = 10_000,\n ) {}\n\n /** @inheritdoc */\n get attempt(): number {\n return this._attempt;\n }\n\n /** @inheritdoc */\n schedule(onReconnect: ReconnectCallback, label: string): boolean {\n if (this._attempt >= this._maxAttempts) {\n console.log(`Reconnect: abandoned after ${this._maxAttempts} attempts for '${label}'`);\n return false;\n }\n\n // Cancel any pending reconnect timer so we don't fire multiple\n // concurrent reconnect attempts when schedule() is called rapidly.\n this.cancel();\n\n this._attempt++;\n const delay = Math.min(this._initialDelayMs + this._delayStepMs * this._attempt, this._maxDelayMs);\n console.log(`Reconnect: attempt ${this._attempt} in ${delay}ms for '${label}'`);\n this._timer = setTimeout(onReconnect, delay);\n return true;\n }\n\n /** @inheritdoc */\n reset(): void {\n this._attempt = 0;\n this.cancel();\n }\n\n /** @inheritdoc */\n cancel(): void {\n if (this._timer) {\n clearTimeout(this._timer);\n this._timer = undefined;\n }\n }\n}\n"],"names":[],"mappings":";;MAiBa,eAAe,CAAA;AAYH,IAAA,YAAA;AACA,IAAA,eAAA;AACA,IAAA,YAAA;AACA,IAAA,WAAA;IAdb,QAAQ,GAAG,CAAC;AACZ,IAAA,MAAM;IASd,WAAA,CACqB,YAAA,GAAuB,GAAG,EAC1B,eAAA,GAA0B,GAAG,EAC7B,YAAA,GAAuB,GAAG,EAC1B,WAAA,GAAsB,MAAM,EAAA;QAH5B,IAAA,CAAA,YAAY,GAAZ,YAAY;QACZ,IAAA,CAAA,eAAe,GAAf,eAAe;QACf,IAAA,CAAA,YAAY,GAAZ,YAAY;QACZ,IAAA,CAAA,WAAW,GAAX,WAAW;IAC7B;AAGH,IAAA,IAAI,OAAO,GAAA;QACP,OAAO,IAAI,CAAC,QAAQ;IACxB;IAGA,QAAQ,CAAC,WAA8B,EAAE,KAAa,EAAA;QAClD,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,YAAY,EAAE;YACpC,OAAO,CAAC,GAAG,CAAC,CAAA,2BAAA,EAA8B,IAAI,CAAC,YAAY,CAAA,eAAA,EAAkB,KAAK,CAAA,CAAA,CAAG,CAAC;AACtF,YAAA,OAAO,KAAK;QAChB;QAIA,IAAI,CAAC,MAAM,EAAE;QAEb,IAAI,CAAC,QAAQ,EAAE;QACf,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,WAAW,CAAC;AAClG,QAAA,OAAO,CAAC,GAAG,CAAC,CAAA,mBAAA,EAAsB,IAAI,CAAC,QAAQ,CAAA,IAAA,EAAO,KAAK,CAAA,QAAA,EAAW,KAAK,CAAA,CAAA,CAAG,CAAC;QAC/E,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC,WAAW,EAAE,KAAK,CAAC;AAC5C,QAAA,OAAO,IAAI;IACf;IAGA,KAAK,GAAA;AACD,QAAA,IAAI,CAAC,QAAQ,GAAG,CAAC;QACjB,IAAI,CAAC,MAAM,EAAE;IACjB;IAGA,MAAM,GAAA;AACF,QAAA,IAAI,IAAI,CAAC,MAAM,EAAE;AACb,YAAA,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC;AACzB,YAAA,IAAI,CAAC,MAAM,GAAG,SAAS;QAC3B;IACJ;AACH;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ServerSentEventHubConnection.d.ts","sourceRoot":"","sources":["../../../queries/ServerSentEventHubConnection.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtD,OAAO,EAA8B,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAoB3F,qBAAa,4BAA6B,YAAW,6BAA6B;IAwB1E,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAE9B,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO;IA7B5B,OAAO,CAAC,YAAY,CAAC,CAAc;IACnC,OAAO,CAAC,aAAa,CAAC,CAAS;IAC/B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,qBAAqB,CAA8C;IAC3E,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,eAAe,CAAgB;IACvC,OAAO,CAAC,oBAAoB,CAAC,CAAgC;IAC7D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;gBAe/B,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,MAAM,EACvB,aAAa,EAAE,MAAM,EACtC,mBAAmB,GAAE,MAAc,EAClB,iBAAiB,GAAE,MAAc,EACjC,OAAO,GAAE,gBAAwC;
|
|
1
|
+
{"version":3,"file":"ServerSentEventHubConnection.d.ts","sourceRoot":"","sources":["../../../queries/ServerSentEventHubConnection.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtD,OAAO,EAA8B,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAoB3F,qBAAa,4BAA6B,YAAW,6BAA6B;IAwB1E,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAE9B,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO;IA7B5B,OAAO,CAAC,YAAY,CAAC,CAAc;IACnC,OAAO,CAAC,aAAa,CAAC,CAAS;IAC/B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,qBAAqB,CAA8C;IAC3E,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,eAAe,CAAgB;IACvC,OAAO,CAAC,oBAAoB,CAAC,CAAgC;IAC7D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;gBAe/B,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,MAAM,EACvB,aAAa,EAAE,MAAM,EACtC,mBAAmB,GAAE,MAAc,EAClB,iBAAiB,GAAE,MAAc,EACjC,OAAO,GAAE,gBAAwC;IAqBtE,IAAI,UAAU,IAAI,MAAM,CAEvB;IAGD,IAAI,eAAe,IAAI,MAAM,CAE5B;IAGD,IAAI,cAAc,IAAI,MAAM,CAG3B;IAGD,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,YAAY,CAAC,GAAG,CAAC,GAAG,IAAI;IAe3F,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAclC,OAAO,IAAI,IAAI;IAYf,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,KAAK;IAUb,OAAO,CAAC,eAAe;IAwCvB,OAAO,CAAC,SAAS;IAwBjB,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,aAAa;IA2BrB,OAAO,CAAC,eAAe;IAcvB,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,aAAa;IA0BrB,OAAO,CAAC,eAAe;CAkB1B"}
|
|
@@ -29,12 +29,13 @@ class ServerSentEventHubConnection {
|
|
|
29
29
|
this._microservice = _microservice;
|
|
30
30
|
this._connectTimeoutMs = _connectTimeoutMs;
|
|
31
31
|
this._policy = _policy;
|
|
32
|
+
const idleThresholdMs = Math.round(keepAliveIntervalMs * 1.5);
|
|
32
33
|
this._keepAlive = new HubConnectionKeepAlive.HubConnectionKeepAlive(keepAliveIntervalMs, () => {
|
|
33
34
|
if (!this._disconnected && this._subscriptions.size > 0) {
|
|
34
|
-
console.warn(`SSE hub: no messages received for ${
|
|
35
|
+
console.warn(`SSE hub: no messages received for ${idleThresholdMs}ms, reconnecting '${this._sseUrl}'`);
|
|
35
36
|
this.reconnect();
|
|
36
37
|
}
|
|
37
|
-
});
|
|
38
|
+
}, idleThresholdMs);
|
|
38
39
|
}
|
|
39
40
|
get queryCount() {
|
|
40
41
|
return this._subscriptions.size;
|
|
@@ -221,8 +222,14 @@ class ServerSentEventHubConnection {
|
|
|
221
222
|
method: 'POST',
|
|
222
223
|
headers: { 'Content-Type': 'application/json', ...customHeaders },
|
|
223
224
|
body: JSON.stringify(body),
|
|
225
|
+
}).then(response => {
|
|
226
|
+
if (!response.ok) {
|
|
227
|
+
console.warn(`SSE hub: subscribe POST for '${queryId}' returned ${response.status}, reconnecting`);
|
|
228
|
+
this.reconnect();
|
|
229
|
+
}
|
|
224
230
|
}).catch(error => {
|
|
225
|
-
console.error(`SSE hub: subscribe POST failed for '${queryId}'`, error);
|
|
231
|
+
console.error(`SSE hub: subscribe POST failed for '${queryId}', reconnecting`, error);
|
|
232
|
+
this.reconnect();
|
|
226
233
|
});
|
|
227
234
|
}
|
|
228
235
|
sendUnsubscribe(queryId) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ServerSentEventHubConnection.js","sources":["../../../queries/ServerSentEventHubConnection.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { Globals } from '../Globals';\nimport { IObservableQueryHubConnection } from './IObservableQueryHubConnection';\nimport { DataReceived } from './ObservableQueryConnection';\nimport { HubConnectionKeepAlive } from './HubConnectionKeepAlive';\nimport { IReconnectPolicy } from './IReconnectPolicy';\nimport { ReconnectPolicy } from './ReconnectPolicy';\nimport { QueryResult } from './QueryResult';\nimport { HubMessage, HubMessageType, SubscriptionRequest } from './WebSocketHubConnection';\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\ninterface ActiveSubscription {\n request: SubscriptionRequest;\n callback: DataReceived<any>;\n}\n\n/**\n * A multiplexed SSE hub connection that uses EventSource for server→client streaming\n * and fetch POST requests for client→server subscribe/unsubscribe commands.\n *\n * Protocol:\n * 1. Open EventSource to the SSE hub endpoint.\n * 2. Server sends a {@link HubMessageType.Connected} message with the connection identifier.\n * 3. Client sends POST to subscribe/unsubscribe endpoints using the connection identifier.\n * 4. Server streams {@link HubMessageType.QueryResult} messages tagged with queryId.\n * 5. When EventSource closes, server cleans up all subscriptions for this connection.\n */\nexport class ServerSentEventHubConnection implements IObservableQueryHubConnection {\n private _eventSource?: EventSource;\n private _connectionId?: string;\n private _disconnected = false;\n private _subscriptions: Map<string, ActiveSubscription> = new Map();\n private _pendingSubscriptions: Map<string, ActiveSubscription> = new Map();\n private _lastPongLatency: number = 0;\n private _latencySamples: number[] = [];\n private _connectTimeoutTimer?: ReturnType<typeof setTimeout>;\n private readonly _keepAlive: HubConnectionKeepAlive;\n\n /**\n * Initializes a new instance of {@link ServerSentEventHubConnection}.\n * @param {string} _sseUrl The SSE hub endpoint URL (e.g. `http://localhost:5000/.cratis/queries/sse`).\n * @param {string} _subscribeUrl The subscribe POST endpoint URL.\n * @param {string} _unsubscribeUrl The unsubscribe POST endpoint URL.\n * @param {string} _microservice The microservice name to pass as a query argument.\n * @param {number} keepAliveIntervalMs How long without any server message before the connection\n * is considered stale and a reconnect is forced (default: 30 000 ms).\n * @param {number} connectTimeoutMs How long to wait for the {@link HubMessageType.Connected}\n * message after the HTTP connection opens before giving up and retrying (default: 15 000 ms).\n * @param {IReconnectPolicy} _policy The reconnect policy to use (default: {@link ReconnectPolicy}).\n */\n constructor(\n private readonly _sseUrl: string,\n private readonly _subscribeUrl: string,\n private readonly _unsubscribeUrl: string,\n private readonly _microservice: string,\n keepAliveIntervalMs: number = 30000,\n private readonly _connectTimeoutMs: number = 15000,\n private readonly _policy: IReconnectPolicy = new ReconnectPolicy()\n ) {\n // SSE is server→client only: the client cannot send pings. Instead we watch for\n // inactivity — if the server stops sending messages (including its own keep-alive\n // pings) for the entire interval, the connection is stale and we reconnect.\n this._keepAlive = new HubConnectionKeepAlive(keepAliveIntervalMs, () => {\n if (!this._disconnected && this._subscriptions.size > 0) {\n console.warn(`SSE hub: no messages received for ${keepAliveIntervalMs}ms, reconnecting '${this._sseUrl}'`);\n this.reconnect();\n }\n });\n }\n\n /** @inheritdoc */\n get queryCount(): number {\n return this._subscriptions.size;\n }\n\n /** @inheritdoc */\n get lastPingLatency(): number {\n return this._lastPongLatency;\n }\n\n /** @inheritdoc */\n get averageLatency(): number {\n if (this._latencySamples.length === 0) return 0;\n return this._latencySamples.reduce((a, b) => a + b, 0) / this._latencySamples.length;\n }\n\n /** @inheritdoc */\n subscribe(queryId: string, request: SubscriptionRequest, callback: DataReceived<any>): void {\n const sub: ActiveSubscription = { request, callback };\n this._subscriptions.set(queryId, sub);\n\n this.ensureConnected();\n\n if (this._connectionId) {\n this.sendSubscribe(queryId, request);\n } else {\n // Not yet connected, queue for when Connected message arrives.\n this._pendingSubscriptions.set(queryId, sub);\n }\n }\n\n /** @inheritdoc */\n unsubscribe(queryId: string): void {\n this._subscriptions.delete(queryId);\n this._pendingSubscriptions.delete(queryId);\n\n if (this._connectionId) {\n this.sendUnsubscribe(queryId);\n }\n\n if (this._subscriptions.size === 0) {\n this.close();\n }\n }\n\n /** @inheritdoc */\n dispose(): void {\n this._disconnected = true;\n this._subscriptions.clear();\n this._pendingSubscriptions.clear();\n this._policy.cancel();\n this._keepAlive.stop();\n this.clearConnectTimeout();\n this._eventSource?.close();\n this._eventSource = undefined;\n this._connectionId = undefined;\n }\n\n private ensureConnected(): void {\n if (this._disconnected) {\n this._disconnected = false;\n }\n\n if (this._eventSource && this._eventSource.readyState !== EventSource.CLOSED) {\n return;\n }\n\n this.openEventSource();\n }\n\n private close(): void {\n this._disconnected = true;\n this._policy.cancel();\n this._keepAlive.stop();\n this.clearConnectTimeout();\n this._eventSource?.close();\n this._eventSource = undefined;\n this._connectionId = undefined;\n }\n\n private openEventSource(): void {\n let url = this._sseUrl;\n if (this._microservice?.length > 0) {\n const param = `${Globals.microserviceWSQueryArgument}=${encodeURIComponent(this._microservice)}`;\n url += (url.includes('?') ? '&' : '?') + param;\n }\n\n this._connectionId = undefined;\n this._eventSource = new EventSource(url);\n\n this._eventSource.onopen = () => {\n if (this._disconnected) return;\n console.log(`SSE hub connection established: '${url}'`);\n this._policy.reset();\n this._keepAlive.start();\n\n // If the server does not send a Connected message within the timeout, the\n // connection is broken. Close and retry via the reconnect policy.\n this.clearConnectTimeout();\n this._connectTimeoutTimer = setTimeout(() => {\n if (!this._disconnected && !this._connectionId) {\n console.warn(`SSE hub: no Connected message within ${this._connectTimeoutMs}ms, retrying '${url}'`);\n this.reconnect();\n }\n }, this._connectTimeoutMs);\n };\n\n this._eventSource.onmessage = (event: MessageEvent) => {\n if (this._disconnected) return;\n this._keepAlive.recordActivity();\n this.handleMessage(event.data as string);\n };\n\n this._eventSource.onerror = () => {\n if (this._disconnected) return;\n console.warn(`SSE hub connection error: '${url}'`);\n this.reconnect();\n };\n }\n\n private reconnect(): void {\n this._keepAlive.stop();\n this.clearConnectTimeout();\n\n // Close the EventSource so the reconnect policy manages the schedule.\n this._eventSource?.close();\n this._eventSource = undefined;\n this._connectionId = undefined;\n\n // Move all active subscriptions to pending so they re-subscribe when\n // the next Connected message arrives after the managed reconnect.\n for (const [queryId, sub] of this._subscriptions) {\n this._pendingSubscriptions.set(queryId, sub);\n }\n\n if (this._subscriptions.size === 0) return;\n\n this._policy.schedule(() => {\n if (!this._disconnected && this._subscriptions.size > 0) {\n this.openEventSource();\n }\n }, this._sseUrl);\n }\n\n private clearConnectTimeout(): void {\n if (this._connectTimeoutTimer !== undefined) {\n clearTimeout(this._connectTimeoutTimer);\n this._connectTimeoutTimer = undefined;\n }\n }\n\n private handleMessage(rawData: string): void {\n try {\n const message = JSON.parse(rawData) as HubMessage;\n\n switch (message.type) {\n case HubMessageType.Connected:\n this.handleConnected(message);\n break;\n case HubMessageType.QueryResult:\n this.handleQueryResult(message);\n break;\n case HubMessageType.Ping:\n // Server-sent keep-alive ping — activity already recorded in onmessage.\n break;\n case HubMessageType.Unauthorized:\n console.warn(`SSE hub: query '${message.queryId}' unauthorized`);\n this.handleUnauthorized(message);\n break;\n case HubMessageType.Error:\n console.error(`SSE hub: query '${message.queryId}' error:`, message.payload);\n break;\n }\n } catch (error) {\n console.error('SSE hub: error parsing message', error);\n }\n }\n\n private handleConnected(message: HubMessage): void {\n this._connectionId = message.payload as string;\n console.log(`SSE hub: connected with id '${this._connectionId}'`);\n\n // Connected message arrived — cancel the connect timeout.\n this.clearConnectTimeout();\n\n // Send all pending subscriptions now that we have a connection ID.\n for (const [queryId, sub] of this._pendingSubscriptions) {\n this.sendSubscribe(queryId, sub.request);\n }\n this._pendingSubscriptions.clear();\n }\n\n private handleQueryResult(message: HubMessage): void {\n if (!message.queryId) return;\n\n const sub = this._subscriptions.get(message.queryId);\n if (!sub) return;\n\n const result = message.payload as QueryResult<any>;\n sub.callback(result);\n }\n\n private handleUnauthorized(message: HubMessage): void {\n if (!message.queryId) return;\n\n const sub = this._subscriptions.get(message.queryId);\n if (!sub) return;\n\n this._subscriptions.delete(message.queryId);\n this._pendingSubscriptions.delete(message.queryId);\n sub.callback(QueryResult.unauthorized());\n }\n\n private sendSubscribe(queryId: string, request: SubscriptionRequest): void {\n if (!this._connectionId) return;\n\n const body = {\n connectionId: this._connectionId,\n queryId,\n request,\n };\n\n const customHeaders = Globals.httpHeadersCallback?.() ?? {};\n\n fetch(this._subscribeUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify(body),\n }).catch(error => {\n console.error(`SSE hub: subscribe POST failed for '${queryId}'`, error);\n });\n }\n\n private sendUnsubscribe(queryId: string): void {\n if (!this._connectionId) return;\n\n const body = {\n connectionId: this._connectionId,\n queryId,\n };\n\n const customHeaders = Globals.httpHeadersCallback?.() ?? {};\n\n fetch(this._unsubscribeUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify(body),\n }).catch(error => {\n console.error(`SSE hub: unsubscribe POST failed for '${queryId}'`, error);\n });\n }\n}\n"],"names":["ReconnectPolicy","HubConnectionKeepAlive","Globals","HubMessageType","QueryResult"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ServerSentEventHubConnection.js","sources":["../../../queries/ServerSentEventHubConnection.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { Globals } from '../Globals';\nimport { IObservableQueryHubConnection } from './IObservableQueryHubConnection';\nimport { DataReceived } from './ObservableQueryConnection';\nimport { HubConnectionKeepAlive } from './HubConnectionKeepAlive';\nimport { IReconnectPolicy } from './IReconnectPolicy';\nimport { ReconnectPolicy } from './ReconnectPolicy';\nimport { QueryResult } from './QueryResult';\nimport { HubMessage, HubMessageType, SubscriptionRequest } from './WebSocketHubConnection';\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\ninterface ActiveSubscription {\n request: SubscriptionRequest;\n callback: DataReceived<any>;\n}\n\n/**\n * A multiplexed SSE hub connection that uses EventSource for server→client streaming\n * and fetch POST requests for client→server subscribe/unsubscribe commands.\n *\n * Protocol:\n * 1. Open EventSource to the SSE hub endpoint.\n * 2. Server sends a {@link HubMessageType.Connected} message with the connection identifier.\n * 3. Client sends POST to subscribe/unsubscribe endpoints using the connection identifier.\n * 4. Server streams {@link HubMessageType.QueryResult} messages tagged with queryId.\n * 5. When EventSource closes, server cleans up all subscriptions for this connection.\n */\nexport class ServerSentEventHubConnection implements IObservableQueryHubConnection {\n private _eventSource?: EventSource;\n private _connectionId?: string;\n private _disconnected = false;\n private _subscriptions: Map<string, ActiveSubscription> = new Map();\n private _pendingSubscriptions: Map<string, ActiveSubscription> = new Map();\n private _lastPongLatency: number = 0;\n private _latencySamples: number[] = [];\n private _connectTimeoutTimer?: ReturnType<typeof setTimeout>;\n private readonly _keepAlive: HubConnectionKeepAlive;\n\n /**\n * Initializes a new instance of {@link ServerSentEventHubConnection}.\n * @param {string} _sseUrl The SSE hub endpoint URL (e.g. `http://localhost:5000/.cratis/queries/sse`).\n * @param {string} _subscribeUrl The subscribe POST endpoint URL.\n * @param {string} _unsubscribeUrl The unsubscribe POST endpoint URL.\n * @param {string} _microservice The microservice name to pass as a query argument.\n * @param {number} keepAliveIntervalMs How long without any server message before the connection\n * is considered stale and a reconnect is forced (default: 30 000 ms).\n * @param {number} connectTimeoutMs How long to wait for the {@link HubMessageType.Connected}\n * message after the HTTP connection opens before giving up and retrying (default: 15 000 ms).\n * @param {IReconnectPolicy} _policy The reconnect policy to use (default: {@link ReconnectPolicy}).\n */\n constructor(\n private readonly _sseUrl: string,\n private readonly _subscribeUrl: string,\n private readonly _unsubscribeUrl: string,\n private readonly _microservice: string,\n keepAliveIntervalMs: number = 30000,\n private readonly _connectTimeoutMs: number = 15000,\n private readonly _policy: IReconnectPolicy = new ReconnectPolicy()\n ) {\n // SSE is server→client only: the client cannot send pings. Instead we watch for\n // inactivity — if the server stops sending messages (including its own keep-alive\n // pings) for the entire idle threshold, the connection is stale and we reconnect.\n //\n // The idle threshold is set to 1.5× the check interval so the server's keep-alive\n // ping (which fires on the same cadence) has time to arrive over the network before\n // the client declares the connection dead. Without this tolerance the client's timer\n // and the server's timer race — the client often fires first and reconnects\n // unnecessarily.\n const idleThresholdMs = Math.round(keepAliveIntervalMs * 1.5);\n this._keepAlive = new HubConnectionKeepAlive(keepAliveIntervalMs, () => {\n if (!this._disconnected && this._subscriptions.size > 0) {\n console.warn(`SSE hub: no messages received for ${idleThresholdMs}ms, reconnecting '${this._sseUrl}'`);\n this.reconnect();\n }\n }, idleThresholdMs);\n }\n\n /** @inheritdoc */\n get queryCount(): number {\n return this._subscriptions.size;\n }\n\n /** @inheritdoc */\n get lastPingLatency(): number {\n return this._lastPongLatency;\n }\n\n /** @inheritdoc */\n get averageLatency(): number {\n if (this._latencySamples.length === 0) return 0;\n return this._latencySamples.reduce((a, b) => a + b, 0) / this._latencySamples.length;\n }\n\n /** @inheritdoc */\n subscribe(queryId: string, request: SubscriptionRequest, callback: DataReceived<any>): void {\n const sub: ActiveSubscription = { request, callback };\n this._subscriptions.set(queryId, sub);\n\n this.ensureConnected();\n\n if (this._connectionId) {\n this.sendSubscribe(queryId, request);\n } else {\n // Not yet connected, queue for when Connected message arrives.\n this._pendingSubscriptions.set(queryId, sub);\n }\n }\n\n /** @inheritdoc */\n unsubscribe(queryId: string): void {\n this._subscriptions.delete(queryId);\n this._pendingSubscriptions.delete(queryId);\n\n if (this._connectionId) {\n this.sendUnsubscribe(queryId);\n }\n\n if (this._subscriptions.size === 0) {\n this.close();\n }\n }\n\n /** @inheritdoc */\n dispose(): void {\n this._disconnected = true;\n this._subscriptions.clear();\n this._pendingSubscriptions.clear();\n this._policy.cancel();\n this._keepAlive.stop();\n this.clearConnectTimeout();\n this._eventSource?.close();\n this._eventSource = undefined;\n this._connectionId = undefined;\n }\n\n private ensureConnected(): void {\n if (this._disconnected) {\n this._disconnected = false;\n }\n\n if (this._eventSource && this._eventSource.readyState !== EventSource.CLOSED) {\n return;\n }\n\n this.openEventSource();\n }\n\n private close(): void {\n this._disconnected = true;\n this._policy.cancel();\n this._keepAlive.stop();\n this.clearConnectTimeout();\n this._eventSource?.close();\n this._eventSource = undefined;\n this._connectionId = undefined;\n }\n\n private openEventSource(): void {\n let url = this._sseUrl;\n if (this._microservice?.length > 0) {\n const param = `${Globals.microserviceWSQueryArgument}=${encodeURIComponent(this._microservice)}`;\n url += (url.includes('?') ? '&' : '?') + param;\n }\n\n this._connectionId = undefined;\n this._eventSource = new EventSource(url);\n\n this._eventSource.onopen = () => {\n if (this._disconnected) return;\n console.log(`SSE hub connection established: '${url}'`);\n this._policy.reset();\n this._keepAlive.start();\n\n // If the server does not send a Connected message within the timeout, the\n // connection is broken. Close and retry via the reconnect policy.\n this.clearConnectTimeout();\n this._connectTimeoutTimer = setTimeout(() => {\n if (!this._disconnected && !this._connectionId) {\n console.warn(`SSE hub: no Connected message within ${this._connectTimeoutMs}ms, retrying '${url}'`);\n this.reconnect();\n }\n }, this._connectTimeoutMs);\n };\n\n this._eventSource.onmessage = (event: MessageEvent) => {\n if (this._disconnected) return;\n this._keepAlive.recordActivity();\n this.handleMessage(event.data as string);\n };\n\n this._eventSource.onerror = () => {\n if (this._disconnected) return;\n console.warn(`SSE hub connection error: '${url}'`);\n this.reconnect();\n };\n }\n\n private reconnect(): void {\n this._keepAlive.stop();\n this.clearConnectTimeout();\n\n // Close the EventSource so the reconnect policy manages the schedule.\n this._eventSource?.close();\n this._eventSource = undefined;\n this._connectionId = undefined;\n\n // Move all active subscriptions to pending so they re-subscribe when\n // the next Connected message arrives after the managed reconnect.\n for (const [queryId, sub] of this._subscriptions) {\n this._pendingSubscriptions.set(queryId, sub);\n }\n\n if (this._subscriptions.size === 0) return;\n\n this._policy.schedule(() => {\n if (!this._disconnected && this._subscriptions.size > 0) {\n this.openEventSource();\n }\n }, this._sseUrl);\n }\n\n private clearConnectTimeout(): void {\n if (this._connectTimeoutTimer !== undefined) {\n clearTimeout(this._connectTimeoutTimer);\n this._connectTimeoutTimer = undefined;\n }\n }\n\n private handleMessage(rawData: string): void {\n try {\n const message = JSON.parse(rawData) as HubMessage;\n\n switch (message.type) {\n case HubMessageType.Connected:\n this.handleConnected(message);\n break;\n case HubMessageType.QueryResult:\n this.handleQueryResult(message);\n break;\n case HubMessageType.Ping:\n // Server-sent keep-alive ping — activity already recorded in onmessage.\n break;\n case HubMessageType.Unauthorized:\n console.warn(`SSE hub: query '${message.queryId}' unauthorized`);\n this.handleUnauthorized(message);\n break;\n case HubMessageType.Error:\n console.error(`SSE hub: query '${message.queryId}' error:`, message.payload);\n break;\n }\n } catch (error) {\n console.error('SSE hub: error parsing message', error);\n }\n }\n\n private handleConnected(message: HubMessage): void {\n this._connectionId = message.payload as string;\n console.log(`SSE hub: connected with id '${this._connectionId}'`);\n\n // Connected message arrived — cancel the connect timeout.\n this.clearConnectTimeout();\n\n // Send all pending subscriptions now that we have a connection ID.\n for (const [queryId, sub] of this._pendingSubscriptions) {\n this.sendSubscribe(queryId, sub.request);\n }\n this._pendingSubscriptions.clear();\n }\n\n private handleQueryResult(message: HubMessage): void {\n if (!message.queryId) return;\n\n const sub = this._subscriptions.get(message.queryId);\n if (!sub) return;\n\n const result = message.payload as QueryResult<any>;\n sub.callback(result);\n }\n\n private handleUnauthorized(message: HubMessage): void {\n if (!message.queryId) return;\n\n const sub = this._subscriptions.get(message.queryId);\n if (!sub) return;\n\n this._subscriptions.delete(message.queryId);\n this._pendingSubscriptions.delete(message.queryId);\n sub.callback(QueryResult.unauthorized());\n }\n\n private sendSubscribe(queryId: string, request: SubscriptionRequest): void {\n if (!this._connectionId) return;\n\n const body = {\n connectionId: this._connectionId,\n queryId,\n request,\n };\n\n const customHeaders = Globals.httpHeadersCallback?.() ?? {};\n\n fetch(this._subscribeUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify(body),\n }).then(response => {\n if (!response.ok) {\n console.warn(`SSE hub: subscribe POST for '${queryId}' returned ${response.status}, reconnecting`);\n this.reconnect();\n }\n }).catch(error => {\n console.error(`SSE hub: subscribe POST failed for '${queryId}', reconnecting`, error);\n this.reconnect();\n });\n }\n\n private sendUnsubscribe(queryId: string): void {\n if (!this._connectionId) return;\n\n const body = {\n connectionId: this._connectionId,\n queryId,\n };\n\n const customHeaders = Globals.httpHeadersCallback?.() ?? {};\n\n fetch(this._unsubscribeUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify(body),\n }).catch(error => {\n console.error(`SSE hub: unsubscribe POST failed for '${queryId}'`, error);\n });\n }\n}\n"],"names":["ReconnectPolicy","HubConnectionKeepAlive","Globals","HubMessageType","QueryResult"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"respects_threshold.d.ts","sourceRoot":"","sources":["../../../../../queries/for_HubConnectionKeepAlive/when_started_with_custom_idle_threshold/respects_threshold.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cancels_previous_timer.d.ts","sourceRoot":"","sources":["../../../../../../queries/for_ReconnectPolicy/when_scheduling/and_rescheduled_before_first_fires/cancels_previous_timer.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"and_subscribe_post_fails.d.ts","sourceRoot":"","sources":["../../../../../queries/for_ServerSentEventHubConnection/when_subscribing/and_subscribe_post_fails.ts"],"names":[],"mappings":""}
|
|
@@ -3,7 +3,8 @@ export declare class HubConnectionKeepAlive {
|
|
|
3
3
|
private readonly _onIdle;
|
|
4
4
|
private _lastActivityTime;
|
|
5
5
|
private _timer?;
|
|
6
|
-
|
|
6
|
+
private readonly _idleThresholdMs;
|
|
7
|
+
constructor(_intervalMs: number, _onIdle: () => void, idleThresholdMs?: number);
|
|
7
8
|
start(): void;
|
|
8
9
|
stop(): void;
|
|
9
10
|
recordActivity(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HubConnectionKeepAlive.d.ts","sourceRoot":"","sources":["../../../queries/HubConnectionKeepAlive.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"HubConnectionKeepAlive.d.ts","sourceRoot":"","sources":["../../../queries/HubConnectionKeepAlive.ts"],"names":[],"mappings":"AAqBA,qBAAa,sBAAsB;IAgB3B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAhB5B,OAAO,CAAC,iBAAiB,CAAc;IACvC,OAAO,CAAC,MAAM,CAAC,CAAiC;IAChD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;gBAarB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,IAAI,EACpC,eAAe,CAAC,EAAE,MAAM;IAQ5B,KAAK,IAAI,IAAI;IAab,IAAI,IAAI,IAAI;IAWZ,cAAc,IAAI,IAAI;CAGzB"}
|
|
@@ -3,15 +3,17 @@ class HubConnectionKeepAlive {
|
|
|
3
3
|
_onIdle;
|
|
4
4
|
_lastActivityTime = Date.now();
|
|
5
5
|
_timer;
|
|
6
|
-
|
|
6
|
+
_idleThresholdMs;
|
|
7
|
+
constructor(_intervalMs, _onIdle, idleThresholdMs) {
|
|
7
8
|
this._intervalMs = _intervalMs;
|
|
8
9
|
this._onIdle = _onIdle;
|
|
10
|
+
this._idleThresholdMs = idleThresholdMs ?? _intervalMs;
|
|
9
11
|
}
|
|
10
12
|
start() {
|
|
11
13
|
this.stop();
|
|
12
14
|
this._lastActivityTime = Date.now();
|
|
13
15
|
this._timer = setInterval(() => {
|
|
14
|
-
if (Date.now() - this._lastActivityTime >= this.
|
|
16
|
+
if (Date.now() - this._lastActivityTime >= this._idleThresholdMs) {
|
|
15
17
|
this._onIdle();
|
|
16
18
|
}
|
|
17
19
|
}, this._intervalMs);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HubConnectionKeepAlive.js","sources":["../../../queries/HubConnectionKeepAlive.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\n/**\n * Manages keep-alive behavior for hub connections (both WebSocket and Server-Sent Events).\n *\n * Records connection activity (any message received or sent). An interval fires every\n * {@link intervalMs} milliseconds; if no activity has been recorded since the last
|
|
1
|
+
{"version":3,"file":"HubConnectionKeepAlive.js","sources":["../../../queries/HubConnectionKeepAlive.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\n/**\n * Manages keep-alive behavior for hub connections (both WebSocket and Server-Sent Events).\n *\n * Records connection activity (any message received or sent). An interval fires every\n * {@link intervalMs} milliseconds; if no activity has been recorded since the last\n * {@link idleThresholdMs} milliseconds the provided {@link onIdle} callback is invoked —\n * the caller decides whether to send a ping, trigger a reconnect, or take some other action.\n *\n * The idle threshold defaults to the check interval but can be set higher to tolerate\n * network latency between the server's keep-alive ping and the client's idle check.\n * For SSE connections, where the idle callback triggers a reconnect, a tolerance of\n * 1.5× the server's keep-alive interval prevents spurious reconnects caused by the\n * client's timer firing just before the server's ping arrives.\n *\n * Both {@link WebSocketHubConnection} and {@link ServerSentEventHubConnection} own one instance\n * of this class so the keep-alive logic is written once and behaves identically for both\n * transports.\n */\nexport class HubConnectionKeepAlive {\n private _lastActivityTime = Date.now();\n private _timer?: ReturnType<typeof setInterval>;\n private readonly _idleThresholdMs: number;\n\n /**\n * Initializes a new instance of {@link HubConnectionKeepAlive}.\n * @param {number} intervalMs How often (in milliseconds) to check for idle connections.\n * @param {() => void} onIdle Callback invoked when the interval fires and no activity has\n * been recorded within the idle threshold.\n * @param {number} idleThresholdMs Optional. How long (in milliseconds) without activity\n * before the connection is considered idle. Defaults to {@link intervalMs}. Set this\n * higher than {@link intervalMs} when the peer sends keep-alive messages on a similar\n * cadence to account for network latency and timer jitter.\n */\n constructor(\n private readonly _intervalMs: number,\n private readonly _onIdle: () => void,\n idleThresholdMs?: number,\n ) {\n this._idleThresholdMs = idleThresholdMs ?? _intervalMs;\n }\n\n /**\n * Start the keep-alive timer. Safe to call multiple times — a running timer is stopped first.\n */\n start(): void {\n this.stop();\n this._lastActivityTime = Date.now();\n this._timer = setInterval(() => {\n if (Date.now() - this._lastActivityTime >= this._idleThresholdMs) {\n this._onIdle();\n }\n }, this._intervalMs);\n }\n\n /**\n * Stop the keep-alive timer.\n */\n stop(): void {\n if (this._timer !== undefined) {\n clearInterval(this._timer);\n this._timer = undefined;\n }\n }\n\n /**\n * Record that the connection has been active (message sent or received).\n * Resets the idle timer so that a keep-alive is not sent while data is already flowing.\n */\n recordActivity(): void {\n this._lastActivityTime = Date.now();\n }\n}\n"],"names":[],"mappings":"MAqBa,sBAAsB,CAAA;AAgBV,IAAA,WAAA;AACA,IAAA,OAAA;AAhBb,IAAA,iBAAiB,GAAG,IAAI,CAAC,GAAG,EAAE;AAC9B,IAAA,MAAM;AACG,IAAA,gBAAgB;AAYjC,IAAA,WAAA,CACqB,WAAmB,EACnB,OAAmB,EACpC,eAAwB,EAAA;QAFP,IAAA,CAAA,WAAW,GAAX,WAAW;QACX,IAAA,CAAA,OAAO,GAAP,OAAO;AAGxB,QAAA,IAAI,CAAC,gBAAgB,GAAG,eAAe,IAAI,WAAW;IAC1D;IAKA,KAAK,GAAA;QACD,IAAI,CAAC,IAAI,EAAE;AACX,QAAA,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,GAAG,EAAE;AACnC,QAAA,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,MAAK;AAC3B,YAAA,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,iBAAiB,IAAI,IAAI,CAAC,gBAAgB,EAAE;gBAC9D,IAAI,CAAC,OAAO,EAAE;YAClB;AACJ,QAAA,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC;IACxB;IAKA,IAAI,GAAA;AACA,QAAA,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE;AAC3B,YAAA,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC;AAC1B,YAAA,IAAI,CAAC,MAAM,GAAG,SAAS;QAC3B;IACJ;IAMA,cAAc,GAAA;AACV,QAAA,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,GAAG,EAAE;IACvC;AACH;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReconnectPolicy.d.ts","sourceRoot":"","sources":["../../../queries/ReconnectPolicy.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAczE,qBAAa,eAAgB,YAAW,gBAAgB;IAYhD,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAdhC,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,MAAM,CAAC,CAAgC;gBAU1B,YAAY,GAAE,MAAY,EAC1B,eAAe,GAAE,MAAY,EAC7B,YAAY,GAAE,MAAY,EAC1B,WAAW,GAAE,MAAe;IAIjD,IAAI,OAAO,IAAI,MAAM,CAEpB;IAGD,QAAQ,CAAC,WAAW,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;
|
|
1
|
+
{"version":3,"file":"ReconnectPolicy.d.ts","sourceRoot":"","sources":["../../../queries/ReconnectPolicy.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAczE,qBAAa,eAAgB,YAAW,gBAAgB;IAYhD,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAdhC,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,MAAM,CAAC,CAAgC;gBAU1B,YAAY,GAAE,MAAY,EAC1B,eAAe,GAAE,MAAY,EAC7B,YAAY,GAAE,MAAY,EAC1B,WAAW,GAAE,MAAe;IAIjD,IAAI,OAAO,IAAI,MAAM,CAEpB;IAGD,QAAQ,CAAC,WAAW,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;IAkBhE,KAAK,IAAI,IAAI;IAMb,MAAM,IAAI,IAAI;CAMjB"}
|
|
@@ -19,6 +19,7 @@ class ReconnectPolicy {
|
|
|
19
19
|
console.log(`Reconnect: abandoned after ${this._maxAttempts} attempts for '${label}'`);
|
|
20
20
|
return false;
|
|
21
21
|
}
|
|
22
|
+
this.cancel();
|
|
22
23
|
this._attempt++;
|
|
23
24
|
const delay = Math.min(this._initialDelayMs + this._delayStepMs * this._attempt, this._maxDelayMs);
|
|
24
25
|
console.log(`Reconnect: attempt ${this._attempt} in ${delay}ms for '${label}'`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReconnectPolicy.js","sources":["../../../queries/ReconnectPolicy.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { IReconnectPolicy, ReconnectCallback } from './IReconnectPolicy';\n\n/**\n * Default exponential back-off reconnect policy shared by all observable query connections.\n *\n * Delay formula: `Math.min(initialDelayMs + delayStepMs * attempt, maxDelayMs)`\n *\n * With the default parameters:\n * - Attempt 1 → 1 000 ms\n * - Attempt 2 → 1 500 ms\n * - …\n * - Attempt 19+ → 10 000 ms (capped)\n * - After 100 attempts → abandoned (returns `false`)\n */\nexport class ReconnectPolicy implements IReconnectPolicy {\n private _attempt = 0;\n private _timer?: ReturnType<typeof setTimeout>;\n\n /**\n * Initializes a new instance of {@link ReconnectPolicy}.\n * @param {number} maxAttempts Maximum number of reconnect attempts before giving up (default: 100).\n * @param {number} initialDelayMs Base delay in milliseconds added to every attempt (default: 500).\n * @param {number} delayStepMs Additional delay per attempt in milliseconds (default: 500).\n * @param {number} maxDelayMs Upper bound on the computed delay in milliseconds (default: 10 000).\n */\n constructor(\n private readonly _maxAttempts: number = 100,\n private readonly _initialDelayMs: number = 500,\n private readonly _delayStepMs: number = 500,\n private readonly _maxDelayMs: number = 10_000,\n ) {}\n\n /** @inheritdoc */\n get attempt(): number {\n return this._attempt;\n }\n\n /** @inheritdoc */\n schedule(onReconnect: ReconnectCallback, label: string): boolean {\n if (this._attempt >= this._maxAttempts) {\n console.log(`Reconnect: abandoned after ${this._maxAttempts} attempts for '${label}'`);\n return false;\n }\n\n this._attempt++;\n const delay = Math.min(this._initialDelayMs + this._delayStepMs * this._attempt, this._maxDelayMs);\n console.log(`Reconnect: attempt ${this._attempt} in ${delay}ms for '${label}'`);\n this._timer = setTimeout(onReconnect, delay);\n return true;\n }\n\n /** @inheritdoc */\n reset(): void {\n this._attempt = 0;\n this.cancel();\n }\n\n /** @inheritdoc */\n cancel(): void {\n if (this._timer) {\n clearTimeout(this._timer);\n this._timer = undefined;\n }\n }\n}\n"],"names":[],"mappings":"MAiBa,eAAe,CAAA;AAYH,IAAA,YAAA;AACA,IAAA,eAAA;AACA,IAAA,YAAA;AACA,IAAA,WAAA;IAdb,QAAQ,GAAG,CAAC;AACZ,IAAA,MAAM;IASd,WAAA,CACqB,YAAA,GAAuB,GAAG,EAC1B,eAAA,GAA0B,GAAG,EAC7B,YAAA,GAAuB,GAAG,EAC1B,WAAA,GAAsB,MAAM,EAAA;QAH5B,IAAA,CAAA,YAAY,GAAZ,YAAY;QACZ,IAAA,CAAA,eAAe,GAAf,eAAe;QACf,IAAA,CAAA,YAAY,GAAZ,YAAY;QACZ,IAAA,CAAA,WAAW,GAAX,WAAW;IAC7B;AAGH,IAAA,IAAI,OAAO,GAAA;QACP,OAAO,IAAI,CAAC,QAAQ;IACxB;IAGA,QAAQ,CAAC,WAA8B,EAAE,KAAa,EAAA;QAClD,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,YAAY,EAAE;YACpC,OAAO,CAAC,GAAG,CAAC,CAAA,2BAAA,EAA8B,IAAI,CAAC,YAAY,CAAA,eAAA,EAAkB,KAAK,CAAA,CAAA,CAAG,CAAC;AACtF,YAAA,OAAO,KAAK;QAChB;
|
|
1
|
+
{"version":3,"file":"ReconnectPolicy.js","sources":["../../../queries/ReconnectPolicy.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { IReconnectPolicy, ReconnectCallback } from './IReconnectPolicy';\n\n/**\n * Default exponential back-off reconnect policy shared by all observable query connections.\n *\n * Delay formula: `Math.min(initialDelayMs + delayStepMs * attempt, maxDelayMs)`\n *\n * With the default parameters:\n * - Attempt 1 → 1 000 ms\n * - Attempt 2 → 1 500 ms\n * - …\n * - Attempt 19+ → 10 000 ms (capped)\n * - After 100 attempts → abandoned (returns `false`)\n */\nexport class ReconnectPolicy implements IReconnectPolicy {\n private _attempt = 0;\n private _timer?: ReturnType<typeof setTimeout>;\n\n /**\n * Initializes a new instance of {@link ReconnectPolicy}.\n * @param {number} maxAttempts Maximum number of reconnect attempts before giving up (default: 100).\n * @param {number} initialDelayMs Base delay in milliseconds added to every attempt (default: 500).\n * @param {number} delayStepMs Additional delay per attempt in milliseconds (default: 500).\n * @param {number} maxDelayMs Upper bound on the computed delay in milliseconds (default: 10 000).\n */\n constructor(\n private readonly _maxAttempts: number = 100,\n private readonly _initialDelayMs: number = 500,\n private readonly _delayStepMs: number = 500,\n private readonly _maxDelayMs: number = 10_000,\n ) {}\n\n /** @inheritdoc */\n get attempt(): number {\n return this._attempt;\n }\n\n /** @inheritdoc */\n schedule(onReconnect: ReconnectCallback, label: string): boolean {\n if (this._attempt >= this._maxAttempts) {\n console.log(`Reconnect: abandoned after ${this._maxAttempts} attempts for '${label}'`);\n return false;\n }\n\n // Cancel any pending reconnect timer so we don't fire multiple\n // concurrent reconnect attempts when schedule() is called rapidly.\n this.cancel();\n\n this._attempt++;\n const delay = Math.min(this._initialDelayMs + this._delayStepMs * this._attempt, this._maxDelayMs);\n console.log(`Reconnect: attempt ${this._attempt} in ${delay}ms for '${label}'`);\n this._timer = setTimeout(onReconnect, delay);\n return true;\n }\n\n /** @inheritdoc */\n reset(): void {\n this._attempt = 0;\n this.cancel();\n }\n\n /** @inheritdoc */\n cancel(): void {\n if (this._timer) {\n clearTimeout(this._timer);\n this._timer = undefined;\n }\n }\n}\n"],"names":[],"mappings":"MAiBa,eAAe,CAAA;AAYH,IAAA,YAAA;AACA,IAAA,eAAA;AACA,IAAA,YAAA;AACA,IAAA,WAAA;IAdb,QAAQ,GAAG,CAAC;AACZ,IAAA,MAAM;IASd,WAAA,CACqB,YAAA,GAAuB,GAAG,EAC1B,eAAA,GAA0B,GAAG,EAC7B,YAAA,GAAuB,GAAG,EAC1B,WAAA,GAAsB,MAAM,EAAA;QAH5B,IAAA,CAAA,YAAY,GAAZ,YAAY;QACZ,IAAA,CAAA,eAAe,GAAf,eAAe;QACf,IAAA,CAAA,YAAY,GAAZ,YAAY;QACZ,IAAA,CAAA,WAAW,GAAX,WAAW;IAC7B;AAGH,IAAA,IAAI,OAAO,GAAA;QACP,OAAO,IAAI,CAAC,QAAQ;IACxB;IAGA,QAAQ,CAAC,WAA8B,EAAE,KAAa,EAAA;QAClD,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,YAAY,EAAE;YACpC,OAAO,CAAC,GAAG,CAAC,CAAA,2BAAA,EAA8B,IAAI,CAAC,YAAY,CAAA,eAAA,EAAkB,KAAK,CAAA,CAAA,CAAG,CAAC;AACtF,YAAA,OAAO,KAAK;QAChB;QAIA,IAAI,CAAC,MAAM,EAAE;QAEb,IAAI,CAAC,QAAQ,EAAE;QACf,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,WAAW,CAAC;AAClG,QAAA,OAAO,CAAC,GAAG,CAAC,CAAA,mBAAA,EAAsB,IAAI,CAAC,QAAQ,CAAA,IAAA,EAAO,KAAK,CAAA,QAAA,EAAW,KAAK,CAAA,CAAA,CAAG,CAAC;QAC/E,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC,WAAW,EAAE,KAAK,CAAC;AAC5C,QAAA,OAAO,IAAI;IACf;IAGA,KAAK,GAAA;AACD,QAAA,IAAI,CAAC,QAAQ,GAAG,CAAC;QACjB,IAAI,CAAC,MAAM,EAAE;IACjB;IAGA,MAAM,GAAA;AACF,QAAA,IAAI,IAAI,CAAC,MAAM,EAAE;AACb,YAAA,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC;AACzB,YAAA,IAAI,CAAC,MAAM,GAAG,SAAS;QAC3B;IACJ;AACH;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ServerSentEventHubConnection.d.ts","sourceRoot":"","sources":["../../../queries/ServerSentEventHubConnection.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtD,OAAO,EAA8B,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAoB3F,qBAAa,4BAA6B,YAAW,6BAA6B;IAwB1E,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAE9B,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO;IA7B5B,OAAO,CAAC,YAAY,CAAC,CAAc;IACnC,OAAO,CAAC,aAAa,CAAC,CAAS;IAC/B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,qBAAqB,CAA8C;IAC3E,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,eAAe,CAAgB;IACvC,OAAO,CAAC,oBAAoB,CAAC,CAAgC;IAC7D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;gBAe/B,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,MAAM,EACvB,aAAa,EAAE,MAAM,EACtC,mBAAmB,GAAE,MAAc,EAClB,iBAAiB,GAAE,MAAc,EACjC,OAAO,GAAE,gBAAwC;
|
|
1
|
+
{"version":3,"file":"ServerSentEventHubConnection.d.ts","sourceRoot":"","sources":["../../../queries/ServerSentEventHubConnection.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGtD,OAAO,EAA8B,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAoB3F,qBAAa,4BAA6B,YAAW,6BAA6B;IAwB1E,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAE9B,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO;IA7B5B,OAAO,CAAC,YAAY,CAAC,CAAc;IACnC,OAAO,CAAC,aAAa,CAAC,CAAS;IAC/B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,qBAAqB,CAA8C;IAC3E,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,eAAe,CAAgB;IACvC,OAAO,CAAC,oBAAoB,CAAC,CAAgC;IAC7D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;gBAe/B,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,MAAM,EACvB,aAAa,EAAE,MAAM,EACtC,mBAAmB,GAAE,MAAc,EAClB,iBAAiB,GAAE,MAAc,EACjC,OAAO,GAAE,gBAAwC;IAqBtE,IAAI,UAAU,IAAI,MAAM,CAEvB;IAGD,IAAI,eAAe,IAAI,MAAM,CAE5B;IAGD,IAAI,cAAc,IAAI,MAAM,CAG3B;IAGD,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,YAAY,CAAC,GAAG,CAAC,GAAG,IAAI;IAe3F,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAclC,OAAO,IAAI,IAAI;IAYf,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,KAAK;IAUb,OAAO,CAAC,eAAe;IAwCvB,OAAO,CAAC,SAAS;IAwBjB,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,aAAa;IA2BrB,OAAO,CAAC,eAAe;IAcvB,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,aAAa;IA0BrB,OAAO,CAAC,eAAe;CAkB1B"}
|
|
@@ -27,12 +27,13 @@ class ServerSentEventHubConnection {
|
|
|
27
27
|
this._microservice = _microservice;
|
|
28
28
|
this._connectTimeoutMs = _connectTimeoutMs;
|
|
29
29
|
this._policy = _policy;
|
|
30
|
+
const idleThresholdMs = Math.round(keepAliveIntervalMs * 1.5);
|
|
30
31
|
this._keepAlive = new HubConnectionKeepAlive(keepAliveIntervalMs, () => {
|
|
31
32
|
if (!this._disconnected && this._subscriptions.size > 0) {
|
|
32
|
-
console.warn(`SSE hub: no messages received for ${
|
|
33
|
+
console.warn(`SSE hub: no messages received for ${idleThresholdMs}ms, reconnecting '${this._sseUrl}'`);
|
|
33
34
|
this.reconnect();
|
|
34
35
|
}
|
|
35
|
-
});
|
|
36
|
+
}, idleThresholdMs);
|
|
36
37
|
}
|
|
37
38
|
get queryCount() {
|
|
38
39
|
return this._subscriptions.size;
|
|
@@ -219,8 +220,14 @@ class ServerSentEventHubConnection {
|
|
|
219
220
|
method: 'POST',
|
|
220
221
|
headers: { 'Content-Type': 'application/json', ...customHeaders },
|
|
221
222
|
body: JSON.stringify(body),
|
|
223
|
+
}).then(response => {
|
|
224
|
+
if (!response.ok) {
|
|
225
|
+
console.warn(`SSE hub: subscribe POST for '${queryId}' returned ${response.status}, reconnecting`);
|
|
226
|
+
this.reconnect();
|
|
227
|
+
}
|
|
222
228
|
}).catch(error => {
|
|
223
|
-
console.error(`SSE hub: subscribe POST failed for '${queryId}'`, error);
|
|
229
|
+
console.error(`SSE hub: subscribe POST failed for '${queryId}', reconnecting`, error);
|
|
230
|
+
this.reconnect();
|
|
224
231
|
});
|
|
225
232
|
}
|
|
226
233
|
sendUnsubscribe(queryId) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ServerSentEventHubConnection.js","sources":["../../../queries/ServerSentEventHubConnection.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { Globals } from '../Globals';\nimport { IObservableQueryHubConnection } from './IObservableQueryHubConnection';\nimport { DataReceived } from './ObservableQueryConnection';\nimport { HubConnectionKeepAlive } from './HubConnectionKeepAlive';\nimport { IReconnectPolicy } from './IReconnectPolicy';\nimport { ReconnectPolicy } from './ReconnectPolicy';\nimport { QueryResult } from './QueryResult';\nimport { HubMessage, HubMessageType, SubscriptionRequest } from './WebSocketHubConnection';\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\ninterface ActiveSubscription {\n request: SubscriptionRequest;\n callback: DataReceived<any>;\n}\n\n/**\n * A multiplexed SSE hub connection that uses EventSource for server→client streaming\n * and fetch POST requests for client→server subscribe/unsubscribe commands.\n *\n * Protocol:\n * 1. Open EventSource to the SSE hub endpoint.\n * 2. Server sends a {@link HubMessageType.Connected} message with the connection identifier.\n * 3. Client sends POST to subscribe/unsubscribe endpoints using the connection identifier.\n * 4. Server streams {@link HubMessageType.QueryResult} messages tagged with queryId.\n * 5. When EventSource closes, server cleans up all subscriptions for this connection.\n */\nexport class ServerSentEventHubConnection implements IObservableQueryHubConnection {\n private _eventSource?: EventSource;\n private _connectionId?: string;\n private _disconnected = false;\n private _subscriptions: Map<string, ActiveSubscription> = new Map();\n private _pendingSubscriptions: Map<string, ActiveSubscription> = new Map();\n private _lastPongLatency: number = 0;\n private _latencySamples: number[] = [];\n private _connectTimeoutTimer?: ReturnType<typeof setTimeout>;\n private readonly _keepAlive: HubConnectionKeepAlive;\n\n /**\n * Initializes a new instance of {@link ServerSentEventHubConnection}.\n * @param {string} _sseUrl The SSE hub endpoint URL (e.g. `http://localhost:5000/.cratis/queries/sse`).\n * @param {string} _subscribeUrl The subscribe POST endpoint URL.\n * @param {string} _unsubscribeUrl The unsubscribe POST endpoint URL.\n * @param {string} _microservice The microservice name to pass as a query argument.\n * @param {number} keepAliveIntervalMs How long without any server message before the connection\n * is considered stale and a reconnect is forced (default: 30 000 ms).\n * @param {number} connectTimeoutMs How long to wait for the {@link HubMessageType.Connected}\n * message after the HTTP connection opens before giving up and retrying (default: 15 000 ms).\n * @param {IReconnectPolicy} _policy The reconnect policy to use (default: {@link ReconnectPolicy}).\n */\n constructor(\n private readonly _sseUrl: string,\n private readonly _subscribeUrl: string,\n private readonly _unsubscribeUrl: string,\n private readonly _microservice: string,\n keepAliveIntervalMs: number = 30000,\n private readonly _connectTimeoutMs: number = 15000,\n private readonly _policy: IReconnectPolicy = new ReconnectPolicy()\n ) {\n // SSE is server→client only: the client cannot send pings. Instead we watch for\n // inactivity — if the server stops sending messages (including its own keep-alive\n // pings) for the entire interval, the connection is stale and we reconnect.\n this._keepAlive = new HubConnectionKeepAlive(keepAliveIntervalMs, () => {\n if (!this._disconnected && this._subscriptions.size > 0) {\n console.warn(`SSE hub: no messages received for ${keepAliveIntervalMs}ms, reconnecting '${this._sseUrl}'`);\n this.reconnect();\n }\n });\n }\n\n /** @inheritdoc */\n get queryCount(): number {\n return this._subscriptions.size;\n }\n\n /** @inheritdoc */\n get lastPingLatency(): number {\n return this._lastPongLatency;\n }\n\n /** @inheritdoc */\n get averageLatency(): number {\n if (this._latencySamples.length === 0) return 0;\n return this._latencySamples.reduce((a, b) => a + b, 0) / this._latencySamples.length;\n }\n\n /** @inheritdoc */\n subscribe(queryId: string, request: SubscriptionRequest, callback: DataReceived<any>): void {\n const sub: ActiveSubscription = { request, callback };\n this._subscriptions.set(queryId, sub);\n\n this.ensureConnected();\n\n if (this._connectionId) {\n this.sendSubscribe(queryId, request);\n } else {\n // Not yet connected, queue for when Connected message arrives.\n this._pendingSubscriptions.set(queryId, sub);\n }\n }\n\n /** @inheritdoc */\n unsubscribe(queryId: string): void {\n this._subscriptions.delete(queryId);\n this._pendingSubscriptions.delete(queryId);\n\n if (this._connectionId) {\n this.sendUnsubscribe(queryId);\n }\n\n if (this._subscriptions.size === 0) {\n this.close();\n }\n }\n\n /** @inheritdoc */\n dispose(): void {\n this._disconnected = true;\n this._subscriptions.clear();\n this._pendingSubscriptions.clear();\n this._policy.cancel();\n this._keepAlive.stop();\n this.clearConnectTimeout();\n this._eventSource?.close();\n this._eventSource = undefined;\n this._connectionId = undefined;\n }\n\n private ensureConnected(): void {\n if (this._disconnected) {\n this._disconnected = false;\n }\n\n if (this._eventSource && this._eventSource.readyState !== EventSource.CLOSED) {\n return;\n }\n\n this.openEventSource();\n }\n\n private close(): void {\n this._disconnected = true;\n this._policy.cancel();\n this._keepAlive.stop();\n this.clearConnectTimeout();\n this._eventSource?.close();\n this._eventSource = undefined;\n this._connectionId = undefined;\n }\n\n private openEventSource(): void {\n let url = this._sseUrl;\n if (this._microservice?.length > 0) {\n const param = `${Globals.microserviceWSQueryArgument}=${encodeURIComponent(this._microservice)}`;\n url += (url.includes('?') ? '&' : '?') + param;\n }\n\n this._connectionId = undefined;\n this._eventSource = new EventSource(url);\n\n this._eventSource.onopen = () => {\n if (this._disconnected) return;\n console.log(`SSE hub connection established: '${url}'`);\n this._policy.reset();\n this._keepAlive.start();\n\n // If the server does not send a Connected message within the timeout, the\n // connection is broken. Close and retry via the reconnect policy.\n this.clearConnectTimeout();\n this._connectTimeoutTimer = setTimeout(() => {\n if (!this._disconnected && !this._connectionId) {\n console.warn(`SSE hub: no Connected message within ${this._connectTimeoutMs}ms, retrying '${url}'`);\n this.reconnect();\n }\n }, this._connectTimeoutMs);\n };\n\n this._eventSource.onmessage = (event: MessageEvent) => {\n if (this._disconnected) return;\n this._keepAlive.recordActivity();\n this.handleMessage(event.data as string);\n };\n\n this._eventSource.onerror = () => {\n if (this._disconnected) return;\n console.warn(`SSE hub connection error: '${url}'`);\n this.reconnect();\n };\n }\n\n private reconnect(): void {\n this._keepAlive.stop();\n this.clearConnectTimeout();\n\n // Close the EventSource so the reconnect policy manages the schedule.\n this._eventSource?.close();\n this._eventSource = undefined;\n this._connectionId = undefined;\n\n // Move all active subscriptions to pending so they re-subscribe when\n // the next Connected message arrives after the managed reconnect.\n for (const [queryId, sub] of this._subscriptions) {\n this._pendingSubscriptions.set(queryId, sub);\n }\n\n if (this._subscriptions.size === 0) return;\n\n this._policy.schedule(() => {\n if (!this._disconnected && this._subscriptions.size > 0) {\n this.openEventSource();\n }\n }, this._sseUrl);\n }\n\n private clearConnectTimeout(): void {\n if (this._connectTimeoutTimer !== undefined) {\n clearTimeout(this._connectTimeoutTimer);\n this._connectTimeoutTimer = undefined;\n }\n }\n\n private handleMessage(rawData: string): void {\n try {\n const message = JSON.parse(rawData) as HubMessage;\n\n switch (message.type) {\n case HubMessageType.Connected:\n this.handleConnected(message);\n break;\n case HubMessageType.QueryResult:\n this.handleQueryResult(message);\n break;\n case HubMessageType.Ping:\n // Server-sent keep-alive ping — activity already recorded in onmessage.\n break;\n case HubMessageType.Unauthorized:\n console.warn(`SSE hub: query '${message.queryId}' unauthorized`);\n this.handleUnauthorized(message);\n break;\n case HubMessageType.Error:\n console.error(`SSE hub: query '${message.queryId}' error:`, message.payload);\n break;\n }\n } catch (error) {\n console.error('SSE hub: error parsing message', error);\n }\n }\n\n private handleConnected(message: HubMessage): void {\n this._connectionId = message.payload as string;\n console.log(`SSE hub: connected with id '${this._connectionId}'`);\n\n // Connected message arrived — cancel the connect timeout.\n this.clearConnectTimeout();\n\n // Send all pending subscriptions now that we have a connection ID.\n for (const [queryId, sub] of this._pendingSubscriptions) {\n this.sendSubscribe(queryId, sub.request);\n }\n this._pendingSubscriptions.clear();\n }\n\n private handleQueryResult(message: HubMessage): void {\n if (!message.queryId) return;\n\n const sub = this._subscriptions.get(message.queryId);\n if (!sub) return;\n\n const result = message.payload as QueryResult<any>;\n sub.callback(result);\n }\n\n private handleUnauthorized(message: HubMessage): void {\n if (!message.queryId) return;\n\n const sub = this._subscriptions.get(message.queryId);\n if (!sub) return;\n\n this._subscriptions.delete(message.queryId);\n this._pendingSubscriptions.delete(message.queryId);\n sub.callback(QueryResult.unauthorized());\n }\n\n private sendSubscribe(queryId: string, request: SubscriptionRequest): void {\n if (!this._connectionId) return;\n\n const body = {\n connectionId: this._connectionId,\n queryId,\n request,\n };\n\n const customHeaders = Globals.httpHeadersCallback?.() ?? {};\n\n fetch(this._subscribeUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify(body),\n }).catch(error => {\n console.error(`SSE hub: subscribe POST failed for '${queryId}'`, error);\n });\n }\n\n private sendUnsubscribe(queryId: string): void {\n if (!this._connectionId) return;\n\n const body = {\n connectionId: this._connectionId,\n queryId,\n };\n\n const customHeaders = Globals.httpHeadersCallback?.() ?? {};\n\n fetch(this._unsubscribeUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify(body),\n }).catch(error => {\n console.error(`SSE hub: unsubscribe POST failed for '${queryId}'`, error);\n });\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ServerSentEventHubConnection.js","sources":["../../../queries/ServerSentEventHubConnection.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { Globals } from '../Globals';\nimport { IObservableQueryHubConnection } from './IObservableQueryHubConnection';\nimport { DataReceived } from './ObservableQueryConnection';\nimport { HubConnectionKeepAlive } from './HubConnectionKeepAlive';\nimport { IReconnectPolicy } from './IReconnectPolicy';\nimport { ReconnectPolicy } from './ReconnectPolicy';\nimport { QueryResult } from './QueryResult';\nimport { HubMessage, HubMessageType, SubscriptionRequest } from './WebSocketHubConnection';\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\ninterface ActiveSubscription {\n request: SubscriptionRequest;\n callback: DataReceived<any>;\n}\n\n/**\n * A multiplexed SSE hub connection that uses EventSource for server→client streaming\n * and fetch POST requests for client→server subscribe/unsubscribe commands.\n *\n * Protocol:\n * 1. Open EventSource to the SSE hub endpoint.\n * 2. Server sends a {@link HubMessageType.Connected} message with the connection identifier.\n * 3. Client sends POST to subscribe/unsubscribe endpoints using the connection identifier.\n * 4. Server streams {@link HubMessageType.QueryResult} messages tagged with queryId.\n * 5. When EventSource closes, server cleans up all subscriptions for this connection.\n */\nexport class ServerSentEventHubConnection implements IObservableQueryHubConnection {\n private _eventSource?: EventSource;\n private _connectionId?: string;\n private _disconnected = false;\n private _subscriptions: Map<string, ActiveSubscription> = new Map();\n private _pendingSubscriptions: Map<string, ActiveSubscription> = new Map();\n private _lastPongLatency: number = 0;\n private _latencySamples: number[] = [];\n private _connectTimeoutTimer?: ReturnType<typeof setTimeout>;\n private readonly _keepAlive: HubConnectionKeepAlive;\n\n /**\n * Initializes a new instance of {@link ServerSentEventHubConnection}.\n * @param {string} _sseUrl The SSE hub endpoint URL (e.g. `http://localhost:5000/.cratis/queries/sse`).\n * @param {string} _subscribeUrl The subscribe POST endpoint URL.\n * @param {string} _unsubscribeUrl The unsubscribe POST endpoint URL.\n * @param {string} _microservice The microservice name to pass as a query argument.\n * @param {number} keepAliveIntervalMs How long without any server message before the connection\n * is considered stale and a reconnect is forced (default: 30 000 ms).\n * @param {number} connectTimeoutMs How long to wait for the {@link HubMessageType.Connected}\n * message after the HTTP connection opens before giving up and retrying (default: 15 000 ms).\n * @param {IReconnectPolicy} _policy The reconnect policy to use (default: {@link ReconnectPolicy}).\n */\n constructor(\n private readonly _sseUrl: string,\n private readonly _subscribeUrl: string,\n private readonly _unsubscribeUrl: string,\n private readonly _microservice: string,\n keepAliveIntervalMs: number = 30000,\n private readonly _connectTimeoutMs: number = 15000,\n private readonly _policy: IReconnectPolicy = new ReconnectPolicy()\n ) {\n // SSE is server→client only: the client cannot send pings. Instead we watch for\n // inactivity — if the server stops sending messages (including its own keep-alive\n // pings) for the entire idle threshold, the connection is stale and we reconnect.\n //\n // The idle threshold is set to 1.5× the check interval so the server's keep-alive\n // ping (which fires on the same cadence) has time to arrive over the network before\n // the client declares the connection dead. Without this tolerance the client's timer\n // and the server's timer race — the client often fires first and reconnects\n // unnecessarily.\n const idleThresholdMs = Math.round(keepAliveIntervalMs * 1.5);\n this._keepAlive = new HubConnectionKeepAlive(keepAliveIntervalMs, () => {\n if (!this._disconnected && this._subscriptions.size > 0) {\n console.warn(`SSE hub: no messages received for ${idleThresholdMs}ms, reconnecting '${this._sseUrl}'`);\n this.reconnect();\n }\n }, idleThresholdMs);\n }\n\n /** @inheritdoc */\n get queryCount(): number {\n return this._subscriptions.size;\n }\n\n /** @inheritdoc */\n get lastPingLatency(): number {\n return this._lastPongLatency;\n }\n\n /** @inheritdoc */\n get averageLatency(): number {\n if (this._latencySamples.length === 0) return 0;\n return this._latencySamples.reduce((a, b) => a + b, 0) / this._latencySamples.length;\n }\n\n /** @inheritdoc */\n subscribe(queryId: string, request: SubscriptionRequest, callback: DataReceived<any>): void {\n const sub: ActiveSubscription = { request, callback };\n this._subscriptions.set(queryId, sub);\n\n this.ensureConnected();\n\n if (this._connectionId) {\n this.sendSubscribe(queryId, request);\n } else {\n // Not yet connected, queue for when Connected message arrives.\n this._pendingSubscriptions.set(queryId, sub);\n }\n }\n\n /** @inheritdoc */\n unsubscribe(queryId: string): void {\n this._subscriptions.delete(queryId);\n this._pendingSubscriptions.delete(queryId);\n\n if (this._connectionId) {\n this.sendUnsubscribe(queryId);\n }\n\n if (this._subscriptions.size === 0) {\n this.close();\n }\n }\n\n /** @inheritdoc */\n dispose(): void {\n this._disconnected = true;\n this._subscriptions.clear();\n this._pendingSubscriptions.clear();\n this._policy.cancel();\n this._keepAlive.stop();\n this.clearConnectTimeout();\n this._eventSource?.close();\n this._eventSource = undefined;\n this._connectionId = undefined;\n }\n\n private ensureConnected(): void {\n if (this._disconnected) {\n this._disconnected = false;\n }\n\n if (this._eventSource && this._eventSource.readyState !== EventSource.CLOSED) {\n return;\n }\n\n this.openEventSource();\n }\n\n private close(): void {\n this._disconnected = true;\n this._policy.cancel();\n this._keepAlive.stop();\n this.clearConnectTimeout();\n this._eventSource?.close();\n this._eventSource = undefined;\n this._connectionId = undefined;\n }\n\n private openEventSource(): void {\n let url = this._sseUrl;\n if (this._microservice?.length > 0) {\n const param = `${Globals.microserviceWSQueryArgument}=${encodeURIComponent(this._microservice)}`;\n url += (url.includes('?') ? '&' : '?') + param;\n }\n\n this._connectionId = undefined;\n this._eventSource = new EventSource(url);\n\n this._eventSource.onopen = () => {\n if (this._disconnected) return;\n console.log(`SSE hub connection established: '${url}'`);\n this._policy.reset();\n this._keepAlive.start();\n\n // If the server does not send a Connected message within the timeout, the\n // connection is broken. Close and retry via the reconnect policy.\n this.clearConnectTimeout();\n this._connectTimeoutTimer = setTimeout(() => {\n if (!this._disconnected && !this._connectionId) {\n console.warn(`SSE hub: no Connected message within ${this._connectTimeoutMs}ms, retrying '${url}'`);\n this.reconnect();\n }\n }, this._connectTimeoutMs);\n };\n\n this._eventSource.onmessage = (event: MessageEvent) => {\n if (this._disconnected) return;\n this._keepAlive.recordActivity();\n this.handleMessage(event.data as string);\n };\n\n this._eventSource.onerror = () => {\n if (this._disconnected) return;\n console.warn(`SSE hub connection error: '${url}'`);\n this.reconnect();\n };\n }\n\n private reconnect(): void {\n this._keepAlive.stop();\n this.clearConnectTimeout();\n\n // Close the EventSource so the reconnect policy manages the schedule.\n this._eventSource?.close();\n this._eventSource = undefined;\n this._connectionId = undefined;\n\n // Move all active subscriptions to pending so they re-subscribe when\n // the next Connected message arrives after the managed reconnect.\n for (const [queryId, sub] of this._subscriptions) {\n this._pendingSubscriptions.set(queryId, sub);\n }\n\n if (this._subscriptions.size === 0) return;\n\n this._policy.schedule(() => {\n if (!this._disconnected && this._subscriptions.size > 0) {\n this.openEventSource();\n }\n }, this._sseUrl);\n }\n\n private clearConnectTimeout(): void {\n if (this._connectTimeoutTimer !== undefined) {\n clearTimeout(this._connectTimeoutTimer);\n this._connectTimeoutTimer = undefined;\n }\n }\n\n private handleMessage(rawData: string): void {\n try {\n const message = JSON.parse(rawData) as HubMessage;\n\n switch (message.type) {\n case HubMessageType.Connected:\n this.handleConnected(message);\n break;\n case HubMessageType.QueryResult:\n this.handleQueryResult(message);\n break;\n case HubMessageType.Ping:\n // Server-sent keep-alive ping — activity already recorded in onmessage.\n break;\n case HubMessageType.Unauthorized:\n console.warn(`SSE hub: query '${message.queryId}' unauthorized`);\n this.handleUnauthorized(message);\n break;\n case HubMessageType.Error:\n console.error(`SSE hub: query '${message.queryId}' error:`, message.payload);\n break;\n }\n } catch (error) {\n console.error('SSE hub: error parsing message', error);\n }\n }\n\n private handleConnected(message: HubMessage): void {\n this._connectionId = message.payload as string;\n console.log(`SSE hub: connected with id '${this._connectionId}'`);\n\n // Connected message arrived — cancel the connect timeout.\n this.clearConnectTimeout();\n\n // Send all pending subscriptions now that we have a connection ID.\n for (const [queryId, sub] of this._pendingSubscriptions) {\n this.sendSubscribe(queryId, sub.request);\n }\n this._pendingSubscriptions.clear();\n }\n\n private handleQueryResult(message: HubMessage): void {\n if (!message.queryId) return;\n\n const sub = this._subscriptions.get(message.queryId);\n if (!sub) return;\n\n const result = message.payload as QueryResult<any>;\n sub.callback(result);\n }\n\n private handleUnauthorized(message: HubMessage): void {\n if (!message.queryId) return;\n\n const sub = this._subscriptions.get(message.queryId);\n if (!sub) return;\n\n this._subscriptions.delete(message.queryId);\n this._pendingSubscriptions.delete(message.queryId);\n sub.callback(QueryResult.unauthorized());\n }\n\n private sendSubscribe(queryId: string, request: SubscriptionRequest): void {\n if (!this._connectionId) return;\n\n const body = {\n connectionId: this._connectionId,\n queryId,\n request,\n };\n\n const customHeaders = Globals.httpHeadersCallback?.() ?? {};\n\n fetch(this._subscribeUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify(body),\n }).then(response => {\n if (!response.ok) {\n console.warn(`SSE hub: subscribe POST for '${queryId}' returned ${response.status}, reconnecting`);\n this.reconnect();\n }\n }).catch(error => {\n console.error(`SSE hub: subscribe POST failed for '${queryId}', reconnecting`, error);\n this.reconnect();\n });\n }\n\n private sendUnsubscribe(queryId: string): void {\n if (!this._connectionId) return;\n\n const body = {\n connectionId: this._connectionId,\n queryId,\n };\n\n const customHeaders = Globals.httpHeadersCallback?.() ?? {};\n\n fetch(this._unsubscribeUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...customHeaders },\n body: JSON.stringify(body),\n }).catch(error => {\n console.error(`SSE hub: unsubscribe POST failed for '${queryId}'`, error);\n });\n }\n}\n"],"names":[],"mappings":";;;;;;MA8Ba,4BAA4B,CAAA;AAwBhB,IAAA,OAAA;AACA,IAAA,aAAA;AACA,IAAA,eAAA;AACA,IAAA,aAAA;AAEA,IAAA,iBAAA;AACA,IAAA,OAAA;AA7Bb,IAAA,YAAY;AACZ,IAAA,aAAa;IACb,aAAa,GAAG,KAAK;AACrB,IAAA,cAAc,GAAoC,IAAI,GAAG,EAAE;AAC3D,IAAA,qBAAqB,GAAoC,IAAI,GAAG,EAAE;IAClE,gBAAgB,GAAW,CAAC;IAC5B,eAAe,GAAa,EAAE;AAC9B,IAAA,oBAAoB;AACX,IAAA,UAAU;AAc3B,IAAA,WAAA,CACqB,OAAe,EACf,aAAqB,EACrB,eAAuB,EACvB,aAAqB,EACtC,mBAAA,GAA8B,KAAK,EAClB,iBAAA,GAA4B,KAAK,EACjC,OAAA,GAA4B,IAAI,eAAe,EAAE,EAAA;QANjD,IAAA,CAAA,OAAO,GAAP,OAAO;QACP,IAAA,CAAA,aAAa,GAAb,aAAa;QACb,IAAA,CAAA,eAAe,GAAf,eAAe;QACf,IAAA,CAAA,aAAa,GAAb,aAAa;QAEb,IAAA,CAAA,iBAAiB,GAAjB,iBAAiB;QACjB,IAAA,CAAA,OAAO,GAAP,OAAO;QAWxB,MAAM,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,mBAAmB,GAAG,GAAG,CAAC;QAC7D,IAAI,CAAC,UAAU,GAAG,IAAI,sBAAsB,CAAC,mBAAmB,EAAE,MAAK;AACnE,YAAA,IAAI,CAAC,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,cAAc,CAAC,IAAI,GAAG,CAAC,EAAE;gBACrD,OAAO,CAAC,IAAI,CAAC,CAAA,kCAAA,EAAqC,eAAe,CAAA,kBAAA,EAAqB,IAAI,CAAC,OAAO,CAAA,CAAA,CAAG,CAAC;gBACtG,IAAI,CAAC,SAAS,EAAE;YACpB;QACJ,CAAC,EAAE,eAAe,CAAC;IACvB;AAGA,IAAA,IAAI,UAAU,GAAA;AACV,QAAA,OAAO,IAAI,CAAC,cAAc,CAAC,IAAI;IACnC;AAGA,IAAA,IAAI,eAAe,GAAA;QACf,OAAO,IAAI,CAAC,gBAAgB;IAChC;AAGA,IAAA,IAAI,cAAc,GAAA;AACd,QAAA,IAAI,IAAI,CAAC,eAAe,CAAC,MAAM,KAAK,CAAC;AAAE,YAAA,OAAO,CAAC;QAC/C,OAAO,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM;IACxF;AAGA,IAAA,SAAS,CAAC,OAAe,EAAE,OAA4B,EAAE,QAA2B,EAAA;AAChF,QAAA,MAAM,GAAG,GAAuB,EAAE,OAAO,EAAE,QAAQ,EAAE;QACrD,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC;QAErC,IAAI,CAAC,eAAe,EAAE;AAEtB,QAAA,IAAI,IAAI,CAAC,aAAa,EAAE;AACpB,YAAA,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC;QACxC;aAAO;YAEH,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC;QAChD;IACJ;AAGA,IAAA,WAAW,CAAC,OAAe,EAAA;AACvB,QAAA,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC;AACnC,QAAA,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,OAAO,CAAC;AAE1C,QAAA,IAAI,IAAI,CAAC,aAAa,EAAE;AACpB,YAAA,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC;QACjC;QAEA,IAAI,IAAI,CAAC,cAAc,CAAC,IAAI,KAAK,CAAC,EAAE;YAChC,IAAI,CAAC,KAAK,EAAE;QAChB;IACJ;IAGA,OAAO,GAAA;AACH,QAAA,IAAI,CAAC,aAAa,GAAG,IAAI;AACzB,QAAA,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE;AAC3B,QAAA,IAAI,CAAC,qBAAqB,CAAC,KAAK,EAAE;AAClC,QAAA,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE;AACrB,QAAA,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE;QACtB,IAAI,CAAC,mBAAmB,EAAE;AAC1B,QAAA,IAAI,CAAC,YAAY,EAAE,KAAK,EAAE;AAC1B,QAAA,IAAI,CAAC,YAAY,GAAG,SAAS;AAC7B,QAAA,IAAI,CAAC,aAAa,GAAG,SAAS;IAClC;IAEQ,eAAe,GAAA;AACnB,QAAA,IAAI,IAAI,CAAC,aAAa,EAAE;AACpB,YAAA,IAAI,CAAC,aAAa,GAAG,KAAK;QAC9B;AAEA,QAAA,IAAI,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,YAAY,CAAC,UAAU,KAAK,WAAW,CAAC,MAAM,EAAE;YAC1E;QACJ;QAEA,IAAI,CAAC,eAAe,EAAE;IAC1B;IAEQ,KAAK,GAAA;AACT,QAAA,IAAI,CAAC,aAAa,GAAG,IAAI;AACzB,QAAA,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE;AACrB,QAAA,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE;QACtB,IAAI,CAAC,mBAAmB,EAAE;AAC1B,QAAA,IAAI,CAAC,YAAY,EAAE,KAAK,EAAE;AAC1B,QAAA,IAAI,CAAC,YAAY,GAAG,SAAS;AAC7B,QAAA,IAAI,CAAC,aAAa,GAAG,SAAS;IAClC;IAEQ,eAAe,GAAA;AACnB,QAAA,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO;QACtB,IAAI,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,CAAC,EAAE;AAChC,YAAA,MAAM,KAAK,GAAG,CAAA,EAAG,OAAO,CAAC,2BAA2B,CAAA,CAAA,EAAI,kBAAkB,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE;AAChG,YAAA,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,GAAG,IAAI,KAAK;QAClD;AAEA,QAAA,IAAI,CAAC,aAAa,GAAG,SAAS;QAC9B,IAAI,CAAC,YAAY,GAAG,IAAI,WAAW,CAAC,GAAG,CAAC;AAExC,QAAA,IAAI,CAAC,YAAY,CAAC,MAAM,GAAG,MAAK;YAC5B,IAAI,IAAI,CAAC,aAAa;gBAAE;AACxB,YAAA,OAAO,CAAC,GAAG,CAAC,oCAAoC,GAAG,CAAA,CAAA,CAAG,CAAC;AACvD,YAAA,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE;AACpB,YAAA,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE;YAIvB,IAAI,CAAC,mBAAmB,EAAE;AAC1B,YAAA,IAAI,CAAC,oBAAoB,GAAG,UAAU,CAAC,MAAK;gBACxC,IAAI,CAAC,IAAI,CAAC,aAAa,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE;oBAC5C,OAAO,CAAC,IAAI,CAAC,CAAA,qCAAA,EAAwC,IAAI,CAAC,iBAAiB,CAAA,cAAA,EAAiB,GAAG,CAAA,CAAA,CAAG,CAAC;oBACnG,IAAI,CAAC,SAAS,EAAE;gBACpB;AACJ,YAAA,CAAC,EAAE,IAAI,CAAC,iBAAiB,CAAC;AAC9B,QAAA,CAAC;QAED,IAAI,CAAC,YAAY,CAAC,SAAS,GAAG,CAAC,KAAmB,KAAI;YAClD,IAAI,IAAI,CAAC,aAAa;gBAAE;AACxB,YAAA,IAAI,CAAC,UAAU,CAAC,cAAc,EAAE;AAChC,YAAA,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAc,CAAC;AAC5C,QAAA,CAAC;AAED,QAAA,IAAI,CAAC,YAAY,CAAC,OAAO,GAAG,MAAK;YAC7B,IAAI,IAAI,CAAC,aAAa;gBAAE;AACxB,YAAA,OAAO,CAAC,IAAI,CAAC,8BAA8B,GAAG,CAAA,CAAA,CAAG,CAAC;YAClD,IAAI,CAAC,SAAS,EAAE;AACpB,QAAA,CAAC;IACL;IAEQ,SAAS,GAAA;AACb,QAAA,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE;QACtB,IAAI,CAAC,mBAAmB,EAAE;AAG1B,QAAA,IAAI,CAAC,YAAY,EAAE,KAAK,EAAE;AAC1B,QAAA,IAAI,CAAC,YAAY,GAAG,SAAS;AAC7B,QAAA,IAAI,CAAC,aAAa,GAAG,SAAS;QAI9B,KAAK,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,cAAc,EAAE;YAC9C,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC;QAChD;AAEA,QAAA,IAAI,IAAI,CAAC,cAAc,CAAC,IAAI,KAAK,CAAC;YAAE;AAEpC,QAAA,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAK;AACvB,YAAA,IAAI,CAAC,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,cAAc,CAAC,IAAI,GAAG,CAAC,EAAE;gBACrD,IAAI,CAAC,eAAe,EAAE;YAC1B;AACJ,QAAA,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC;IACpB;IAEQ,mBAAmB,GAAA;AACvB,QAAA,IAAI,IAAI,CAAC,oBAAoB,KAAK,SAAS,EAAE;AACzC,YAAA,YAAY,CAAC,IAAI,CAAC,oBAAoB,CAAC;AACvC,YAAA,IAAI,CAAC,oBAAoB,GAAG,SAAS;QACzC;IACJ;AAEQ,IAAA,aAAa,CAAC,OAAe,EAAA;AACjC,QAAA,IAAI;YACA,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAe;AAEjD,YAAA,QAAQ,OAAO,CAAC,IAAI;gBAChB,KAAK,cAAc,CAAC,SAAS;AACzB,oBAAA,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC;oBAC7B;gBACJ,KAAK,cAAc,CAAC,WAAW;AAC3B,oBAAA,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC;oBAC/B;gBACJ,KAAK,cAAc,CAAC,IAAI;oBAEpB;gBACJ,KAAK,cAAc,CAAC,YAAY;oBAC5B,OAAO,CAAC,IAAI,CAAC,CAAA,gBAAA,EAAmB,OAAO,CAAC,OAAO,CAAA,cAAA,CAAgB,CAAC;AAChE,oBAAA,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC;oBAChC;gBACJ,KAAK,cAAc,CAAC,KAAK;AACrB,oBAAA,OAAO,CAAC,KAAK,CAAC,CAAA,gBAAA,EAAmB,OAAO,CAAC,OAAO,CAAA,QAAA,CAAU,EAAE,OAAO,CAAC,OAAO,CAAC;oBAC5E;;QAEZ;QAAE,OAAO,KAAK,EAAE;AACZ,YAAA,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,KAAK,CAAC;QAC1D;IACJ;AAEQ,IAAA,eAAe,CAAC,OAAmB,EAAA;AACvC,QAAA,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,OAAiB;QAC9C,OAAO,CAAC,GAAG,CAAC,CAAA,4BAAA,EAA+B,IAAI,CAAC,aAAa,CAAA,CAAA,CAAG,CAAC;QAGjE,IAAI,CAAC,mBAAmB,EAAE;QAG1B,KAAK,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,qBAAqB,EAAE;YACrD,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC;QAC5C;AACA,QAAA,IAAI,CAAC,qBAAqB,CAAC,KAAK,EAAE;IACtC;AAEQ,IAAA,iBAAiB,CAAC,OAAmB,EAAA;QACzC,IAAI,CAAC,OAAO,CAAC,OAAO;YAAE;AAEtB,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC;AACpD,QAAA,IAAI,CAAC,GAAG;YAAE;AAEV,QAAA,MAAM,MAAM,GAAG,OAAO,CAAC,OAA2B;AAClD,QAAA,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC;IACxB;AAEQ,IAAA,kBAAkB,CAAC,OAAmB,EAAA;QAC1C,IAAI,CAAC,OAAO,CAAC,OAAO;YAAE;AAEtB,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC;AACpD,QAAA,IAAI,CAAC,GAAG;YAAE;QAEV,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;QAC3C,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;QAClD,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,YAAY,EAAE,CAAC;IAC5C;IAEQ,aAAa,CAAC,OAAe,EAAE,OAA4B,EAAA;QAC/D,IAAI,CAAC,IAAI,CAAC,aAAa;YAAE;AAEzB,QAAA,MAAM,IAAI,GAAG;YACT,YAAY,EAAE,IAAI,CAAC,aAAa;YAChC,OAAO;YACP,OAAO;SACV;QAED,MAAM,aAAa,GAAG,OAAO,CAAC,mBAAmB,IAAI,IAAI,EAAE;AAE3D,QAAA,KAAK,CAAC,IAAI,CAAC,aAAa,EAAE;AACtB,YAAA,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,GAAG,aAAa,EAAE;AACjE,YAAA,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;AAC7B,SAAA,CAAC,CAAC,IAAI,CAAC,QAAQ,IAAG;AACf,YAAA,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE;gBACd,OAAO,CAAC,IAAI,CAAC,CAAA,6BAAA,EAAgC,OAAO,CAAA,WAAA,EAAc,QAAQ,CAAC,MAAM,CAAA,cAAA,CAAgB,CAAC;gBAClG,IAAI,CAAC,SAAS,EAAE;YACpB;AACJ,QAAA,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,IAAG;YACb,OAAO,CAAC,KAAK,CAAC,CAAA,oCAAA,EAAuC,OAAO,CAAA,eAAA,CAAiB,EAAE,KAAK,CAAC;YACrF,IAAI,CAAC,SAAS,EAAE;AACpB,QAAA,CAAC,CAAC;IACN;AAEQ,IAAA,eAAe,CAAC,OAAe,EAAA;QACnC,IAAI,CAAC,IAAI,CAAC,aAAa;YAAE;AAEzB,QAAA,MAAM,IAAI,GAAG;YACT,YAAY,EAAE,IAAI,CAAC,aAAa;YAChC,OAAO;SACV;QAED,MAAM,aAAa,GAAG,OAAO,CAAC,mBAAmB,IAAI,IAAI,EAAE;AAE3D,QAAA,KAAK,CAAC,IAAI,CAAC,eAAe,EAAE;AACxB,YAAA,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,GAAG,aAAa,EAAE;AACjE,YAAA,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;AAC7B,SAAA,CAAC,CAAC,KAAK,CAAC,KAAK,IAAG;YACb,OAAO,CAAC,KAAK,CAAC,CAAA,sCAAA,EAAyC,OAAO,CAAA,CAAA,CAAG,EAAE,KAAK,CAAC;AAC7E,QAAA,CAAC,CAAC;IACN;AACH;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"respects_threshold.d.ts","sourceRoot":"","sources":["../../../../../queries/for_HubConnectionKeepAlive/when_started_with_custom_idle_threshold/respects_threshold.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import sinon from 'sinon';
|
|
2
|
+
import { HubConnectionKeepAlive } from '../../HubConnectionKeepAlive';
|
|
3
|
+
describe('when started with a custom idle threshold larger than the check interval', () => {
|
|
4
|
+
let clock;
|
|
5
|
+
let onIdle;
|
|
6
|
+
let keepAlive;
|
|
7
|
+
const checkIntervalMs = 500;
|
|
8
|
+
const idleThresholdMs = 750;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
clock = sinon.useFakeTimers();
|
|
11
|
+
onIdle = sinon.stub();
|
|
12
|
+
keepAlive = new HubConnectionKeepAlive(checkIntervalMs, onIdle, idleThresholdMs);
|
|
13
|
+
keepAlive.start();
|
|
14
|
+
});
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
keepAlive.stop();
|
|
17
|
+
clock.restore();
|
|
18
|
+
sinon.restore();
|
|
19
|
+
});
|
|
20
|
+
describe('and the check interval elapses but idle threshold has not', () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
clock.tick(checkIntervalMs + 1);
|
|
23
|
+
});
|
|
24
|
+
it('should not invoke the onIdle callback', () => onIdle.called.should.be.false);
|
|
25
|
+
});
|
|
26
|
+
describe('and the idle threshold elapses', () => {
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
clock.tick(checkIntervalMs * 2 + 1);
|
|
29
|
+
});
|
|
30
|
+
it('should invoke the onIdle callback', () => onIdle.calledOnce.should.be.true);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
//# sourceMappingURL=respects_threshold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"respects_threshold.js","sourceRoot":"","sources":["../../../../../queries/for_HubConnectionKeepAlive/when_started_with_custom_idle_threshold/respects_threshold.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAEtE,QAAQ,CAAC,0EAA0E,EAAE,GAAG,EAAE;IACtF,IAAI,KAA4B,CAAC;IACjC,IAAI,MAAuB,CAAC;IAC5B,IAAI,SAAiC,CAAC;IAEtC,MAAM,eAAe,GAAG,GAAG,CAAC;IAC5B,MAAM,eAAe,GAAG,GAAG,CAAC;IAE5B,UAAU,CAAC,GAAG,EAAE;QACZ,KAAK,GAAG,KAAK,CAAC,aAAa,EAAE,CAAC;QAC9B,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QACtB,SAAS,GAAG,IAAI,sBAAsB,CAAC,eAAe,EAAE,MAAM,EAAE,eAAe,CAAC,CAAC;QACjF,SAAS,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACX,SAAS,CAAC,IAAI,EAAE,CAAC;QACjB,KAAK,CAAC,OAAO,EAAE,CAAC;QAChB,KAAK,CAAC,OAAO,EAAE,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACvE,UAAU,CAAC,GAAG,EAAE;YAEZ,KAAK,CAAC,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IACrF,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,gCAAgC,EAAE,GAAG,EAAE;QAC5C,UAAU,CAAC,GAAG,EAAE;YAGZ,KAAK,CAAC,IAAI,CAAC,eAAe,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QACxC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;IACpF,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cancels_previous_timer.d.ts","sourceRoot":"","sources":["../../../../../../queries/for_ReconnectPolicy/when_scheduling/and_rescheduled_before_first_fires/cancels_previous_timer.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import sinon from 'sinon';
|
|
2
|
+
import { ReconnectPolicy } from '../../../ReconnectPolicy';
|
|
3
|
+
describe('when scheduling twice before the first timer fires', () => {
|
|
4
|
+
let policy;
|
|
5
|
+
let clock;
|
|
6
|
+
let firstCallback;
|
|
7
|
+
let secondCallback;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
clock = sinon.useFakeTimers();
|
|
10
|
+
policy = new ReconnectPolicy(100, 500, 500, 10_000);
|
|
11
|
+
firstCallback = sinon.stub();
|
|
12
|
+
secondCallback = sinon.stub();
|
|
13
|
+
policy.schedule(firstCallback, 'first');
|
|
14
|
+
policy.schedule(secondCallback, 'second');
|
|
15
|
+
clock.tick(2000);
|
|
16
|
+
});
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
clock.restore();
|
|
19
|
+
sinon.restore();
|
|
20
|
+
});
|
|
21
|
+
it('should cancel the first timer so it never fires', () => firstCallback.called.should.be.false);
|
|
22
|
+
it('should fire the second timer', () => secondCallback.calledOnce.should.be.true);
|
|
23
|
+
});
|
|
24
|
+
//# sourceMappingURL=cancels_previous_timer.js.map
|