@cratis/arc 21.1.0 → 21.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/queries/ObservableQueryConnection.js +24 -4
- package/dist/cjs/queries/ObservableQueryConnection.js.map +1 -1
- package/dist/cjs/queries/ServerSentEventQueryConnection.js +24 -2
- package/dist/cjs/queries/ServerSentEventQueryConnection.js.map +1 -1
- package/dist/esm/queries/ObservableQueryConnection.d.ts +2 -0
- package/dist/esm/queries/ObservableQueryConnection.d.ts.map +1 -1
- package/dist/esm/queries/ObservableQueryConnection.js +24 -4
- package/dist/esm/queries/ObservableQueryConnection.js.map +1 -1
- package/dist/esm/queries/ServerSentEventQueryConnection.d.ts +2 -0
- package/dist/esm/queries/ServerSentEventQueryConnection.d.ts.map +1 -1
- package/dist/esm/queries/ServerSentEventQueryConnection.js +24 -2
- package/dist/esm/queries/ServerSentEventQueryConnection.js.map +1 -1
- package/dist/esm/queries/for_ObservableQueryConnection/given/an_observable_query_connection_with_a_reconnect_policy.d.ts +25 -0
- package/dist/esm/queries/for_ObservableQueryConnection/given/an_observable_query_connection_with_a_reconnect_policy.d.ts.map +1 -0
- package/dist/esm/queries/for_ObservableQueryConnection/given/an_observable_query_connection_with_a_reconnect_policy.js +45 -0
- package/dist/esm/queries/for_ObservableQueryConnection/given/an_observable_query_connection_with_a_reconnect_policy.js.map +1 -0
- package/dist/esm/queries/for_ObservableQueryConnection/when_receiving_unauthorized/terminates_without_reconnecting.d.ts +2 -0
- package/dist/esm/queries/for_ObservableQueryConnection/when_receiving_unauthorized/terminates_without_reconnecting.d.ts.map +1 -0
- package/dist/esm/queries/for_ObservableQueryConnection/when_receiving_unauthorized/terminates_without_reconnecting.js +44 -0
- package/dist/esm/queries/for_ObservableQueryConnection/when_receiving_unauthorized/terminates_without_reconnecting.js.map +1 -0
- package/dist/esm/queries/for_ServerSentEventQueryConnection/when_receiving_unauthorized/terminates_without_reconnecting.d.ts +2 -0
- package/dist/esm/queries/for_ServerSentEventQueryConnection/when_receiving_unauthorized/terminates_without_reconnecting.d.ts.map +1 -0
- package/dist/esm/queries/for_ServerSentEventQueryConnection/when_receiving_unauthorized/terminates_without_reconnecting.js +43 -0
- package/dist/esm/queries/for_ServerSentEventQueryConnection/when_receiving_unauthorized/terminates_without_reconnecting.js.map +1 -0
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/queries/ObservableQueryConnection.ts +28 -4
- package/queries/ServerSentEventQueryConnection.ts +26 -2
- package/queries/for_ObservableQueryConnection/given/an_observable_query_connection_with_a_reconnect_policy.ts +72 -0
- package/queries/for_ObservableQueryConnection/when_receiving_unauthorized/terminates_without_reconnecting.ts +57 -0
- package/queries/for_ServerSentEventQueryConnection/when_receiving_unauthorized/terminates_without_reconnecting.ts +63 -0
|
@@ -13,6 +13,7 @@ var WebSocketMessage = require('./WebSocketMessage.js');
|
|
|
13
13
|
_reconnectPolicy;
|
|
14
14
|
_socket;
|
|
15
15
|
_disconnected = false;
|
|
16
|
+
_unauthorized = false;
|
|
16
17
|
_url;
|
|
17
18
|
_pingInterval;
|
|
18
19
|
_pingIntervalMs = 10000;
|
|
@@ -81,13 +82,13 @@ var WebSocketMessage = require('./WebSocketMessage.js');
|
|
|
81
82
|
this.startPinging();
|
|
82
83
|
};
|
|
83
84
|
this._socket.onclose = ()=>{
|
|
84
|
-
if (this._disconnected) return;
|
|
85
|
+
if (this._disconnected || this._unauthorized) return;
|
|
85
86
|
console.log(`Unexpected connection closed for route '${url}'`);
|
|
86
87
|
this.stopPinging();
|
|
87
88
|
this._reconnectPolicy.schedule(connectSocket, url);
|
|
88
89
|
};
|
|
89
90
|
this._socket.onerror = (error)=>{
|
|
90
|
-
if (this._disconnected) return;
|
|
91
|
+
if (this._disconnected || this._unauthorized) return;
|
|
91
92
|
console.log(`Error with connection for '${url}' - ${error}`);
|
|
92
93
|
this.stopPinging();
|
|
93
94
|
this._reconnectPolicy.schedule(connectSocket, url);
|
|
@@ -100,7 +101,7 @@ var WebSocketMessage = require('./WebSocketMessage.js');
|
|
|
100
101
|
this.handleMessage(ev.data, dataReceived);
|
|
101
102
|
};
|
|
102
103
|
};
|
|
103
|
-
if (this._disconnected) return;
|
|
104
|
+
if (this._disconnected || this._unauthorized) return;
|
|
104
105
|
connectSocket();
|
|
105
106
|
}
|
|
106
107
|
/** @inheritdoc */ disconnect() {
|
|
@@ -148,12 +149,31 @@ var WebSocketMessage = require('./WebSocketMessage.js');
|
|
|
148
149
|
// For new-style messages (type === 'Data') the query result is wrapped in message.data.
|
|
149
150
|
// For legacy/backward-compatible messages (no type) the entire message IS the query result.
|
|
150
151
|
const data = message.type === WebSocketMessage.WebSocketMessageType.Data ? message.data : message;
|
|
151
|
-
|
|
152
|
+
const result = data;
|
|
153
|
+
if (result?.isAuthorized === false) {
|
|
154
|
+
this.handleUnauthorized(result, dataReceived);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
dataReceived(result);
|
|
152
158
|
}
|
|
153
159
|
} catch (error) {
|
|
154
160
|
console.error('Error parsing WebSocket message:', error);
|
|
155
161
|
}
|
|
156
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Handles the terminal result the server sends when the caller is no longer authorized for the query it is
|
|
165
|
+
* subscribed to. The subscription is over for good, so the result is delivered and the automatic reconnect is
|
|
166
|
+
* suppressed — reconnecting would re-subscribe, be denied again, and loop.
|
|
167
|
+
* @param {QueryResult<TDataType>} result The unauthorized result to deliver.
|
|
168
|
+
* @param {DataReceived<TDataType>} dataReceived Callback that receives the result.
|
|
169
|
+
*/ handleUnauthorized(result, dataReceived) {
|
|
170
|
+
console.warn(`Connection for '${this._url}' was ended by the server - no longer authorized`);
|
|
171
|
+
this._unauthorized = true;
|
|
172
|
+
this.stopPinging();
|
|
173
|
+
this._reconnectPolicy.cancel();
|
|
174
|
+
dataReceived(result);
|
|
175
|
+
this._socket?.close();
|
|
176
|
+
}
|
|
157
177
|
handlePong(message) {
|
|
158
178
|
if (message.timestamp && this._lastPingSentTime) {
|
|
159
179
|
const latency = Date.now() - message.timestamp;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ObservableQueryConnection.js","sources":["../../../queries/ObservableQueryConnection.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 { IObservableQueryConnection } from './IObservableQueryConnection';\nimport { IReconnectPolicy } from './IReconnectPolicy';\nimport { ReconnectPolicy } from './ReconnectPolicy';\nimport { QueryResult } from './QueryResult';\nimport { WebSocketMessage, WebSocketMessageType } from './WebSocketMessage';\n\nexport type DataReceived<TDataType> = (data: QueryResult<TDataType>) => void;\n\n/**\n * Represents the connection for an observable query.\n */\nexport class ObservableQueryConnection<TDataType> implements IObservableQueryConnection<TDataType> {\n\n private _socket!: WebSocket;\n private _disconnected = false;\n private _url: string;\n private _pingInterval?: ReturnType<typeof setInterval>;\n private _pingIntervalMs: number = 10000;\n private _lastPingSentTime?: number;\n private _lastPongLatency: number = 0;\n private _latencySamples: number[] = [];\n private _connectionStartTime?: number;\n\n /**\n * Initializes a new instance of the {@link ObservableQueryConnection<TDataType>} class.\n * @param {Url} url The fully qualified Url.\n * @param {string} _microservice The microservice name.\n * @param {number} pingIntervalMs The ping interval in milliseconds (default: 10000).\n * @param {IReconnectPolicy} reconnectPolicy The reconnect policy to use (default: {@link ReconnectPolicy}).\n */\n constructor(url: URL, private readonly _microservice: string, pingIntervalMs: number = 10000, private readonly _reconnectPolicy: IReconnectPolicy = new ReconnectPolicy()) {\n this._pingIntervalMs = pingIntervalMs;\n const secure = url.protocol?.indexOf('https') === 0 || false;\n\n this._url = `${secure ? 'wss' : 'ws'}://${url.host}${url.pathname}${url.search}`;\n if (this._microservice?.length > 0) {\n const microserviceParam = `${Globals.microserviceWSQueryArgument}=${this._microservice}`;\n if (this._url.indexOf('?') > 0) {\n this._url = `${this._url}&${microserviceParam}`;\n } else {\n this._url = `${this._url}?${microserviceParam}`;\n }\n }\n }\n\n /**\n * Disposes the connection.\n */\n dispose() {\n this.disconnect();\n }\n\n /**\n * Gets the latency of the last ping/pong sequence in milliseconds.\n */\n get lastPingLatency(): number {\n return this._lastPongLatency;\n }\n\n /**\n * Gets the average latency since the connection started in milliseconds.\n */\n get averageLatency(): number {\n if (this._latencySamples.length === 0) {\n return 0;\n }\n const sum = this._latencySamples.reduce((acc, val) => acc + val, 0);\n return sum / this._latencySamples.length;\n }\n\n /** @inheritdoc */\n connect(dataReceived: DataReceived<TDataType>, queryArguments?: object) {\n let url = this._url;\n if (queryArguments) {\n if (url.indexOf('?') < 0) {\n url = `${url}?`;\n } else {\n url = `${url}&`;\n }\n const query = Object.keys(queryArguments).map(key => `${key}=${queryArguments[key]}`).join('&');\n url = `${url}${query}`;\n }\n\n const connectSocket = () => {\n this._socket = new WebSocket(url);\n this._socket.onopen = () => {\n if (this._disconnected) return;\n console.log(`Connection for '${url}' established`);\n this._reconnectPolicy.reset();\n this._connectionStartTime = Date.now();\n this.startPinging();\n };\n this._socket.onclose = () => {\n if (this._disconnected) return;\n console.log(`Unexpected connection closed for route '${url}'`);\n this.stopPinging();\n this._reconnectPolicy.schedule(connectSocket, url);\n };\n this._socket.onerror = (error) => {\n if (this._disconnected) return;\n console.log(`Error with connection for '${url}' - ${error}`);\n this.stopPinging();\n this._reconnectPolicy.schedule(connectSocket, url);\n };\n this._socket.onmessage = (ev) => {\n if (this._disconnected) {\n console.log('Received message after closing connection');\n return;\n }\n this.handleMessage(ev.data, dataReceived);\n };\n };\n\n if (this._disconnected) return;\n connectSocket();\n }\n\n /** @inheritdoc */\n disconnect() {\n if (this._disconnected) {\n return;\n }\n console.log(`Disconnecting '${this._url}'`);\n this._disconnected = true;\n this._reconnectPolicy.cancel();\n this.stopPinging();\n this._socket?.close();\n console.log(`Connection for '${this._url}' closed`);\n this._socket = undefined!;\n }\n\n private startPinging() {\n this.stopPinging();\n this._pingInterval = setInterval(() => {\n this.sendPing();\n }, this._pingIntervalMs);\n }\n\n private stopPinging() {\n if (this._pingInterval) {\n clearInterval(this._pingInterval);\n this._pingInterval = undefined;\n }\n }\n\n private sendPing() {\n if (this._disconnected || !this._socket || this._socket.readyState !== WebSocket.OPEN) {\n return;\n }\n\n this._lastPingSentTime = Date.now();\n const pingMessage: WebSocketMessage = {\n type: WebSocketMessageType.Ping,\n timestamp: this._lastPingSentTime\n };\n\n this._socket.send(JSON.stringify(pingMessage));\n }\n\n private handleMessage(rawData: string, dataReceived: DataReceived<TDataType>) {\n try {\n const message = JSON.parse(rawData) as WebSocketMessage;\n\n // Handle messages based on type\n if (message.type === WebSocketMessageType.Pong) {\n this.handlePong(message);\n } else if (message.type === WebSocketMessageType.Data || !message.type) {\n // For new-style messages (type === 'Data') the query result is wrapped in message.data.\n // For legacy/backward-compatible messages (no type) the entire message IS the query result.\n const data = message.type === WebSocketMessageType.Data ? message.data : message;\n dataReceived(data as QueryResult<TDataType>);\n }\n } catch (error) {\n console.error('Error parsing WebSocket message:', error);\n }\n }\n\n private handlePong(message: WebSocketMessage) {\n if (message.timestamp && this._lastPingSentTime) {\n const latency = Date.now() - message.timestamp;\n this._lastPongLatency = latency;\n this._latencySamples.push(latency);\n\n // Keep only the last 100 samples for average calculation\n if (this._latencySamples.length > 100) {\n this._latencySamples.shift();\n }\n }\n }\n}\n"],"names":["ObservableQueryConnection","_socket","_disconnected","_url","_pingInterval","_pingIntervalMs","_lastPingSentTime","_lastPongLatency","_latencySamples","_connectionStartTime","url","_microservice","pingIntervalMs","_reconnectPolicy","ReconnectPolicy","secure","protocol","indexOf","host","pathname","search","length","microserviceParam","Globals","microserviceWSQueryArgument","dispose","disconnect","lastPingLatency","averageLatency","sum","reduce","acc","val","connect","dataReceived","queryArguments","query","Object","keys","map","key","join","connectSocket","WebSocket","onopen","console","log","reset","Date","now","startPinging","onclose","stopPinging","schedule","onerror","error","onmessage","ev","handleMessage","data","cancel","close","undefined","setInterval","sendPing","clearInterval","readyState","OPEN","pingMessage","type","WebSocketMessageType","Ping","timestamp","send","JSON","stringify","rawData","message","parse","Pong","handlePong","Data","latency","push","shift"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ObservableQueryConnection.js","sources":["../../../queries/ObservableQueryConnection.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 { IObservableQueryConnection } from './IObservableQueryConnection';\nimport { IReconnectPolicy } from './IReconnectPolicy';\nimport { ReconnectPolicy } from './ReconnectPolicy';\nimport { QueryResult } from './QueryResult';\nimport { WebSocketMessage, WebSocketMessageType } from './WebSocketMessage';\n\nexport type DataReceived<TDataType> = (data: QueryResult<TDataType>) => void;\n\n/**\n * Represents the connection for an observable query.\n */\nexport class ObservableQueryConnection<TDataType> implements IObservableQueryConnection<TDataType> {\n\n private _socket!: WebSocket;\n private _disconnected = false;\n private _unauthorized = false;\n private _url: string;\n private _pingInterval?: ReturnType<typeof setInterval>;\n private _pingIntervalMs: number = 10000;\n private _lastPingSentTime?: number;\n private _lastPongLatency: number = 0;\n private _latencySamples: number[] = [];\n private _connectionStartTime?: number;\n\n /**\n * Initializes a new instance of the {@link ObservableQueryConnection<TDataType>} class.\n * @param {Url} url The fully qualified Url.\n * @param {string} _microservice The microservice name.\n * @param {number} pingIntervalMs The ping interval in milliseconds (default: 10000).\n * @param {IReconnectPolicy} reconnectPolicy The reconnect policy to use (default: {@link ReconnectPolicy}).\n */\n constructor(url: URL, private readonly _microservice: string, pingIntervalMs: number = 10000, private readonly _reconnectPolicy: IReconnectPolicy = new ReconnectPolicy()) {\n this._pingIntervalMs = pingIntervalMs;\n const secure = url.protocol?.indexOf('https') === 0 || false;\n\n this._url = `${secure ? 'wss' : 'ws'}://${url.host}${url.pathname}${url.search}`;\n if (this._microservice?.length > 0) {\n const microserviceParam = `${Globals.microserviceWSQueryArgument}=${this._microservice}`;\n if (this._url.indexOf('?') > 0) {\n this._url = `${this._url}&${microserviceParam}`;\n } else {\n this._url = `${this._url}?${microserviceParam}`;\n }\n }\n }\n\n /**\n * Disposes the connection.\n */\n dispose() {\n this.disconnect();\n }\n\n /**\n * Gets the latency of the last ping/pong sequence in milliseconds.\n */\n get lastPingLatency(): number {\n return this._lastPongLatency;\n }\n\n /**\n * Gets the average latency since the connection started in milliseconds.\n */\n get averageLatency(): number {\n if (this._latencySamples.length === 0) {\n return 0;\n }\n const sum = this._latencySamples.reduce((acc, val) => acc + val, 0);\n return sum / this._latencySamples.length;\n }\n\n /** @inheritdoc */\n connect(dataReceived: DataReceived<TDataType>, queryArguments?: object) {\n let url = this._url;\n if (queryArguments) {\n if (url.indexOf('?') < 0) {\n url = `${url}?`;\n } else {\n url = `${url}&`;\n }\n const query = Object.keys(queryArguments).map(key => `${key}=${queryArguments[key]}`).join('&');\n url = `${url}${query}`;\n }\n\n const connectSocket = () => {\n this._socket = new WebSocket(url);\n this._socket.onopen = () => {\n if (this._disconnected) return;\n console.log(`Connection for '${url}' established`);\n this._reconnectPolicy.reset();\n this._connectionStartTime = Date.now();\n this.startPinging();\n };\n this._socket.onclose = () => {\n if (this._disconnected || this._unauthorized) return;\n console.log(`Unexpected connection closed for route '${url}'`);\n this.stopPinging();\n this._reconnectPolicy.schedule(connectSocket, url);\n };\n this._socket.onerror = (error) => {\n if (this._disconnected || this._unauthorized) return;\n console.log(`Error with connection for '${url}' - ${error}`);\n this.stopPinging();\n this._reconnectPolicy.schedule(connectSocket, url);\n };\n this._socket.onmessage = (ev) => {\n if (this._disconnected) {\n console.log('Received message after closing connection');\n return;\n }\n this.handleMessage(ev.data, dataReceived);\n };\n };\n\n if (this._disconnected || this._unauthorized) return;\n connectSocket();\n }\n\n /** @inheritdoc */\n disconnect() {\n if (this._disconnected) {\n return;\n }\n console.log(`Disconnecting '${this._url}'`);\n this._disconnected = true;\n this._reconnectPolicy.cancel();\n this.stopPinging();\n this._socket?.close();\n console.log(`Connection for '${this._url}' closed`);\n this._socket = undefined!;\n }\n\n private startPinging() {\n this.stopPinging();\n this._pingInterval = setInterval(() => {\n this.sendPing();\n }, this._pingIntervalMs);\n }\n\n private stopPinging() {\n if (this._pingInterval) {\n clearInterval(this._pingInterval);\n this._pingInterval = undefined;\n }\n }\n\n private sendPing() {\n if (this._disconnected || !this._socket || this._socket.readyState !== WebSocket.OPEN) {\n return;\n }\n\n this._lastPingSentTime = Date.now();\n const pingMessage: WebSocketMessage = {\n type: WebSocketMessageType.Ping,\n timestamp: this._lastPingSentTime\n };\n\n this._socket.send(JSON.stringify(pingMessage));\n }\n\n private handleMessage(rawData: string, dataReceived: DataReceived<TDataType>) {\n try {\n const message = JSON.parse(rawData) as WebSocketMessage;\n\n // Handle messages based on type\n if (message.type === WebSocketMessageType.Pong) {\n this.handlePong(message);\n } else if (message.type === WebSocketMessageType.Data || !message.type) {\n // For new-style messages (type === 'Data') the query result is wrapped in message.data.\n // For legacy/backward-compatible messages (no type) the entire message IS the query result.\n const data = message.type === WebSocketMessageType.Data ? message.data : message;\n const result = data as QueryResult<TDataType>;\n\n if (result?.isAuthorized === false) {\n this.handleUnauthorized(result, dataReceived);\n return;\n }\n\n dataReceived(result);\n }\n } catch (error) {\n console.error('Error parsing WebSocket message:', error);\n }\n }\n\n /**\n * Handles the terminal result the server sends when the caller is no longer authorized for the query it is\n * subscribed to. The subscription is over for good, so the result is delivered and the automatic reconnect is\n * suppressed — reconnecting would re-subscribe, be denied again, and loop.\n * @param {QueryResult<TDataType>} result The unauthorized result to deliver.\n * @param {DataReceived<TDataType>} dataReceived Callback that receives the result.\n */\n private handleUnauthorized(result: QueryResult<TDataType>, dataReceived: DataReceived<TDataType>) {\n console.warn(`Connection for '${this._url}' was ended by the server - no longer authorized`);\n this._unauthorized = true;\n this.stopPinging();\n this._reconnectPolicy.cancel();\n dataReceived(result);\n this._socket?.close();\n }\n\n private handlePong(message: WebSocketMessage) {\n if (message.timestamp && this._lastPingSentTime) {\n const latency = Date.now() - message.timestamp;\n this._lastPongLatency = latency;\n this._latencySamples.push(latency);\n\n // Keep only the last 100 samples for average calculation\n if (this._latencySamples.length > 100) {\n this._latencySamples.shift();\n }\n }\n }\n}\n"],"names":["ObservableQueryConnection","_socket","_disconnected","_unauthorized","_url","_pingInterval","_pingIntervalMs","_lastPingSentTime","_lastPongLatency","_latencySamples","_connectionStartTime","url","_microservice","pingIntervalMs","_reconnectPolicy","ReconnectPolicy","secure","protocol","indexOf","host","pathname","search","length","microserviceParam","Globals","microserviceWSQueryArgument","dispose","disconnect","lastPingLatency","averageLatency","sum","reduce","acc","val","connect","dataReceived","queryArguments","query","Object","keys","map","key","join","connectSocket","WebSocket","onopen","console","log","reset","Date","now","startPinging","onclose","stopPinging","schedule","onerror","error","onmessage","ev","handleMessage","data","cancel","close","undefined","setInterval","sendPing","clearInterval","readyState","OPEN","pingMessage","type","WebSocketMessageType","Ping","timestamp","send","JSON","stringify","rawData","message","parse","Pong","handlePong","Data","result","isAuthorized","handleUnauthorized","warn","latency","push","shift"],"mappings":"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"}
|
|
@@ -19,6 +19,7 @@ var Globals = require('../Globals.js');
|
|
|
19
19
|
_url;
|
|
20
20
|
_eventSource;
|
|
21
21
|
_disconnected = false;
|
|
22
|
+
_unauthorized = false;
|
|
22
23
|
/** @inheritdoc */ lastPingLatency = 0;
|
|
23
24
|
/** @inheritdoc */ averageLatency = 0;
|
|
24
25
|
/**
|
|
@@ -28,7 +29,7 @@ var Globals = require('../Globals.js');
|
|
|
28
29
|
this._url = _url;
|
|
29
30
|
}
|
|
30
31
|
/** @inheritdoc */ connect(dataReceived, queryArguments) {
|
|
31
|
-
if (this._disconnected) return;
|
|
32
|
+
if (this._disconnected || this._unauthorized) return;
|
|
32
33
|
// Guard against environments where EventSource is not available (e.g. Node.js, SSR)
|
|
33
34
|
// and no custom factory has been supplied to substitute it.
|
|
34
35
|
if (!Globals.Globals.eventSourceFactory && typeof EventSource === 'undefined') {
|
|
@@ -47,13 +48,17 @@ var Globals = require('../Globals.js');
|
|
|
47
48
|
if (this._disconnected) return;
|
|
48
49
|
try {
|
|
49
50
|
const result = JSON.parse(event.data);
|
|
51
|
+
if (result?.isAuthorized === false) {
|
|
52
|
+
this.handleUnauthorized(url, result, dataReceived);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
50
55
|
dataReceived(result);
|
|
51
56
|
} catch (error) {
|
|
52
57
|
console.error('SSE: error parsing message', error);
|
|
53
58
|
}
|
|
54
59
|
};
|
|
55
60
|
this._eventSource.onerror = ()=>{
|
|
56
|
-
if (this._disconnected) return;
|
|
61
|
+
if (this._disconnected || this._unauthorized) return;
|
|
57
62
|
console.warn(`SSE: connection error for '${url}', EventSource will retry automatically.`);
|
|
58
63
|
};
|
|
59
64
|
}
|
|
@@ -63,6 +68,23 @@ var Globals = require('../Globals.js');
|
|
|
63
68
|
this._eventSource?.close();
|
|
64
69
|
this._eventSource = undefined;
|
|
65
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Handles the terminal result the server sends when the caller is no longer authorized for the query it is
|
|
73
|
+
* subscribed to.
|
|
74
|
+
*
|
|
75
|
+
* Unlike a WebSocket, an `EventSource` re-establishes a stream the server ended - the server closing its response
|
|
76
|
+
* is precisely the signal the browser retries on, roughly every three seconds. Each retry re-runs the whole query
|
|
77
|
+
* pipeline server side just to be denied again, so the stream is only actually terminal if this client closes it.
|
|
78
|
+
* @param {string} url The stream url, for diagnostics.
|
|
79
|
+
* @param {QueryResult<TDataType>} result The unauthorized result to deliver.
|
|
80
|
+
* @param {DataReceived<TDataType>} dataReceived Callback that receives the result.
|
|
81
|
+
*/ handleUnauthorized(url, result, dataReceived) {
|
|
82
|
+
console.warn(`SSE: stream for '${url}' was ended by the server - no longer authorized`);
|
|
83
|
+
this._unauthorized = true;
|
|
84
|
+
this._eventSource?.close();
|
|
85
|
+
this._eventSource = undefined;
|
|
86
|
+
dataReceived(result);
|
|
87
|
+
}
|
|
66
88
|
}
|
|
67
89
|
|
|
68
90
|
exports.SSE_HUB_ROUTE = SSE_HUB_ROUTE;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ServerSentEventQueryConnection.js","sources":["../../../queries/ServerSentEventQueryConnection.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 { IObservableQueryConnection } from './IObservableQueryConnection';\nimport { DataReceived } from './ObservableQueryConnection';\nimport { QueryResult } from './QueryResult';\n\n/**\n * The SSE demultiplexer route used when connecting through the multiplexed observable query endpoint.\n */\nexport const SSE_HUB_ROUTE = '/.cratis/queries/sse';\n\n/**\n * Represents a direct Server-Sent Events (SSE) connection for a single observable query.\n *\n * In direct mode the URL points to the per-query endpoint (e.g. `/api/queries/latest`).\n * The backend detects the `Accept: text/event-stream` header and streams results directly.\n *\n * The caller (typically {@link createObservableQueryConnection}) decides which URL to use;\n * this class is transport-agnostic beyond being SSE.\n */\nexport class ServerSentEventQueryConnection<TDataType> implements IObservableQueryConnection<TDataType> {\n private _eventSource?: EventSource;\n private _disconnected = false;\n\n /** @inheritdoc */\n readonly lastPingLatency: number = 0;\n\n /** @inheritdoc */\n readonly averageLatency: number = 0;\n\n /**\n * Initializes a new instance of {@link ServerSentEventQueryConnection}.\n * @param {URL} url The fully qualified URL of the SSE endpoint (including query parameters).\n */\n constructor(private readonly _url: URL) {}\n\n /** @inheritdoc */\n connect(dataReceived: DataReceived<TDataType>, queryArguments?: object): void {\n if (this._disconnected) return;\n\n // Guard against environments where EventSource is not available (e.g. Node.js, SSR)\n // and no custom factory has been supplied to substitute it.\n if (!Globals.eventSourceFactory && typeof EventSource === 'undefined') {\n return;\n }\n\n let url = this._url.toString();\n if (queryArguments) {\n const separator = url.includes('?') ? '&' : '?';\n const query = Object.entries(queryArguments)\n .filter(([, value]) => value !== undefined && value !== null)\n .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)\n .join('&');\n if (query) {\n url = `${url}${separator}${query}`;\n }\n }\n\n this._eventSource = Globals.eventSourceFactory ? Globals.eventSourceFactory(url) : new EventSource(url);\n\n this._eventSource.onmessage = (event: MessageEvent) => {\n if (this._disconnected) return;\n try {\n const result = JSON.parse(event.data as string) as QueryResult<TDataType>;\n dataReceived(result);\n } catch (error) {\n console.error('SSE: error parsing message', error);\n }\n };\n\n this._eventSource.onerror = () => {\n if (this._disconnected) return;\n console.warn(`SSE: connection error for '${url}', EventSource will retry automatically.`);\n };\n }\n\n /** @inheritdoc */\n disconnect(): void {\n if (this._disconnected) return;\n this._disconnected = true;\n this._eventSource?.close();\n this._eventSource = undefined;\n }\n}\n"],"names":["SSE_HUB_ROUTE","ServerSentEventQueryConnection","_eventSource","_disconnected","lastPingLatency","averageLatency","_url","connect","dataReceived","queryArguments","Globals","eventSourceFactory","EventSource","url","toString","separator","includes","query","Object","entries","filter","value","undefined","map","key","encodeURIComponent","String","join","onmessage","event","result","JSON","parse","data","error","console","onerror","warn","disconnect","close"],"mappings":";;;;AAAA;AACA;AAOA;;IAGO,MAAMA,aAAAA,GAAgB;AAE7B;;;;;;;;AAQC,IACM,MAAMC,8BAAAA,CAAAA;;IACDC,YAAAA;AACAC,IAAAA,aAAAA,GAAgB,KAAA;uBAGxB,eAASC,GAA0B,CAAA;uBAGnC,cAASC,GAAyB,CAAA;AAElC;;;QAIA,WAAA,CAAY,IAA0B,CAAE;aAAXC,IAAAA,GAAAA,IAAAA;AAAY,IAAA;AAEzC,uBACAC,OAAAA,CAAQC,YAAqC,EAAEC,cAAuB,EAAQ;
|
|
1
|
+
{"version":3,"file":"ServerSentEventQueryConnection.js","sources":["../../../queries/ServerSentEventQueryConnection.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 { IObservableQueryConnection } from './IObservableQueryConnection';\nimport { DataReceived } from './ObservableQueryConnection';\nimport { QueryResult } from './QueryResult';\n\n/**\n * The SSE demultiplexer route used when connecting through the multiplexed observable query endpoint.\n */\nexport const SSE_HUB_ROUTE = '/.cratis/queries/sse';\n\n/**\n * Represents a direct Server-Sent Events (SSE) connection for a single observable query.\n *\n * In direct mode the URL points to the per-query endpoint (e.g. `/api/queries/latest`).\n * The backend detects the `Accept: text/event-stream` header and streams results directly.\n *\n * The caller (typically {@link createObservableQueryConnection}) decides which URL to use;\n * this class is transport-agnostic beyond being SSE.\n */\nexport class ServerSentEventQueryConnection<TDataType> implements IObservableQueryConnection<TDataType> {\n private _eventSource?: EventSource;\n private _disconnected = false;\n private _unauthorized = false;\n\n /** @inheritdoc */\n readonly lastPingLatency: number = 0;\n\n /** @inheritdoc */\n readonly averageLatency: number = 0;\n\n /**\n * Initializes a new instance of {@link ServerSentEventQueryConnection}.\n * @param {URL} url The fully qualified URL of the SSE endpoint (including query parameters).\n */\n constructor(private readonly _url: URL) {}\n\n /** @inheritdoc */\n connect(dataReceived: DataReceived<TDataType>, queryArguments?: object): void {\n if (this._disconnected || this._unauthorized) return;\n\n // Guard against environments where EventSource is not available (e.g. Node.js, SSR)\n // and no custom factory has been supplied to substitute it.\n if (!Globals.eventSourceFactory && typeof EventSource === 'undefined') {\n return;\n }\n\n let url = this._url.toString();\n if (queryArguments) {\n const separator = url.includes('?') ? '&' : '?';\n const query = Object.entries(queryArguments)\n .filter(([, value]) => value !== undefined && value !== null)\n .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)\n .join('&');\n if (query) {\n url = `${url}${separator}${query}`;\n }\n }\n\n this._eventSource = Globals.eventSourceFactory ? Globals.eventSourceFactory(url) : new EventSource(url);\n\n this._eventSource.onmessage = (event: MessageEvent) => {\n if (this._disconnected) return;\n try {\n const result = JSON.parse(event.data as string) as QueryResult<TDataType>;\n if ((result as unknown as { isAuthorized?: boolean })?.isAuthorized === false) {\n this.handleUnauthorized(url, result, dataReceived);\n return;\n }\n dataReceived(result);\n } catch (error) {\n console.error('SSE: error parsing message', error);\n }\n };\n\n this._eventSource.onerror = () => {\n if (this._disconnected || this._unauthorized) return;\n console.warn(`SSE: connection error for '${url}', EventSource will retry automatically.`);\n };\n }\n\n /** @inheritdoc */\n disconnect(): void {\n if (this._disconnected) return;\n this._disconnected = true;\n this._eventSource?.close();\n this._eventSource = undefined;\n }\n\n /**\n * Handles the terminal result the server sends when the caller is no longer authorized for the query it is\n * subscribed to.\n *\n * Unlike a WebSocket, an `EventSource` re-establishes a stream the server ended - the server closing its response\n * is precisely the signal the browser retries on, roughly every three seconds. Each retry re-runs the whole query\n * pipeline server side just to be denied again, so the stream is only actually terminal if this client closes it.\n * @param {string} url The stream url, for diagnostics.\n * @param {QueryResult<TDataType>} result The unauthorized result to deliver.\n * @param {DataReceived<TDataType>} dataReceived Callback that receives the result.\n */\n private handleUnauthorized(url: string, result: QueryResult<TDataType>, dataReceived: DataReceived<TDataType>): void {\n console.warn(`SSE: stream for '${url}' was ended by the server - no longer authorized`);\n this._unauthorized = true;\n this._eventSource?.close();\n this._eventSource = undefined;\n dataReceived(result);\n }\n}\n"],"names":["SSE_HUB_ROUTE","ServerSentEventQueryConnection","_eventSource","_disconnected","_unauthorized","lastPingLatency","averageLatency","_url","connect","dataReceived","queryArguments","Globals","eventSourceFactory","EventSource","url","toString","separator","includes","query","Object","entries","filter","value","undefined","map","key","encodeURIComponent","String","join","onmessage","event","result","JSON","parse","data","isAuthorized","handleUnauthorized","error","console","onerror","warn","disconnect","close"],"mappings":"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"}
|
|
@@ -7,6 +7,7 @@ export declare class ObservableQueryConnection<TDataType> implements IObservable
|
|
|
7
7
|
private readonly _reconnectPolicy;
|
|
8
8
|
private _socket;
|
|
9
9
|
private _disconnected;
|
|
10
|
+
private _unauthorized;
|
|
10
11
|
private _url;
|
|
11
12
|
private _pingInterval?;
|
|
12
13
|
private _pingIntervalMs;
|
|
@@ -24,6 +25,7 @@ export declare class ObservableQueryConnection<TDataType> implements IObservable
|
|
|
24
25
|
private stopPinging;
|
|
25
26
|
private sendPing;
|
|
26
27
|
private handleMessage;
|
|
28
|
+
private handleUnauthorized;
|
|
27
29
|
private handlePong;
|
|
28
30
|
}
|
|
29
31
|
//# sourceMappingURL=ObservableQueryConnection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ObservableQueryConnection.d.ts","sourceRoot":"","sources":["../../../queries/ObservableQueryConnection.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5C,MAAM,MAAM,YAAY,CAAC,SAAS,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC;AAK7E,qBAAa,yBAAyB,CAAC,SAAS,CAAE,YAAW,0BAA0B,CAAC,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"ObservableQueryConnection.d.ts","sourceRoot":"","sources":["../../../queries/ObservableQueryConnection.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5C,MAAM,MAAM,YAAY,CAAC,SAAS,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC;AAK7E,qBAAa,yBAAyB,CAAC,SAAS,CAAE,YAAW,0BAA0B,CAAC,SAAS,CAAC;IAoBxE,OAAO,CAAC,QAAQ,CAAC,aAAa;IAA0C,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAlB/H,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,aAAa,CAAC,CAAiC;IACvD,OAAO,CAAC,eAAe,CAAiB;IACxC,OAAO,CAAC,iBAAiB,CAAC,CAAS;IACnC,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,eAAe,CAAgB;IACvC,OAAO,CAAC,oBAAoB,CAAC,CAAS;IAStC,YAAY,GAAG,EAAE,GAAG,EAAmB,aAAa,EAAE,MAAM,EAAE,cAAc,GAAE,MAAc,EAAmB,gBAAgB,GAAE,gBAAwC,EAaxK;IAKD,OAAO,SAEN;IAKD,IAAI,eAAe,IAAI,MAAM,CAE5B;IAKD,IAAI,cAAc,IAAI,MAAM,CAM3B;IAGD,OAAO,CAAC,YAAY,EAAE,YAAY,CAAC,SAAS,CAAC,EAAE,cAAc,CAAC,EAAE,MAAM,QA4CrE;IAGD,UAAU,SAWT;IAED,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,QAAQ;IAchB,OAAO,CAAC,aAAa;IAgCrB,OAAO,CAAC,kBAAkB;IAS1B,OAAO,CAAC,UAAU;CAYrB"}
|
|
@@ -11,6 +11,7 @@ import { WebSocketMessageType } from './WebSocketMessage.js';
|
|
|
11
11
|
_reconnectPolicy;
|
|
12
12
|
_socket;
|
|
13
13
|
_disconnected = false;
|
|
14
|
+
_unauthorized = false;
|
|
14
15
|
_url;
|
|
15
16
|
_pingInterval;
|
|
16
17
|
_pingIntervalMs = 10000;
|
|
@@ -79,13 +80,13 @@ import { WebSocketMessageType } from './WebSocketMessage.js';
|
|
|
79
80
|
this.startPinging();
|
|
80
81
|
};
|
|
81
82
|
this._socket.onclose = ()=>{
|
|
82
|
-
if (this._disconnected) return;
|
|
83
|
+
if (this._disconnected || this._unauthorized) return;
|
|
83
84
|
console.log(`Unexpected connection closed for route '${url}'`);
|
|
84
85
|
this.stopPinging();
|
|
85
86
|
this._reconnectPolicy.schedule(connectSocket, url);
|
|
86
87
|
};
|
|
87
88
|
this._socket.onerror = (error)=>{
|
|
88
|
-
if (this._disconnected) return;
|
|
89
|
+
if (this._disconnected || this._unauthorized) return;
|
|
89
90
|
console.log(`Error with connection for '${url}' - ${error}`);
|
|
90
91
|
this.stopPinging();
|
|
91
92
|
this._reconnectPolicy.schedule(connectSocket, url);
|
|
@@ -98,7 +99,7 @@ import { WebSocketMessageType } from './WebSocketMessage.js';
|
|
|
98
99
|
this.handleMessage(ev.data, dataReceived);
|
|
99
100
|
};
|
|
100
101
|
};
|
|
101
|
-
if (this._disconnected) return;
|
|
102
|
+
if (this._disconnected || this._unauthorized) return;
|
|
102
103
|
connectSocket();
|
|
103
104
|
}
|
|
104
105
|
/** @inheritdoc */ disconnect() {
|
|
@@ -146,12 +147,31 @@ import { WebSocketMessageType } from './WebSocketMessage.js';
|
|
|
146
147
|
// For new-style messages (type === 'Data') the query result is wrapped in message.data.
|
|
147
148
|
// For legacy/backward-compatible messages (no type) the entire message IS the query result.
|
|
148
149
|
const data = message.type === WebSocketMessageType.Data ? message.data : message;
|
|
149
|
-
|
|
150
|
+
const result = data;
|
|
151
|
+
if (result?.isAuthorized === false) {
|
|
152
|
+
this.handleUnauthorized(result, dataReceived);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
dataReceived(result);
|
|
150
156
|
}
|
|
151
157
|
} catch (error) {
|
|
152
158
|
console.error('Error parsing WebSocket message:', error);
|
|
153
159
|
}
|
|
154
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Handles the terminal result the server sends when the caller is no longer authorized for the query it is
|
|
163
|
+
* subscribed to. The subscription is over for good, so the result is delivered and the automatic reconnect is
|
|
164
|
+
* suppressed — reconnecting would re-subscribe, be denied again, and loop.
|
|
165
|
+
* @param {QueryResult<TDataType>} result The unauthorized result to deliver.
|
|
166
|
+
* @param {DataReceived<TDataType>} dataReceived Callback that receives the result.
|
|
167
|
+
*/ handleUnauthorized(result, dataReceived) {
|
|
168
|
+
console.warn(`Connection for '${this._url}' was ended by the server - no longer authorized`);
|
|
169
|
+
this._unauthorized = true;
|
|
170
|
+
this.stopPinging();
|
|
171
|
+
this._reconnectPolicy.cancel();
|
|
172
|
+
dataReceived(result);
|
|
173
|
+
this._socket?.close();
|
|
174
|
+
}
|
|
155
175
|
handlePong(message) {
|
|
156
176
|
if (message.timestamp && this._lastPingSentTime) {
|
|
157
177
|
const latency = Date.now() - message.timestamp;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ObservableQueryConnection.js","sources":["../../../queries/ObservableQueryConnection.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 { IObservableQueryConnection } from './IObservableQueryConnection';\nimport { IReconnectPolicy } from './IReconnectPolicy';\nimport { ReconnectPolicy } from './ReconnectPolicy';\nimport { QueryResult } from './QueryResult';\nimport { WebSocketMessage, WebSocketMessageType } from './WebSocketMessage';\n\nexport type DataReceived<TDataType> = (data: QueryResult<TDataType>) => void;\n\n/**\n * Represents the connection for an observable query.\n */\nexport class ObservableQueryConnection<TDataType> implements IObservableQueryConnection<TDataType> {\n\n private _socket!: WebSocket;\n private _disconnected = false;\n private _url: string;\n private _pingInterval?: ReturnType<typeof setInterval>;\n private _pingIntervalMs: number = 10000;\n private _lastPingSentTime?: number;\n private _lastPongLatency: number = 0;\n private _latencySamples: number[] = [];\n private _connectionStartTime?: number;\n\n /**\n * Initializes a new instance of the {@link ObservableQueryConnection<TDataType>} class.\n * @param {Url} url The fully qualified Url.\n * @param {string} _microservice The microservice name.\n * @param {number} pingIntervalMs The ping interval in milliseconds (default: 10000).\n * @param {IReconnectPolicy} reconnectPolicy The reconnect policy to use (default: {@link ReconnectPolicy}).\n */\n constructor(url: URL, private readonly _microservice: string, pingIntervalMs: number = 10000, private readonly _reconnectPolicy: IReconnectPolicy = new ReconnectPolicy()) {\n this._pingIntervalMs = pingIntervalMs;\n const secure = url.protocol?.indexOf('https') === 0 || false;\n\n this._url = `${secure ? 'wss' : 'ws'}://${url.host}${url.pathname}${url.search}`;\n if (this._microservice?.length > 0) {\n const microserviceParam = `${Globals.microserviceWSQueryArgument}=${this._microservice}`;\n if (this._url.indexOf('?') > 0) {\n this._url = `${this._url}&${microserviceParam}`;\n } else {\n this._url = `${this._url}?${microserviceParam}`;\n }\n }\n }\n\n /**\n * Disposes the connection.\n */\n dispose() {\n this.disconnect();\n }\n\n /**\n * Gets the latency of the last ping/pong sequence in milliseconds.\n */\n get lastPingLatency(): number {\n return this._lastPongLatency;\n }\n\n /**\n * Gets the average latency since the connection started in milliseconds.\n */\n get averageLatency(): number {\n if (this._latencySamples.length === 0) {\n return 0;\n }\n const sum = this._latencySamples.reduce((acc, val) => acc + val, 0);\n return sum / this._latencySamples.length;\n }\n\n /** @inheritdoc */\n connect(dataReceived: DataReceived<TDataType>, queryArguments?: object) {\n let url = this._url;\n if (queryArguments) {\n if (url.indexOf('?') < 0) {\n url = `${url}?`;\n } else {\n url = `${url}&`;\n }\n const query = Object.keys(queryArguments).map(key => `${key}=${queryArguments[key]}`).join('&');\n url = `${url}${query}`;\n }\n\n const connectSocket = () => {\n this._socket = new WebSocket(url);\n this._socket.onopen = () => {\n if (this._disconnected) return;\n console.log(`Connection for '${url}' established`);\n this._reconnectPolicy.reset();\n this._connectionStartTime = Date.now();\n this.startPinging();\n };\n this._socket.onclose = () => {\n if (this._disconnected) return;\n console.log(`Unexpected connection closed for route '${url}'`);\n this.stopPinging();\n this._reconnectPolicy.schedule(connectSocket, url);\n };\n this._socket.onerror = (error) => {\n if (this._disconnected) return;\n console.log(`Error with connection for '${url}' - ${error}`);\n this.stopPinging();\n this._reconnectPolicy.schedule(connectSocket, url);\n };\n this._socket.onmessage = (ev) => {\n if (this._disconnected) {\n console.log('Received message after closing connection');\n return;\n }\n this.handleMessage(ev.data, dataReceived);\n };\n };\n\n if (this._disconnected) return;\n connectSocket();\n }\n\n /** @inheritdoc */\n disconnect() {\n if (this._disconnected) {\n return;\n }\n console.log(`Disconnecting '${this._url}'`);\n this._disconnected = true;\n this._reconnectPolicy.cancel();\n this.stopPinging();\n this._socket?.close();\n console.log(`Connection for '${this._url}' closed`);\n this._socket = undefined!;\n }\n\n private startPinging() {\n this.stopPinging();\n this._pingInterval = setInterval(() => {\n this.sendPing();\n }, this._pingIntervalMs);\n }\n\n private stopPinging() {\n if (this._pingInterval) {\n clearInterval(this._pingInterval);\n this._pingInterval = undefined;\n }\n }\n\n private sendPing() {\n if (this._disconnected || !this._socket || this._socket.readyState !== WebSocket.OPEN) {\n return;\n }\n\n this._lastPingSentTime = Date.now();\n const pingMessage: WebSocketMessage = {\n type: WebSocketMessageType.Ping,\n timestamp: this._lastPingSentTime\n };\n\n this._socket.send(JSON.stringify(pingMessage));\n }\n\n private handleMessage(rawData: string, dataReceived: DataReceived<TDataType>) {\n try {\n const message = JSON.parse(rawData) as WebSocketMessage;\n\n // Handle messages based on type\n if (message.type === WebSocketMessageType.Pong) {\n this.handlePong(message);\n } else if (message.type === WebSocketMessageType.Data || !message.type) {\n // For new-style messages (type === 'Data') the query result is wrapped in message.data.\n // For legacy/backward-compatible messages (no type) the entire message IS the query result.\n const data = message.type === WebSocketMessageType.Data ? message.data : message;\n dataReceived(data as QueryResult<TDataType>);\n }\n } catch (error) {\n console.error('Error parsing WebSocket message:', error);\n }\n }\n\n private handlePong(message: WebSocketMessage) {\n if (message.timestamp && this._lastPingSentTime) {\n const latency = Date.now() - message.timestamp;\n this._lastPongLatency = latency;\n this._latencySamples.push(latency);\n\n // Keep only the last 100 samples for average calculation\n if (this._latencySamples.length > 100) {\n this._latencySamples.shift();\n }\n }\n }\n}\n"],"names":["ObservableQueryConnection","_socket","_disconnected","_url","_pingInterval","_pingIntervalMs","_lastPingSentTime","_lastPongLatency","_latencySamples","_connectionStartTime","url","_microservice","pingIntervalMs","_reconnectPolicy","ReconnectPolicy","secure","protocol","indexOf","host","pathname","search","length","microserviceParam","Globals","microserviceWSQueryArgument","dispose","disconnect","lastPingLatency","averageLatency","sum","reduce","acc","val","connect","dataReceived","queryArguments","query","Object","keys","map","key","join","connectSocket","WebSocket","onopen","console","log","reset","Date","now","startPinging","onclose","stopPinging","schedule","onerror","error","onmessage","ev","handleMessage","data","cancel","close","undefined","setInterval","sendPing","clearInterval","readyState","OPEN","pingMessage","type","WebSocketMessageType","Ping","timestamp","send","JSON","stringify","rawData","message","parse","Pong","handlePong","Data","latency","push","shift"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ObservableQueryConnection.js","sources":["../../../queries/ObservableQueryConnection.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 { IObservableQueryConnection } from './IObservableQueryConnection';\nimport { IReconnectPolicy } from './IReconnectPolicy';\nimport { ReconnectPolicy } from './ReconnectPolicy';\nimport { QueryResult } from './QueryResult';\nimport { WebSocketMessage, WebSocketMessageType } from './WebSocketMessage';\n\nexport type DataReceived<TDataType> = (data: QueryResult<TDataType>) => void;\n\n/**\n * Represents the connection for an observable query.\n */\nexport class ObservableQueryConnection<TDataType> implements IObservableQueryConnection<TDataType> {\n\n private _socket!: WebSocket;\n private _disconnected = false;\n private _unauthorized = false;\n private _url: string;\n private _pingInterval?: ReturnType<typeof setInterval>;\n private _pingIntervalMs: number = 10000;\n private _lastPingSentTime?: number;\n private _lastPongLatency: number = 0;\n private _latencySamples: number[] = [];\n private _connectionStartTime?: number;\n\n /**\n * Initializes a new instance of the {@link ObservableQueryConnection<TDataType>} class.\n * @param {Url} url The fully qualified Url.\n * @param {string} _microservice The microservice name.\n * @param {number} pingIntervalMs The ping interval in milliseconds (default: 10000).\n * @param {IReconnectPolicy} reconnectPolicy The reconnect policy to use (default: {@link ReconnectPolicy}).\n */\n constructor(url: URL, private readonly _microservice: string, pingIntervalMs: number = 10000, private readonly _reconnectPolicy: IReconnectPolicy = new ReconnectPolicy()) {\n this._pingIntervalMs = pingIntervalMs;\n const secure = url.protocol?.indexOf('https') === 0 || false;\n\n this._url = `${secure ? 'wss' : 'ws'}://${url.host}${url.pathname}${url.search}`;\n if (this._microservice?.length > 0) {\n const microserviceParam = `${Globals.microserviceWSQueryArgument}=${this._microservice}`;\n if (this._url.indexOf('?') > 0) {\n this._url = `${this._url}&${microserviceParam}`;\n } else {\n this._url = `${this._url}?${microserviceParam}`;\n }\n }\n }\n\n /**\n * Disposes the connection.\n */\n dispose() {\n this.disconnect();\n }\n\n /**\n * Gets the latency of the last ping/pong sequence in milliseconds.\n */\n get lastPingLatency(): number {\n return this._lastPongLatency;\n }\n\n /**\n * Gets the average latency since the connection started in milliseconds.\n */\n get averageLatency(): number {\n if (this._latencySamples.length === 0) {\n return 0;\n }\n const sum = this._latencySamples.reduce((acc, val) => acc + val, 0);\n return sum / this._latencySamples.length;\n }\n\n /** @inheritdoc */\n connect(dataReceived: DataReceived<TDataType>, queryArguments?: object) {\n let url = this._url;\n if (queryArguments) {\n if (url.indexOf('?') < 0) {\n url = `${url}?`;\n } else {\n url = `${url}&`;\n }\n const query = Object.keys(queryArguments).map(key => `${key}=${queryArguments[key]}`).join('&');\n url = `${url}${query}`;\n }\n\n const connectSocket = () => {\n this._socket = new WebSocket(url);\n this._socket.onopen = () => {\n if (this._disconnected) return;\n console.log(`Connection for '${url}' established`);\n this._reconnectPolicy.reset();\n this._connectionStartTime = Date.now();\n this.startPinging();\n };\n this._socket.onclose = () => {\n if (this._disconnected || this._unauthorized) return;\n console.log(`Unexpected connection closed for route '${url}'`);\n this.stopPinging();\n this._reconnectPolicy.schedule(connectSocket, url);\n };\n this._socket.onerror = (error) => {\n if (this._disconnected || this._unauthorized) return;\n console.log(`Error with connection for '${url}' - ${error}`);\n this.stopPinging();\n this._reconnectPolicy.schedule(connectSocket, url);\n };\n this._socket.onmessage = (ev) => {\n if (this._disconnected) {\n console.log('Received message after closing connection');\n return;\n }\n this.handleMessage(ev.data, dataReceived);\n };\n };\n\n if (this._disconnected || this._unauthorized) return;\n connectSocket();\n }\n\n /** @inheritdoc */\n disconnect() {\n if (this._disconnected) {\n return;\n }\n console.log(`Disconnecting '${this._url}'`);\n this._disconnected = true;\n this._reconnectPolicy.cancel();\n this.stopPinging();\n this._socket?.close();\n console.log(`Connection for '${this._url}' closed`);\n this._socket = undefined!;\n }\n\n private startPinging() {\n this.stopPinging();\n this._pingInterval = setInterval(() => {\n this.sendPing();\n }, this._pingIntervalMs);\n }\n\n private stopPinging() {\n if (this._pingInterval) {\n clearInterval(this._pingInterval);\n this._pingInterval = undefined;\n }\n }\n\n private sendPing() {\n if (this._disconnected || !this._socket || this._socket.readyState !== WebSocket.OPEN) {\n return;\n }\n\n this._lastPingSentTime = Date.now();\n const pingMessage: WebSocketMessage = {\n type: WebSocketMessageType.Ping,\n timestamp: this._lastPingSentTime\n };\n\n this._socket.send(JSON.stringify(pingMessage));\n }\n\n private handleMessage(rawData: string, dataReceived: DataReceived<TDataType>) {\n try {\n const message = JSON.parse(rawData) as WebSocketMessage;\n\n // Handle messages based on type\n if (message.type === WebSocketMessageType.Pong) {\n this.handlePong(message);\n } else if (message.type === WebSocketMessageType.Data || !message.type) {\n // For new-style messages (type === 'Data') the query result is wrapped in message.data.\n // For legacy/backward-compatible messages (no type) the entire message IS the query result.\n const data = message.type === WebSocketMessageType.Data ? message.data : message;\n const result = data as QueryResult<TDataType>;\n\n if (result?.isAuthorized === false) {\n this.handleUnauthorized(result, dataReceived);\n return;\n }\n\n dataReceived(result);\n }\n } catch (error) {\n console.error('Error parsing WebSocket message:', error);\n }\n }\n\n /**\n * Handles the terminal result the server sends when the caller is no longer authorized for the query it is\n * subscribed to. The subscription is over for good, so the result is delivered and the automatic reconnect is\n * suppressed — reconnecting would re-subscribe, be denied again, and loop.\n * @param {QueryResult<TDataType>} result The unauthorized result to deliver.\n * @param {DataReceived<TDataType>} dataReceived Callback that receives the result.\n */\n private handleUnauthorized(result: QueryResult<TDataType>, dataReceived: DataReceived<TDataType>) {\n console.warn(`Connection for '${this._url}' was ended by the server - no longer authorized`);\n this._unauthorized = true;\n this.stopPinging();\n this._reconnectPolicy.cancel();\n dataReceived(result);\n this._socket?.close();\n }\n\n private handlePong(message: WebSocketMessage) {\n if (message.timestamp && this._lastPingSentTime) {\n const latency = Date.now() - message.timestamp;\n this._lastPongLatency = latency;\n this._latencySamples.push(latency);\n\n // Keep only the last 100 samples for average calculation\n if (this._latencySamples.length > 100) {\n this._latencySamples.shift();\n }\n }\n }\n}\n"],"names":["ObservableQueryConnection","_socket","_disconnected","_unauthorized","_url","_pingInterval","_pingIntervalMs","_lastPingSentTime","_lastPongLatency","_latencySamples","_connectionStartTime","url","_microservice","pingIntervalMs","_reconnectPolicy","ReconnectPolicy","secure","protocol","indexOf","host","pathname","search","length","microserviceParam","Globals","microserviceWSQueryArgument","dispose","disconnect","lastPingLatency","averageLatency","sum","reduce","acc","val","connect","dataReceived","queryArguments","query","Object","keys","map","key","join","connectSocket","WebSocket","onopen","console","log","reset","Date","now","startPinging","onclose","stopPinging","schedule","onerror","error","onmessage","ev","handleMessage","data","cancel","close","undefined","setInterval","sendPing","clearInterval","readyState","OPEN","pingMessage","type","WebSocketMessageType","Ping","timestamp","send","JSON","stringify","rawData","message","parse","Pong","handlePong","Data","result","isAuthorized","handleUnauthorized","warn","latency","push","shift"],"mappings":"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"}
|
|
@@ -5,10 +5,12 @@ export declare class ServerSentEventQueryConnection<TDataType> implements IObser
|
|
|
5
5
|
private readonly _url;
|
|
6
6
|
private _eventSource?;
|
|
7
7
|
private _disconnected;
|
|
8
|
+
private _unauthorized;
|
|
8
9
|
readonly lastPingLatency: number;
|
|
9
10
|
readonly averageLatency: number;
|
|
10
11
|
constructor(_url: URL);
|
|
11
12
|
connect(dataReceived: DataReceived<TDataType>, queryArguments?: object): void;
|
|
12
13
|
disconnect(): void;
|
|
14
|
+
private handleUnauthorized;
|
|
13
15
|
}
|
|
14
16
|
//# sourceMappingURL=ServerSentEventQueryConnection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ServerSentEventQueryConnection.d.ts","sourceRoot":"","sources":["../../../queries/ServerSentEventQueryConnection.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAM3D,eAAO,MAAM,aAAa,yBAAyB,CAAC;AAWpD,qBAAa,8BAA8B,CAAC,SAAS,CAAE,YAAW,0BAA0B,CAAC,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"ServerSentEventQueryConnection.d.ts","sourceRoot":"","sources":["../../../queries/ServerSentEventQueryConnection.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAM3D,eAAO,MAAM,aAAa,yBAAyB,CAAC;AAWpD,qBAAa,8BAA8B,CAAC,SAAS,CAAE,YAAW,0BAA0B,CAAC,SAAS,CAAC;IAevF,OAAO,CAAC,QAAQ,CAAC,IAAI;IAdjC,OAAO,CAAC,YAAY,CAAC,CAAc;IACnC,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,aAAa,CAAS;IAG9B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAK;IAGrC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAK;IAMpC,YAA6B,IAAI,EAAE,GAAG,EAAI;IAG1C,OAAO,CAAC,YAAY,EAAE,YAAY,CAAC,SAAS,CAAC,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAyC5E;IAGD,UAAU,IAAI,IAAI,CAKjB;IAaD,OAAO,CAAC,kBAAkB;CAO7B"}
|
|
@@ -17,6 +17,7 @@ import { Globals } from '../Globals.js';
|
|
|
17
17
|
_url;
|
|
18
18
|
_eventSource;
|
|
19
19
|
_disconnected = false;
|
|
20
|
+
_unauthorized = false;
|
|
20
21
|
/** @inheritdoc */ lastPingLatency = 0;
|
|
21
22
|
/** @inheritdoc */ averageLatency = 0;
|
|
22
23
|
/**
|
|
@@ -26,7 +27,7 @@ import { Globals } from '../Globals.js';
|
|
|
26
27
|
this._url = _url;
|
|
27
28
|
}
|
|
28
29
|
/** @inheritdoc */ connect(dataReceived, queryArguments) {
|
|
29
|
-
if (this._disconnected) return;
|
|
30
|
+
if (this._disconnected || this._unauthorized) return;
|
|
30
31
|
// Guard against environments where EventSource is not available (e.g. Node.js, SSR)
|
|
31
32
|
// and no custom factory has been supplied to substitute it.
|
|
32
33
|
if (!Globals.eventSourceFactory && typeof EventSource === 'undefined') {
|
|
@@ -45,13 +46,17 @@ import { Globals } from '../Globals.js';
|
|
|
45
46
|
if (this._disconnected) return;
|
|
46
47
|
try {
|
|
47
48
|
const result = JSON.parse(event.data);
|
|
49
|
+
if (result?.isAuthorized === false) {
|
|
50
|
+
this.handleUnauthorized(url, result, dataReceived);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
48
53
|
dataReceived(result);
|
|
49
54
|
} catch (error) {
|
|
50
55
|
console.error('SSE: error parsing message', error);
|
|
51
56
|
}
|
|
52
57
|
};
|
|
53
58
|
this._eventSource.onerror = ()=>{
|
|
54
|
-
if (this._disconnected) return;
|
|
59
|
+
if (this._disconnected || this._unauthorized) return;
|
|
55
60
|
console.warn(`SSE: connection error for '${url}', EventSource will retry automatically.`);
|
|
56
61
|
};
|
|
57
62
|
}
|
|
@@ -61,6 +66,23 @@ import { Globals } from '../Globals.js';
|
|
|
61
66
|
this._eventSource?.close();
|
|
62
67
|
this._eventSource = undefined;
|
|
63
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Handles the terminal result the server sends when the caller is no longer authorized for the query it is
|
|
71
|
+
* subscribed to.
|
|
72
|
+
*
|
|
73
|
+
* Unlike a WebSocket, an `EventSource` re-establishes a stream the server ended - the server closing its response
|
|
74
|
+
* is precisely the signal the browser retries on, roughly every three seconds. Each retry re-runs the whole query
|
|
75
|
+
* pipeline server side just to be denied again, so the stream is only actually terminal if this client closes it.
|
|
76
|
+
* @param {string} url The stream url, for diagnostics.
|
|
77
|
+
* @param {QueryResult<TDataType>} result The unauthorized result to deliver.
|
|
78
|
+
* @param {DataReceived<TDataType>} dataReceived Callback that receives the result.
|
|
79
|
+
*/ handleUnauthorized(url, result, dataReceived) {
|
|
80
|
+
console.warn(`SSE: stream for '${url}' was ended by the server - no longer authorized`);
|
|
81
|
+
this._unauthorized = true;
|
|
82
|
+
this._eventSource?.close();
|
|
83
|
+
this._eventSource = undefined;
|
|
84
|
+
dataReceived(result);
|
|
85
|
+
}
|
|
64
86
|
}
|
|
65
87
|
|
|
66
88
|
export { SSE_HUB_ROUTE, ServerSentEventQueryConnection };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ServerSentEventQueryConnection.js","sources":["../../../queries/ServerSentEventQueryConnection.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 { IObservableQueryConnection } from './IObservableQueryConnection';\nimport { DataReceived } from './ObservableQueryConnection';\nimport { QueryResult } from './QueryResult';\n\n/**\n * The SSE demultiplexer route used when connecting through the multiplexed observable query endpoint.\n */\nexport const SSE_HUB_ROUTE = '/.cratis/queries/sse';\n\n/**\n * Represents a direct Server-Sent Events (SSE) connection for a single observable query.\n *\n * In direct mode the URL points to the per-query endpoint (e.g. `/api/queries/latest`).\n * The backend detects the `Accept: text/event-stream` header and streams results directly.\n *\n * The caller (typically {@link createObservableQueryConnection}) decides which URL to use;\n * this class is transport-agnostic beyond being SSE.\n */\nexport class ServerSentEventQueryConnection<TDataType> implements IObservableQueryConnection<TDataType> {\n private _eventSource?: EventSource;\n private _disconnected = false;\n\n /** @inheritdoc */\n readonly lastPingLatency: number = 0;\n\n /** @inheritdoc */\n readonly averageLatency: number = 0;\n\n /**\n * Initializes a new instance of {@link ServerSentEventQueryConnection}.\n * @param {URL} url The fully qualified URL of the SSE endpoint (including query parameters).\n */\n constructor(private readonly _url: URL) {}\n\n /** @inheritdoc */\n connect(dataReceived: DataReceived<TDataType>, queryArguments?: object): void {\n if (this._disconnected) return;\n\n // Guard against environments where EventSource is not available (e.g. Node.js, SSR)\n // and no custom factory has been supplied to substitute it.\n if (!Globals.eventSourceFactory && typeof EventSource === 'undefined') {\n return;\n }\n\n let url = this._url.toString();\n if (queryArguments) {\n const separator = url.includes('?') ? '&' : '?';\n const query = Object.entries(queryArguments)\n .filter(([, value]) => value !== undefined && value !== null)\n .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)\n .join('&');\n if (query) {\n url = `${url}${separator}${query}`;\n }\n }\n\n this._eventSource = Globals.eventSourceFactory ? Globals.eventSourceFactory(url) : new EventSource(url);\n\n this._eventSource.onmessage = (event: MessageEvent) => {\n if (this._disconnected) return;\n try {\n const result = JSON.parse(event.data as string) as QueryResult<TDataType>;\n dataReceived(result);\n } catch (error) {\n console.error('SSE: error parsing message', error);\n }\n };\n\n this._eventSource.onerror = () => {\n if (this._disconnected) return;\n console.warn(`SSE: connection error for '${url}', EventSource will retry automatically.`);\n };\n }\n\n /** @inheritdoc */\n disconnect(): void {\n if (this._disconnected) return;\n this._disconnected = true;\n this._eventSource?.close();\n this._eventSource = undefined;\n }\n}\n"],"names":["SSE_HUB_ROUTE","ServerSentEventQueryConnection","_eventSource","_disconnected","lastPingLatency","averageLatency","_url","connect","dataReceived","queryArguments","Globals","eventSourceFactory","EventSource","url","toString","separator","includes","query","Object","entries","filter","value","undefined","map","key","encodeURIComponent","String","join","onmessage","event","result","JSON","parse","data","error","console","onerror","warn","disconnect","close"],"mappings":";;AAAA;AACA;AAOA;;IAGO,MAAMA,aAAAA,GAAgB;AAE7B;;;;;;;;AAQC,IACM,MAAMC,8BAAAA,CAAAA;;IACDC,YAAAA;AACAC,IAAAA,aAAAA,GAAgB,KAAA;uBAGxB,eAASC,GAA0B,CAAA;uBAGnC,cAASC,GAAyB,CAAA;AAElC;;;QAIA,WAAA,CAAY,IAA0B,CAAE;aAAXC,IAAAA,GAAAA,IAAAA;AAAY,IAAA;AAEzC,uBACAC,OAAAA,CAAQC,YAAqC,EAAEC,cAAuB,EAAQ;
|
|
1
|
+
{"version":3,"file":"ServerSentEventQueryConnection.js","sources":["../../../queries/ServerSentEventQueryConnection.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 { IObservableQueryConnection } from './IObservableQueryConnection';\nimport { DataReceived } from './ObservableQueryConnection';\nimport { QueryResult } from './QueryResult';\n\n/**\n * The SSE demultiplexer route used when connecting through the multiplexed observable query endpoint.\n */\nexport const SSE_HUB_ROUTE = '/.cratis/queries/sse';\n\n/**\n * Represents a direct Server-Sent Events (SSE) connection for a single observable query.\n *\n * In direct mode the URL points to the per-query endpoint (e.g. `/api/queries/latest`).\n * The backend detects the `Accept: text/event-stream` header and streams results directly.\n *\n * The caller (typically {@link createObservableQueryConnection}) decides which URL to use;\n * this class is transport-agnostic beyond being SSE.\n */\nexport class ServerSentEventQueryConnection<TDataType> implements IObservableQueryConnection<TDataType> {\n private _eventSource?: EventSource;\n private _disconnected = false;\n private _unauthorized = false;\n\n /** @inheritdoc */\n readonly lastPingLatency: number = 0;\n\n /** @inheritdoc */\n readonly averageLatency: number = 0;\n\n /**\n * Initializes a new instance of {@link ServerSentEventQueryConnection}.\n * @param {URL} url The fully qualified URL of the SSE endpoint (including query parameters).\n */\n constructor(private readonly _url: URL) {}\n\n /** @inheritdoc */\n connect(dataReceived: DataReceived<TDataType>, queryArguments?: object): void {\n if (this._disconnected || this._unauthorized) return;\n\n // Guard against environments where EventSource is not available (e.g. Node.js, SSR)\n // and no custom factory has been supplied to substitute it.\n if (!Globals.eventSourceFactory && typeof EventSource === 'undefined') {\n return;\n }\n\n let url = this._url.toString();\n if (queryArguments) {\n const separator = url.includes('?') ? '&' : '?';\n const query = Object.entries(queryArguments)\n .filter(([, value]) => value !== undefined && value !== null)\n .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)\n .join('&');\n if (query) {\n url = `${url}${separator}${query}`;\n }\n }\n\n this._eventSource = Globals.eventSourceFactory ? Globals.eventSourceFactory(url) : new EventSource(url);\n\n this._eventSource.onmessage = (event: MessageEvent) => {\n if (this._disconnected) return;\n try {\n const result = JSON.parse(event.data as string) as QueryResult<TDataType>;\n if ((result as unknown as { isAuthorized?: boolean })?.isAuthorized === false) {\n this.handleUnauthorized(url, result, dataReceived);\n return;\n }\n dataReceived(result);\n } catch (error) {\n console.error('SSE: error parsing message', error);\n }\n };\n\n this._eventSource.onerror = () => {\n if (this._disconnected || this._unauthorized) return;\n console.warn(`SSE: connection error for '${url}', EventSource will retry automatically.`);\n };\n }\n\n /** @inheritdoc */\n disconnect(): void {\n if (this._disconnected) return;\n this._disconnected = true;\n this._eventSource?.close();\n this._eventSource = undefined;\n }\n\n /**\n * Handles the terminal result the server sends when the caller is no longer authorized for the query it is\n * subscribed to.\n *\n * Unlike a WebSocket, an `EventSource` re-establishes a stream the server ended - the server closing its response\n * is precisely the signal the browser retries on, roughly every three seconds. Each retry re-runs the whole query\n * pipeline server side just to be denied again, so the stream is only actually terminal if this client closes it.\n * @param {string} url The stream url, for diagnostics.\n * @param {QueryResult<TDataType>} result The unauthorized result to deliver.\n * @param {DataReceived<TDataType>} dataReceived Callback that receives the result.\n */\n private handleUnauthorized(url: string, result: QueryResult<TDataType>, dataReceived: DataReceived<TDataType>): void {\n console.warn(`SSE: stream for '${url}' was ended by the server - no longer authorized`);\n this._unauthorized = true;\n this._eventSource?.close();\n this._eventSource = undefined;\n dataReceived(result);\n }\n}\n"],"names":["SSE_HUB_ROUTE","ServerSentEventQueryConnection","_eventSource","_disconnected","_unauthorized","lastPingLatency","averageLatency","_url","connect","dataReceived","queryArguments","Globals","eventSourceFactory","EventSource","url","toString","separator","includes","query","Object","entries","filter","value","undefined","map","key","encodeURIComponent","String","join","onmessage","event","result","JSON","parse","data","isAuthorized","handleUnauthorized","error","console","onerror","warn","disconnect","close"],"mappings":";;AAAA;AACA;AAOA;;IAGO,MAAMA,aAAAA,GAAgB;AAE7B;;;;;;;;AAQC,IACM,MAAMC,8BAAAA,CAAAA;;IACDC,YAAAA;AACAC,IAAAA,aAAAA,GAAgB,KAAA;AAChBC,IAAAA,aAAAA,GAAgB,KAAA;uBAGxB,eAASC,GAA0B,CAAA;uBAGnC,cAASC,GAAyB,CAAA;AAElC;;;QAIA,WAAA,CAAY,IAA0B,CAAE;aAAXC,IAAAA,GAAAA,IAAAA;AAAY,IAAA;AAEzC,uBACAC,OAAAA,CAAQC,YAAqC,EAAEC,cAAuB,EAAQ;AAC1E,QAAA,IAAI,IAAI,CAACP,aAAa,IAAI,IAAI,CAACC,aAAa,EAAE;;;AAI9C,QAAA,IAAI,CAACO,OAAAA,CAAQC,kBAAkB,IAAI,OAAOC,gBAAgB,WAAA,EAAa;AACnE,YAAA;AACJ,QAAA;AAEA,QAAA,IAAIC,GAAAA,GAAM,IAAI,CAACP,IAAI,CAACQ,QAAQ,EAAA;AAC5B,QAAA,IAAIL,cAAAA,EAAgB;AAChB,YAAA,MAAMM,SAAAA,GAAYF,GAAAA,CAAIG,QAAQ,CAAC,OAAO,GAAA,GAAM,GAAA;AAC5C,YAAA,MAAMC,KAAAA,GAAQC,MAAAA,CAAOC,OAAO,CAACV,gBACxBW,MAAM,CAAC,CAAC,GAAGC,KAAAA,CAAM,GAAKA,KAAAA,KAAUC,SAAAA,IAAaD,UAAU,IAAA,CAAA,CACvDE,GAAG,CAAC,CAAC,CAACC,GAAAA,EAAKH,KAAAA,CAAM,GAAK,GAAGI,kBAAAA,CAAmBD,GAAAA,CAAAA,CAAK,CAAC,EAAEC,kBAAAA,CAAmBC,MAAAA,CAAOL,KAAAA,CAAAA,CAAAA,CAAAA,CAAS,CAAA,CACvFM,IAAI,CAAC,GAAA,CAAA;AACV,YAAA,IAAIV,KAAAA,EAAO;gBACPJ,GAAAA,GAAM,CAAA,EAAGA,GAAAA,CAAAA,EAAME,SAAAA,CAAAA,EAAYE,KAAAA,CAAAA,CAAO;AACtC,YAAA;AACJ,QAAA;QAEA,IAAI,CAAChB,YAAY,GAAGS,OAAAA,CAAQC,kBAAkB,GAAGD,OAAAA,CAAQC,kBAAkB,CAACE,GAAAA,CAAAA,GAAO,IAAID,WAAAA,CAAYC,GAAAA,CAAAA;AAEnG,QAAA,IAAI,CAACZ,YAAY,CAAC2B,SAAS,GAAG,CAACC,KAAAA,GAAAA;YAC3B,IAAI,IAAI,CAAC3B,aAAa,EAAE;YACxB,IAAI;AACA,gBAAA,MAAM4B,MAAAA,GAASC,IAAAA,CAAKC,KAAK,CAACH,MAAMI,IAAI,CAAA;gBACpC,IAAKH,MAAAA,EAAkDI,YAAAA,KAAiB,KAAA,EAAO;AAC3E,oBAAA,IAAI,CAACC,kBAAkB,CAACtB,GAAAA,EAAKiB,MAAAA,EAAQtB,YAAAA,CAAAA;AACrC,oBAAA;AACJ,gBAAA;gBACAA,YAAAA,CAAasB,MAAAA,CAAAA;AACjB,YAAA,CAAA,CAAE,OAAOM,KAAAA,EAAO;gBACZC,OAAAA,CAAQD,KAAK,CAAC,4BAAA,EAA8BA,KAAAA,CAAAA;AAChD,YAAA;AACJ,QAAA,CAAA;AAEA,QAAA,IAAI,CAACnC,YAAY,CAACqC,OAAO,GAAG,IAAA;AACxB,YAAA,IAAI,IAAI,CAACpC,aAAa,IAAI,IAAI,CAACC,aAAa,EAAE;AAC9CkC,YAAAA,OAAAA,CAAQE,IAAI,CAAC,CAAC,2BAA2B,EAAE1B,GAAAA,CAAI,wCAAwC,CAAC,CAAA;AAC5F,QAAA,CAAA;AACJ,IAAA;AAEA,uBACA2B,UAAAA,GAAmB;QACf,IAAI,IAAI,CAACtC,aAAa,EAAE;QACxB,IAAI,CAACA,aAAa,GAAG,IAAA;QACrB,IAAI,CAACD,YAAY,EAAEwC,KAAAA,EAAAA;QACnB,IAAI,CAACxC,YAAY,GAAGqB,SAAAA;AACxB,IAAA;AAEA;;;;;;;;;;AAUC,QACD,kBAAQa,CAAmBtB,GAAW,EAAEiB,MAA8B,EAAEtB,YAAqC,EAAQ;AACjH6B,QAAAA,OAAAA,CAAQE,IAAI,CAAC,CAAC,iBAAiB,EAAE1B,GAAAA,CAAI,gDAAgD,CAAC,CAAA;QACtF,IAAI,CAACV,aAAa,GAAG,IAAA;QACrB,IAAI,CAACF,YAAY,EAAEwC,KAAAA,EAAAA;QACnB,IAAI,CAACxC,YAAY,GAAGqB,SAAAA;QACpBd,YAAAA,CAAasB,MAAAA,CAAAA;AACjB,IAAA;AACJ;;;;"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import sinon from 'sinon';
|
|
2
|
+
import { ObservableQueryConnection } from '../../ObservableQueryConnection';
|
|
3
|
+
import { IReconnectPolicy } from '../../IReconnectPolicy';
|
|
4
|
+
export type FakeWebSocketWithPolicy = {
|
|
5
|
+
onopen: (() => void) | null;
|
|
6
|
+
onclose: (() => void) | null;
|
|
7
|
+
onerror: ((error: unknown) => void) | null;
|
|
8
|
+
onmessage: ((event: {
|
|
9
|
+
data: string;
|
|
10
|
+
}) => void) | null;
|
|
11
|
+
close: sinon.SinonStub;
|
|
12
|
+
send: sinon.SinonStub;
|
|
13
|
+
readyState: number;
|
|
14
|
+
};
|
|
15
|
+
export declare class an_observable_query_connection_with_a_reconnect_policy {
|
|
16
|
+
connection: ObservableQueryConnection<unknown>;
|
|
17
|
+
fakeWebSocket: FakeWebSocketWithPolicy;
|
|
18
|
+
reconnectPolicy: IReconnectPolicy;
|
|
19
|
+
schedule: sinon.SinonStub;
|
|
20
|
+
cancel: sinon.SinonStub;
|
|
21
|
+
constructor();
|
|
22
|
+
simulateMessage(payload: object): void;
|
|
23
|
+
simulateClose(): void;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=an_observable_query_connection_with_a_reconnect_policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"an_observable_query_connection_with_a_reconnect_policy.d.ts","sourceRoot":"","sources":["../../../../../queries/for_ObservableQueryConnection/given/an_observable_query_connection_with_a_reconnect_policy.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,yBAAyB,EAAE,MAAM,iCAAiC,CAAC;AAC5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAI1D,MAAM,MAAM,uBAAuB,GAAG;IAClC,MAAM,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC;IAC5B,OAAO,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC;IAC7B,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IAC3C,SAAS,EAAE,CAAC,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IACtD,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,qBAAa,sDAAsD;IAC/D,UAAU,EAAE,yBAAyB,CAAC,OAAO,CAAC,CAAC;IAC/C,aAAa,EAAG,uBAAuB,CAAC;IACxC,eAAe,EAAE,gBAAgB,CAAC;IAClC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,MAAM,EAAE,KAAK,CAAC,SAAS,CAAC;IAExB,cAoCC;IAED,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAErC;IAED,aAAa,IAAI,IAAI,CAEpB;CACJ"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import sinon from 'sinon';
|
|
2
|
+
import { ObservableQueryConnection } from '../../ObservableQueryConnection';
|
|
3
|
+
export class an_observable_query_connection_with_a_reconnect_policy {
|
|
4
|
+
connection;
|
|
5
|
+
fakeWebSocket;
|
|
6
|
+
reconnectPolicy;
|
|
7
|
+
schedule;
|
|
8
|
+
cancel;
|
|
9
|
+
constructor() {
|
|
10
|
+
this.fakeWebSocket = {
|
|
11
|
+
onopen: null,
|
|
12
|
+
onclose: null,
|
|
13
|
+
onerror: null,
|
|
14
|
+
onmessage: null,
|
|
15
|
+
close: sinon.stub(),
|
|
16
|
+
send: sinon.stub(),
|
|
17
|
+
readyState: WebSocket.OPEN,
|
|
18
|
+
};
|
|
19
|
+
const fakeWebSocket = this.fakeWebSocket;
|
|
20
|
+
const FakeWebSocketClass = function () {
|
|
21
|
+
fakeWebSocket.onopen = null;
|
|
22
|
+
fakeWebSocket.onclose = null;
|
|
23
|
+
fakeWebSocket.onerror = null;
|
|
24
|
+
fakeWebSocket.onmessage = null;
|
|
25
|
+
return fakeWebSocket;
|
|
26
|
+
};
|
|
27
|
+
globalThis['WebSocket'] = FakeWebSocketClass;
|
|
28
|
+
this.schedule = sinon.stub().returns(true);
|
|
29
|
+
this.cancel = sinon.stub();
|
|
30
|
+
this.reconnectPolicy = {
|
|
31
|
+
attempt: 0,
|
|
32
|
+
schedule: this.schedule,
|
|
33
|
+
reset: sinon.stub(),
|
|
34
|
+
cancel: this.cancel,
|
|
35
|
+
};
|
|
36
|
+
this.connection = new ObservableQueryConnection(new URL('https://example.com/api/test'), 'test-microservice', 10000, this.reconnectPolicy);
|
|
37
|
+
}
|
|
38
|
+
simulateMessage(payload) {
|
|
39
|
+
this.fakeWebSocket.onmessage?.({ data: JSON.stringify(payload) });
|
|
40
|
+
}
|
|
41
|
+
simulateClose() {
|
|
42
|
+
this.fakeWebSocket.onclose?.();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=an_observable_query_connection_with_a_reconnect_policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"an_observable_query_connection_with_a_reconnect_policy.js","sourceRoot":"","sources":["../../../../../queries/for_ObservableQueryConnection/given/an_observable_query_connection_with_a_reconnect_policy.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,yBAAyB,EAAE,MAAM,iCAAiC,CAAC;AAe5E,MAAM,OAAO,sDAAsD;IAC/D,UAAU,CAAqC;IAC/C,aAAa,CAA2B;IACxC,eAAe,CAAmB;IAClC,QAAQ,CAAkB;IAC1B,MAAM,CAAkB;IAExB;QACI,IAAI,CAAC,aAAa,GAAG;YACjB,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,IAAI;YACb,SAAS,EAAE,IAAI;YACf,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE;YACnB,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE;YAClB,UAAU,EAAE,SAAS,CAAC,IAAI;SAC7B,CAAC;QAEF,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,CAAC;QACzC,MAAM,kBAAkB,GAAG;YACvB,aAAa,CAAC,MAAM,GAAG,IAAI,CAAC;YAC5B,aAAa,CAAC,OAAO,GAAG,IAAI,CAAC;YAC7B,aAAa,CAAC,OAAO,GAAG,IAAI,CAAC;YAC7B,aAAa,CAAC,SAAS,GAAG,IAAI,CAAC;YAC/B,OAAO,aAAa,CAAC;QACzB,CAAC,CAAC;QACD,UAAkB,CAAC,WAAW,CAAC,GAAG,kBAAkB,CAAC;QAEtD,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC3C,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC3B,IAAI,CAAC,eAAe,GAAG;YACnB,OAAO,EAAE,CAAC;YACV,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE;YACnB,MAAM,EAAE,IAAI,CAAC,MAAM;SACS,CAAC;QAEjC,IAAI,CAAC,UAAU,GAAG,IAAI,yBAAyB,CAC3C,IAAI,GAAG,CAAC,8BAA8B,CAAC,EACvC,mBAAmB,EACnB,KAAK,EACL,IAAI,CAAC,eAAe,CACvB,CAAC;IACN,CAAC;IAED,eAAe,CAAC,OAAe;QAC3B,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,aAAa;QACT,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,EAAE,CAAC;IACnC,CAAC;CACJ"}
|