@genesislcap/foundation-comms 14.372.0 → 14.373.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/dts/connect/http.connect.d.ts +26 -0
- package/dist/dts/connect/http.connect.d.ts.map +1 -1
- package/dist/dts/connect/http.d.ts +8 -0
- package/dist/dts/connect/http.d.ts.map +1 -1
- package/dist/dts/connect/index.d.ts +5 -4
- package/dist/dts/connect/index.d.ts.map +1 -1
- package/dist/dts/connect/sessionRefresh.d.ts +35 -0
- package/dist/dts/connect/sessionRefresh.d.ts.map +1 -0
- package/dist/dts/connect/socket.d.ts +0 -2
- package/dist/dts/connect/socket.d.ts.map +1 -1
- package/dist/esm/connect/connect.js +2 -2
- package/dist/esm/connect/http.connect.js +107 -5
- package/dist/esm/connect/http.js +12 -0
- package/dist/esm/connect/index.js +5 -4
- package/dist/esm/connect/sessionRefresh.js +135 -0
- package/dist/esm/connect/socket.js +10 -45
- package/dist/foundation-comms.api.json +291 -0
- package/dist/foundation-comms.d.ts +82 -12
- package/docs/api/foundation-comms.httperror.md +74 -0
- package/docs/api/foundation-comms.httperror.message.md +11 -0
- package/docs/api/foundation-comms.httperror.status.md +11 -0
- package/docs/api/foundation-comms.md +44 -0
- package/docs/api/foundation-comms.onothertabreauthcomplete.md +56 -0
- package/docs/api/foundation-comms.sessionrefreshresult.md +98 -0
- package/docs/api/foundation-comms.tryrefreshsession.md +58 -0
- package/docs/api-report.md.api.md +34 -10
- package/package.json +14 -13
|
@@ -27,13 +27,39 @@ export declare class DefaultHttpConnect implements Connect {
|
|
|
27
27
|
get isConnected(): boolean;
|
|
28
28
|
set isConnected(value: boolean);
|
|
29
29
|
private ongoingTurbo?;
|
|
30
|
+
/**
|
|
31
|
+
* Amount of time (in milliseconds) to wait when another tab is handling session reauthentication.
|
|
32
|
+
*/
|
|
33
|
+
private static readonly REAUTH_WAIT_MS;
|
|
34
|
+
/**
|
|
35
|
+
* Amount of time (in milliseconds) to wait before retrying a failed request.
|
|
36
|
+
*/
|
|
37
|
+
private static readonly RETRY_DELAY_MS;
|
|
38
|
+
/**
|
|
39
|
+
* Maximum number of retry attempts for failed requests.
|
|
40
|
+
*/
|
|
41
|
+
private static readonly MAX_RETRIES;
|
|
30
42
|
/**
|
|
31
43
|
* A map of active messages in "polling" mode.
|
|
32
44
|
* @remarks Equivalent to messaged being streamed in a WebSocket connection.
|
|
33
45
|
* @internal
|
|
34
46
|
*/
|
|
35
47
|
private streams;
|
|
48
|
+
/**
|
|
49
|
+
* Promise that tracks an ongoing session refresh to prevent concurrent refreshes from the same instance.
|
|
50
|
+
* @internal
|
|
51
|
+
*/
|
|
52
|
+
private ongoingRefresh;
|
|
36
53
|
constructor(http: Http, messageBuilder: MessageBuilder, session: Session, metaCache: MetaCache, serializer: JSONSerializer, config: HttpConnectConfig);
|
|
54
|
+
/**
|
|
55
|
+
* Attempts to refresh the session when a 401 Unauthorized error is received.
|
|
56
|
+
* Uses a lock mechanism to prevent multiple tabs from trying to reauthenticate simultaneously.
|
|
57
|
+
* Also prevents concurrent refresh attempts from the same instance by reusing an ongoing refresh.
|
|
58
|
+
* If another tab is handling the refresh, waits and retries once to verify the result.
|
|
59
|
+
* @internal
|
|
60
|
+
* @returns A Promise that resolves to `true` if session was refreshed successfully, `false` otherwise.
|
|
61
|
+
*/
|
|
62
|
+
private handleSessionRefresh;
|
|
37
63
|
/**
|
|
38
64
|
* Checks if the host is available by sending a heartbeat ping message and waiting for the response.
|
|
39
65
|
* @internal
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.connect.d.ts","sourceRoot":"","sources":["../../../src/connect/http.connect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"http.connect.d.ts","sourceRoot":"","sources":["../../../src/connect/http.connect.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,6 +19,14 @@ export interface Http {
|
|
|
19
19
|
get<T = any>(url: string, requestInit: HttpRequestInit): Promise<T>;
|
|
20
20
|
post<T = any>(url: string, requestInit: HttpRequestInit): Promise<T>;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* An error thrown when an HTTP request fails with a non-2xx status code.
|
|
24
|
+
* @public
|
|
25
|
+
*/
|
|
26
|
+
export interface HttpError extends Error {
|
|
27
|
+
status: number;
|
|
28
|
+
message: string;
|
|
29
|
+
}
|
|
22
30
|
/**
|
|
23
31
|
* The default implementation of the Http interface.
|
|
24
32
|
* @public
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../src/connect/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAG/D;;;GAGG;AACH,MAAM,WAAW,eAAgB,SAAQ,WAAW;IAClD;;;;OAIG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;CAC7B;AAED;;;GAGG;AACH,MAAM,WAAW,IAAI;IACnB,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,eAAe,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACpE,IAAI,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,eAAe,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACtE;AAED;;;GAGG;AACH,qBAAa,WAAY,YAAW,IAAI;IACV,OAAO,CAAC,UAAU;gBAAV,UAAU,EAAE,cAAc;IAEjD,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,eAAe,GAAG,OAAO,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../src/connect/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAG/D;;;GAGG;AACH,MAAM,WAAW,eAAgB,SAAQ,WAAW;IAClD;;;;OAIG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;CAC7B;AAED;;;GAGG;AACH,MAAM,WAAW,IAAI;IACnB,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,eAAe,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACpE,IAAI,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,eAAe,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACtE;AAED;;;GAGG;AACH,MAAM,WAAW,SAAU,SAAQ,KAAK;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AASD;;;GAGG;AACH,qBAAa,WAAY,YAAW,IAAI;IACV,OAAO,CAAC,UAAU;gBAAV,UAAU,EAAE,cAAc;IAEjD,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,eAAe,GAAG,OAAO,CAAC,CAAC,CAAC;IAc7D,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,eAAe,GAAG,OAAO,CAAC,CAAC,CAAC;CAa5E;AAED;;;GAGG;AACH,eAAO,MAAM,IAAI,4DAA4D,CAAC"}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
export * from './connect.events';
|
|
2
1
|
export * from './connect';
|
|
2
|
+
export * from './connect.events';
|
|
3
3
|
export * from './connect.types';
|
|
4
|
-
export * from './http.connect';
|
|
5
4
|
export * from './http';
|
|
5
|
+
export * from './http.connect';
|
|
6
6
|
export * from './message';
|
|
7
|
+
export * from './reconnectStrategy';
|
|
7
8
|
export * from './serializers';
|
|
8
|
-
export * from './
|
|
9
|
+
export * from './sessionRefresh';
|
|
9
10
|
export * from './socket';
|
|
11
|
+
export * from './socket.status';
|
|
10
12
|
export * from './socket.types';
|
|
11
|
-
export * from './reconnectStrategy';
|
|
12
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/connect/index.ts"],"names":[],"mappings":"AAAA,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/connect/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,QAAQ,CAAC;AACvB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result of a session refresh attempt.
|
|
3
|
+
* @public
|
|
4
|
+
*/
|
|
5
|
+
export declare enum SessionRefreshResult {
|
|
6
|
+
/** Session was successfully refreshed */
|
|
7
|
+
SUCCESS = "SUCCESS",
|
|
8
|
+
/** Refresh failed - page will reload */
|
|
9
|
+
FAILED = "FAILED",
|
|
10
|
+
/** Another tab is handling the refresh */
|
|
11
|
+
HANDLED_BY_OTHER_TAB = "HANDLED_BY_OTHER_TAB",
|
|
12
|
+
/** Session was already valid */
|
|
13
|
+
ALREADY_VALID = "ALREADY_VALID"
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Attempts to refresh the session when authentication has expired.
|
|
17
|
+
* Uses a localStorage-based lock mechanism to prevent multiple tabs from
|
|
18
|
+
* trying to reauthenticate simultaneously.
|
|
19
|
+
*
|
|
20
|
+
* @public
|
|
21
|
+
* @param options - Optional configuration
|
|
22
|
+
* @param options.reloadOnFailure - Whether to reload the page if refresh fails (default: true)
|
|
23
|
+
* @returns The result of the refresh attempt
|
|
24
|
+
*/
|
|
25
|
+
export declare function tryRefreshSession(options?: {
|
|
26
|
+
reloadOnFailure?: boolean;
|
|
27
|
+
}): Promise<SessionRefreshResult>;
|
|
28
|
+
/**
|
|
29
|
+
* Subscribes to storage events to detect when another tab has finished reauthenticating.
|
|
30
|
+
* @public
|
|
31
|
+
* @param callback - Called when the other tab finishes reauthentication
|
|
32
|
+
* @returns A function to unsubscribe from the storage events
|
|
33
|
+
*/
|
|
34
|
+
export declare function onOtherTabReauthComplete(callback: () => void): () => void;
|
|
35
|
+
//# sourceMappingURL=sessionRefresh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionRefresh.d.ts","sourceRoot":"","sources":["../../../src/connect/sessionRefresh.ts"],"names":[],"mappings":"AAQA;;;GAGG;AACH,oBAAY,oBAAoB;IAC9B,yCAAyC;IACzC,OAAO,YAAY;IACnB,wCAAwC;IACxC,MAAM,WAAW;IACjB,0CAA0C;IAC1C,oBAAoB,yBAAyB;IAC7C,gCAAgC;IAChC,aAAa,kBAAkB;CAChC;AA6DD;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,GAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAA;CAAO,GAC1C,OAAO,CAAC,oBAAoB,CAAC,CA+B/B;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CASzE"}
|
|
@@ -153,9 +153,7 @@ export declare class DefaultSocket implements Socket {
|
|
|
153
153
|
* more info here https://docs.genesis.global/docs/develop/server-capabilities/access-control/authentication/#sessiontimeoutmins
|
|
154
154
|
*/
|
|
155
155
|
private handleExpiredSession;
|
|
156
|
-
private static REAUTH_ACTION_LOCK_KEY;
|
|
157
156
|
private handleCookieBasedReauth;
|
|
158
|
-
private onStorageEvent;
|
|
159
157
|
private updateSessionAndSubscriptions;
|
|
160
158
|
private prepareHeartbeat;
|
|
161
159
|
private reconnect;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../../../src/connect/socket.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../../../src/connect/socket.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { __awaiter, __decorate, __param } from "tslib";
|
|
2
|
-
import { FORCE_HTTP, GENESIS_SOCKET_URL } from '@genesislcap/foundation-utils';
|
|
2
|
+
import { FORCE_HTTP, GENESIS_SOCKET_URL, isFeatureActivated } from '@genesislcap/foundation-utils';
|
|
3
3
|
import { observable } from '@microsoft/fast-element';
|
|
4
4
|
import { DI } from '@microsoft/fast-foundation';
|
|
5
5
|
import { scan } from 'rxjs';
|
|
@@ -214,7 +214,7 @@ DefaultConnect = __decorate([
|
|
|
214
214
|
__param(4, ConnectConfig)
|
|
215
215
|
], DefaultConnect);
|
|
216
216
|
export { DefaultConnect };
|
|
217
|
-
const useHttpConnect = FORCE_HTTP === 'true' ||
|
|
217
|
+
const useHttpConnect = FORCE_HTTP === 'true' || isFeatureActivated('forceHttp');
|
|
218
218
|
/**
|
|
219
219
|
* The DI token for the Connect interface (WS-only).
|
|
220
220
|
* @internal
|
|
@@ -3,13 +3,15 @@ import { __awaiter, __decorate, __param } from "tslib";
|
|
|
3
3
|
import { JSONSerializer } from '@genesislcap/foundation-utils';
|
|
4
4
|
import { observable, volatile } from '@microsoft/fast-element';
|
|
5
5
|
import { DI } from '@microsoft/fast-foundation';
|
|
6
|
-
import {
|
|
6
|
+
import { StatusCodes } from 'http-status-codes';
|
|
7
|
+
import { BehaviorSubject, concat, filter, finalize, from, interval, retry, scan, switchMap, takeWhile, timer, } from 'rxjs';
|
|
7
8
|
import { MetaCache } from '../metadata';
|
|
8
9
|
import { Session } from '../session';
|
|
9
10
|
import { logger } from '../utils';
|
|
10
11
|
import { Http } from './http';
|
|
11
12
|
import { HttpConnectConfig } from './http.connect.types';
|
|
12
13
|
import { EventMessageType, MessageBuilder, MessageType, } from './message';
|
|
14
|
+
import { SessionRefreshResult, tryRefreshSession } from './sessionRefresh';
|
|
13
15
|
import { updateState } from './updateState';
|
|
14
16
|
/**
|
|
15
17
|
* The default implementation for the Connect interface (HTTP-only).
|
|
@@ -45,6 +47,49 @@ let DefaultHttpConnect = DefaultHttpConnect_1 = class DefaultHttpConnect {
|
|
|
45
47
|
* @internal
|
|
46
48
|
*/
|
|
47
49
|
this.streams = new Map();
|
|
50
|
+
/**
|
|
51
|
+
* Promise that tracks an ongoing session refresh to prevent concurrent refreshes from the same instance.
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
54
|
+
this.ongoingRefresh = null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Attempts to refresh the session when a 401 Unauthorized error is received.
|
|
58
|
+
* Uses a lock mechanism to prevent multiple tabs from trying to reauthenticate simultaneously.
|
|
59
|
+
* Also prevents concurrent refresh attempts from the same instance by reusing an ongoing refresh.
|
|
60
|
+
* If another tab is handling the refresh, waits and retries once to verify the result.
|
|
61
|
+
* @internal
|
|
62
|
+
* @returns A Promise that resolves to `true` if session was refreshed successfully, `false` otherwise.
|
|
63
|
+
*/
|
|
64
|
+
handleSessionRefresh() {
|
|
65
|
+
// If a refresh is already in progress, reuse it
|
|
66
|
+
if (this.ongoingRefresh) {
|
|
67
|
+
return this.ongoingRefresh;
|
|
68
|
+
}
|
|
69
|
+
// Start a new refresh and clear the lock when done
|
|
70
|
+
this.ongoingRefresh = (() => __awaiter(this, void 0, void 0, function* () {
|
|
71
|
+
const host = this.host;
|
|
72
|
+
this.disconnect();
|
|
73
|
+
let result = yield tryRefreshSession();
|
|
74
|
+
// If another tab is handling the refresh, wait and check once more
|
|
75
|
+
if (result === SessionRefreshResult.HANDLED_BY_OTHER_TAB) {
|
|
76
|
+
yield new Promise((resolve) => {
|
|
77
|
+
setTimeout(resolve, DefaultHttpConnect_1.REAUTH_WAIT_MS);
|
|
78
|
+
});
|
|
79
|
+
// Check if the other tab succeeded
|
|
80
|
+
result = yield tryRefreshSession();
|
|
81
|
+
}
|
|
82
|
+
// Reconnect if session is valid (either we refreshed it or another tab did)
|
|
83
|
+
if (result === SessionRefreshResult.SUCCESS ||
|
|
84
|
+
result === SessionRefreshResult.ALREADY_VALID) {
|
|
85
|
+
this.connect(host);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}))().finally(() => {
|
|
90
|
+
this.ongoingRefresh = null;
|
|
91
|
+
});
|
|
92
|
+
return this.ongoingRefresh;
|
|
48
93
|
}
|
|
49
94
|
/**
|
|
50
95
|
* Checks if the host is available by sending a heartbeat ping message and waiting for the response.
|
|
@@ -68,9 +113,18 @@ let DefaultHttpConnect = DefaultHttpConnect_1 = class DefaultHttpConnect {
|
|
|
68
113
|
});
|
|
69
114
|
}
|
|
70
115
|
send(message, needsHandling) {
|
|
71
|
-
return
|
|
72
|
-
|
|
73
|
-
|
|
116
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
117
|
+
// Wait for any ongoing session refresh to complete before sending
|
|
118
|
+
if (this.ongoingRefresh) {
|
|
119
|
+
const refreshSucceeded = yield this.ongoingRefresh;
|
|
120
|
+
if (!refreshSucceeded) {
|
|
121
|
+
throw new Error('Cannot send message: session refresh failed');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return this.http.post(DefaultHttpConnect_1.getUrl(this.host, message), {
|
|
125
|
+
body: DefaultHttpConnect_1.getBody(message),
|
|
126
|
+
headers: this.messageBuilder.createHTTPHeadersFromMessage(message),
|
|
127
|
+
});
|
|
74
128
|
});
|
|
75
129
|
}
|
|
76
130
|
httpMode() {
|
|
@@ -85,6 +139,11 @@ let DefaultHttpConnect = DefaultHttpConnect_1 = class DefaultHttpConnect {
|
|
|
85
139
|
connect(host) {
|
|
86
140
|
return __awaiter(this, void 0, void 0, function* () {
|
|
87
141
|
this.host = host;
|
|
142
|
+
// below will convert the host to http one if someone is
|
|
143
|
+
// using the feature flag to force http mode but set api host as ws url
|
|
144
|
+
if (this.host.startsWith('ws')) {
|
|
145
|
+
this.host = this.host.replace('ws', 'http');
|
|
146
|
+
}
|
|
88
147
|
if (!this.host.endsWith('/')) {
|
|
89
148
|
this.host = this.host.concat('/');
|
|
90
149
|
}
|
|
@@ -127,9 +186,34 @@ let DefaultHttpConnect = DefaultHttpConnect_1 = class DefaultHttpConnect {
|
|
|
127
186
|
from(this.send(streamMessage)),
|
|
128
187
|
// subsequent GET requests using the same SOURCE_REF
|
|
129
188
|
interval$
|
|
189
|
+
.pipe(filter(() => document.visibilityState === 'visible'))
|
|
130
190
|
.pipe(switchMap((ms) => interval(ms)))
|
|
131
191
|
.pipe(switchMap(() => from(this.getMessage(streamMessage))))).pipe(takeWhile(() => this.streams.has(SOURCE_REF)), // ensure it stops emitting once removed
|
|
132
|
-
|
|
192
|
+
retry({
|
|
193
|
+
count: DefaultHttpConnect_1.MAX_RETRIES,
|
|
194
|
+
delay: (err, retryCount) => {
|
|
195
|
+
const error = err;
|
|
196
|
+
logger.error(`Retry ${retryCount}/${DefaultHttpConnect_1.MAX_RETRIES}:`, error.status, error);
|
|
197
|
+
// Only retry if the error is a connection error or auth error
|
|
198
|
+
const isRetryableError = error.status === 0 ||
|
|
199
|
+
(error.status &&
|
|
200
|
+
error.status >= StatusCodes.BAD_REQUEST &&
|
|
201
|
+
error.status < StatusCodes.INTERNAL_SERVER_ERROR);
|
|
202
|
+
if (!isRetryableError) {
|
|
203
|
+
throw err;
|
|
204
|
+
}
|
|
205
|
+
// Remove SOURCE_REF from the map if the error is a 404, meaning the DS has already expired
|
|
206
|
+
if (error.status === StatusCodes.NOT_FOUND) {
|
|
207
|
+
this.streams.delete(SOURCE_REF);
|
|
208
|
+
}
|
|
209
|
+
// Handle 401 by trying to refresh session, then delay for other errors
|
|
210
|
+
if (error.status === StatusCodes.UNAUTHORIZED) {
|
|
211
|
+
return from(this.handleSessionRefresh());
|
|
212
|
+
}
|
|
213
|
+
// For other errors, just delay
|
|
214
|
+
return timer(DefaultHttpConnect_1.RETRY_DELAY_MS);
|
|
215
|
+
},
|
|
216
|
+
}), finalize(() => {
|
|
133
217
|
logger.debug(`Terminating stream for ${resourceName} - ${SOURCE_REF}`);
|
|
134
218
|
this.dataLogoff(SOURCE_REF);
|
|
135
219
|
}));
|
|
@@ -145,6 +229,12 @@ let DefaultHttpConnect = DefaultHttpConnect_1 = class DefaultHttpConnect {
|
|
|
145
229
|
}
|
|
146
230
|
getMessage(message) {
|
|
147
231
|
return __awaiter(this, void 0, void 0, function* () {
|
|
232
|
+
if (this.ongoingRefresh) {
|
|
233
|
+
const refreshSucceeded = yield this.ongoingRefresh;
|
|
234
|
+
if (!refreshSucceeded) {
|
|
235
|
+
throw new Error('Cannot get message: session refresh failed');
|
|
236
|
+
}
|
|
237
|
+
}
|
|
148
238
|
return this.http.get(DefaultHttpConnect_1.getUrl(this.host, message), {
|
|
149
239
|
headers: this.messageBuilder.createHTTPHeadersFromMessage(message),
|
|
150
240
|
});
|
|
@@ -290,6 +380,18 @@ let DefaultHttpConnect = DefaultHttpConnect_1 = class DefaultHttpConnect {
|
|
|
290
380
|
return `${host}${apiEndpoint}`;
|
|
291
381
|
}
|
|
292
382
|
};
|
|
383
|
+
/**
|
|
384
|
+
* Amount of time (in milliseconds) to wait when another tab is handling session reauthentication.
|
|
385
|
+
*/
|
|
386
|
+
DefaultHttpConnect.REAUTH_WAIT_MS = 5000;
|
|
387
|
+
/**
|
|
388
|
+
* Amount of time (in milliseconds) to wait before retrying a failed request.
|
|
389
|
+
*/
|
|
390
|
+
DefaultHttpConnect.RETRY_DELAY_MS = 1000;
|
|
391
|
+
/**
|
|
392
|
+
* Maximum number of retry attempts for failed requests.
|
|
393
|
+
*/
|
|
394
|
+
DefaultHttpConnect.MAX_RETRIES = 3;
|
|
293
395
|
__decorate([
|
|
294
396
|
observable
|
|
295
397
|
], DefaultHttpConnect.prototype, "host", void 0);
|
package/dist/esm/connect/http.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { __awaiter, __decorate, __param } from "tslib";
|
|
2
2
|
import { JSONSerializer } from '@genesislcap/foundation-utils';
|
|
3
3
|
import { DI } from '@microsoft/fast-foundation';
|
|
4
|
+
function createHttpError(response) {
|
|
5
|
+
const error = new Error(`HTTP error: ${response.status} ${response.statusText}`);
|
|
6
|
+
error.status = response.status;
|
|
7
|
+
error.message = response.statusText;
|
|
8
|
+
return error;
|
|
9
|
+
}
|
|
4
10
|
/**
|
|
5
11
|
* The default implementation of the Http interface.
|
|
6
12
|
* @public
|
|
@@ -12,6 +18,9 @@ let DefaultHttp = class DefaultHttp {
|
|
|
12
18
|
get(url, requestInit) {
|
|
13
19
|
return __awaiter(this, void 0, void 0, function* () {
|
|
14
20
|
const response = yield fetch(url, Object.assign(Object.assign({ credentials: 'include', headers: { 'Content-type': 'application/json; charset=UTF-8' } }, requestInit), { method: 'GET' }));
|
|
21
|
+
if (!response.ok) {
|
|
22
|
+
throw createHttpError(response);
|
|
23
|
+
}
|
|
15
24
|
const serializer = requestInit.serializer || this.serializer;
|
|
16
25
|
return serializer.deserialize(response);
|
|
17
26
|
});
|
|
@@ -19,6 +28,9 @@ let DefaultHttp = class DefaultHttp {
|
|
|
19
28
|
post(url, requestInit) {
|
|
20
29
|
return __awaiter(this, void 0, void 0, function* () {
|
|
21
30
|
const response = yield fetch(url, Object.assign(Object.assign({ credentials: 'include', headers: { 'Content-type': 'application/json; charset=UTF-8' } }, requestInit), { method: 'POST' }));
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
throw createHttpError(response);
|
|
33
|
+
}
|
|
22
34
|
const serializer = requestInit.serializer || this.serializer;
|
|
23
35
|
return serializer.deserialize(response);
|
|
24
36
|
});
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
export * from './connect.events';
|
|
2
1
|
export * from './connect';
|
|
2
|
+
export * from './connect.events';
|
|
3
3
|
export * from './connect.types';
|
|
4
|
-
export * from './http.connect';
|
|
5
4
|
export * from './http';
|
|
5
|
+
export * from './http.connect';
|
|
6
6
|
export * from './message';
|
|
7
|
+
export * from './reconnectStrategy';
|
|
7
8
|
export * from './serializers';
|
|
8
|
-
export * from './
|
|
9
|
+
export * from './sessionRefresh';
|
|
9
10
|
export * from './socket';
|
|
11
|
+
export * from './socket.status';
|
|
10
12
|
export * from './socket.types';
|
|
11
|
-
export * from './reconnectStrategy';
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { __awaiter } from "tslib";
|
|
2
|
+
import { LOGIN_DETAILS_URL, LOGIN_URL } from '@genesislcap/foundation-utils';
|
|
3
|
+
import { DI } from '@genesislcap/web-core';
|
|
4
|
+
import { Http } from './http';
|
|
5
|
+
const REAUTH_ACTION_LOCK_KEY = 'reauthActionLock';
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
|
7
|
+
const LOCK_TIMEOUT_MS = 30000;
|
|
8
|
+
/**
|
|
9
|
+
* Result of a session refresh attempt.
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
export var SessionRefreshResult;
|
|
13
|
+
(function (SessionRefreshResult) {
|
|
14
|
+
/** Session was successfully refreshed */
|
|
15
|
+
SessionRefreshResult["SUCCESS"] = "SUCCESS";
|
|
16
|
+
/** Refresh failed - page will reload */
|
|
17
|
+
SessionRefreshResult["FAILED"] = "FAILED";
|
|
18
|
+
/** Another tab is handling the refresh */
|
|
19
|
+
SessionRefreshResult["HANDLED_BY_OTHER_TAB"] = "HANDLED_BY_OTHER_TAB";
|
|
20
|
+
/** Session was already valid */
|
|
21
|
+
SessionRefreshResult["ALREADY_VALID"] = "ALREADY_VALID";
|
|
22
|
+
})(SessionRefreshResult || (SessionRefreshResult = {}));
|
|
23
|
+
/**
|
|
24
|
+
* Clears stale reauth locks that might have been left behind.
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
function clearStaleLock() {
|
|
28
|
+
const lockTimestamp = parseInt(localStorage.getItem(REAUTH_ACTION_LOCK_KEY) || '0');
|
|
29
|
+
if (Date.now() - lockTimestamp > LOCK_TIMEOUT_MS) {
|
|
30
|
+
localStorage.removeItem(REAUTH_ACTION_LOCK_KEY);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Acquires the reauth lock if available.
|
|
35
|
+
* @internal
|
|
36
|
+
* @returns true if lock was acquired, false if another tab holds it
|
|
37
|
+
*/
|
|
38
|
+
function tryAcquireLock() {
|
|
39
|
+
if (!localStorage.getItem(REAUTH_ACTION_LOCK_KEY)) {
|
|
40
|
+
localStorage.setItem(REAUTH_ACTION_LOCK_KEY, Date.now().toString());
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Releases the reauth lock.
|
|
47
|
+
* @internal
|
|
48
|
+
*/
|
|
49
|
+
function releaseLock() {
|
|
50
|
+
localStorage.removeItem(REAUTH_ACTION_LOCK_KEY);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Checks if the user is currently authenticated.
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
function isAuthenticated() {
|
|
57
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
58
|
+
const loginDetailsFetch = yield fetch(LOGIN_DETAILS_URL, { credentials: 'include' });
|
|
59
|
+
const loginDetails = yield loginDetailsFetch.json();
|
|
60
|
+
return loginDetails.MESSAGE_TYPE === 'EVENT_LOGIN_DETAILS_ACK';
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Attempts to refresh the session via EVENT_LOGIN_AUTH.
|
|
65
|
+
* @internal
|
|
66
|
+
* @returns true if refresh was successful (HTTP 2xx response)
|
|
67
|
+
* @throws If the network request fails (connection error, timeout, etc.)
|
|
68
|
+
*/
|
|
69
|
+
function performRefresh() {
|
|
70
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
71
|
+
const http = DI.getOrCreateDOMContainer().get(Http);
|
|
72
|
+
const refreshLoginResponse = yield http.post(LOGIN_URL, {
|
|
73
|
+
body: JSON.stringify({
|
|
74
|
+
DETAILS: {},
|
|
75
|
+
MESSAGE_TYPE: 'EVENT_LOGIN_AUTH',
|
|
76
|
+
}),
|
|
77
|
+
});
|
|
78
|
+
return refreshLoginResponse.MESSAGE_TYPE === 'EVENT_LOGIN_AUTH_ACK';
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Attempts to refresh the session when authentication has expired.
|
|
83
|
+
* Uses a localStorage-based lock mechanism to prevent multiple tabs from
|
|
84
|
+
* trying to reauthenticate simultaneously.
|
|
85
|
+
*
|
|
86
|
+
* @public
|
|
87
|
+
* @param options - Optional configuration
|
|
88
|
+
* @param options.reloadOnFailure - Whether to reload the page if refresh fails (default: true)
|
|
89
|
+
* @returns The result of the refresh attempt
|
|
90
|
+
*/
|
|
91
|
+
export function tryRefreshSession() {
|
|
92
|
+
return __awaiter(this, arguments, void 0, function* (options = {}) {
|
|
93
|
+
const { reloadOnFailure = true } = options;
|
|
94
|
+
// Clear any stale locks from crashed tabs
|
|
95
|
+
clearStaleLock();
|
|
96
|
+
if (!tryAcquireLock()) {
|
|
97
|
+
// Another tab is handling the refresh
|
|
98
|
+
return SessionRefreshResult.HANDLED_BY_OTHER_TAB;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
// Session might already be extended on another tab, so we first check if user is
|
|
102
|
+
// authenticated. Cookies are shared between tabs so we can just retry.
|
|
103
|
+
if (yield isAuthenticated()) {
|
|
104
|
+
return SessionRefreshResult.ALREADY_VALID;
|
|
105
|
+
}
|
|
106
|
+
const refreshSuccessful = yield performRefresh();
|
|
107
|
+
if (!refreshSuccessful) {
|
|
108
|
+
if (reloadOnFailure) {
|
|
109
|
+
window.location.reload();
|
|
110
|
+
}
|
|
111
|
+
return SessionRefreshResult.FAILED;
|
|
112
|
+
}
|
|
113
|
+
return SessionRefreshResult.SUCCESS;
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
releaseLock();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Subscribes to storage events to detect when another tab has finished reauthenticating.
|
|
122
|
+
* @public
|
|
123
|
+
* @param callback - Called when the other tab finishes reauthentication
|
|
124
|
+
* @returns A function to unsubscribe from the storage events
|
|
125
|
+
*/
|
|
126
|
+
export function onOtherTabReauthComplete(callback) {
|
|
127
|
+
const handler = (event) => {
|
|
128
|
+
if (event.key === REAUTH_ACTION_LOCK_KEY && event.newValue === null) {
|
|
129
|
+
callback();
|
|
130
|
+
window.removeEventListener('storage', handler);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
window.addEventListener('storage', handler);
|
|
134
|
+
return () => window.removeEventListener('storage', handler);
|
|
135
|
+
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
var DefaultSocket_1;
|
|
2
1
|
import { __awaiter, __decorate, __param } from "tslib";
|
|
3
2
|
import { User } from '@genesislcap/foundation-user';
|
|
4
|
-
import { LOGIN_DETAILS_URL, LOGIN_URL } from '@genesislcap/foundation-utils';
|
|
5
3
|
import { GENESIS_SOCKET_URL, JSONSerializer, UUID } from '@genesislcap/foundation-utils';
|
|
6
4
|
import { DOM, observable } from '@microsoft/fast-element';
|
|
7
5
|
import { DI } from '@microsoft/fast-foundation';
|
|
@@ -14,6 +12,7 @@ import { ConnectConfig } from './connect.types';
|
|
|
14
12
|
import { Http } from './http';
|
|
15
13
|
import { EventMessageType, MessageBuilder, MessageType, } from './message';
|
|
16
14
|
import { exponentialScheduler, linearScheduler, MAX_RECONNECT_ATTEMPTS, retryInterval, SocketReconnectStrategy, } from './reconnectStrategy';
|
|
15
|
+
import { onOtherTabReauthComplete, SessionRefreshResult, tryRefreshSession, } from './sessionRefresh';
|
|
17
16
|
import { SocketStatus } from './socket.status';
|
|
18
17
|
import { Ticker } from './ticker';
|
|
19
18
|
/**
|
|
@@ -85,7 +84,7 @@ export class SocketMessageHandler {
|
|
|
85
84
|
* Default Socket implementation.
|
|
86
85
|
* @public
|
|
87
86
|
*/
|
|
88
|
-
let DefaultSocket =
|
|
87
|
+
let DefaultSocket = class DefaultSocket {
|
|
89
88
|
get isConfigured() {
|
|
90
89
|
return this.status.isConfigured;
|
|
91
90
|
}
|
|
@@ -292,51 +291,18 @@ let DefaultSocket = DefaultSocket_1 = class DefaultSocket {
|
|
|
292
291
|
}
|
|
293
292
|
handleCookieBasedReauth() {
|
|
294
293
|
return __awaiter(this, void 0, void 0, function* () {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
294
|
+
const result = yield tryRefreshSession();
|
|
295
|
+
if (result === SessionRefreshResult.HANDLED_BY_OTHER_TAB) {
|
|
296
|
+
// Session is already being reauthenticated on another tab, wait for it to finish
|
|
297
|
+
onOtherTabReauthComplete(() => this.connect());
|
|
298
|
+
return;
|
|
300
299
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
localStorage.setItem(DefaultSocket_1.REAUTH_ACTION_LOCK_KEY, Date.now().toString());
|
|
304
|
-
// session might already be extended on another tab, so we first check if user is
|
|
305
|
-
// authenticated and if it is we just reconnect as cookies are shared between tabs and
|
|
306
|
-
// the browser uses latest one when establishing connection with ws
|
|
307
|
-
const loginDetailsFetch = yield fetch(LOGIN_DETAILS_URL, { credentials: 'include' });
|
|
308
|
-
const loginDetails = yield loginDetailsFetch.json();
|
|
309
|
-
if (loginDetails.MESSAGE_TYPE !== 'EVENT_LOGIN_DETAILS_ACK') {
|
|
310
|
-
const refreshLoginResponse = yield fetch(LOGIN_URL, {
|
|
311
|
-
credentials: 'include',
|
|
312
|
-
headers: { 'Content-type': 'application/json; charset=UTF-8' },
|
|
313
|
-
method: 'POST',
|
|
314
|
-
body: JSON.stringify({
|
|
315
|
-
DETAILS: {},
|
|
316
|
-
MESSAGE_TYPE: 'EVENT_LOGIN_AUTH',
|
|
317
|
-
}),
|
|
318
|
-
});
|
|
319
|
-
// if the refresh login is not successful we need to reload the page to get back to the login page
|
|
320
|
-
if (!refreshLoginResponse.ok) {
|
|
321
|
-
window.location.reload();
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
localStorage.removeItem(DefaultSocket_1.REAUTH_ACTION_LOCK_KEY);
|
|
325
|
-
// connect again to a new stream to use new session token from obtained cookie
|
|
300
|
+
if (result !== SessionRefreshResult.FAILED) {
|
|
301
|
+
// Connect again to use new session token from obtained cookie
|
|
326
302
|
yield this.connect();
|
|
327
303
|
}
|
|
328
|
-
else {
|
|
329
|
-
// session is already being reauthenticated on another tab so we just wait when it finishes and reconnect
|
|
330
|
-
window.addEventListener('storage', this.onStorageEvent.bind(this));
|
|
331
|
-
}
|
|
332
304
|
});
|
|
333
305
|
}
|
|
334
|
-
onStorageEvent(event) {
|
|
335
|
-
if (event.key === DefaultSocket_1.REAUTH_ACTION_LOCK_KEY && event.newValue === null) {
|
|
336
|
-
this.connect();
|
|
337
|
-
window.removeEventListener('storage', this.onStorageEvent);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
306
|
updateSessionAndSubscriptions(refreshTokenResult) {
|
|
341
307
|
if ((refreshTokenResult === null || refreshTokenResult === void 0 ? void 0 : refreshTokenResult.MESSAGE_TYPE) === EventMessageType.EVENT_LOGIN_AUTH_ACK) {
|
|
342
308
|
this.wsMessageHandlers.forEach((mh) => {
|
|
@@ -552,7 +518,6 @@ let DefaultSocket = DefaultSocket_1 = class DefaultSocket {
|
|
|
552
518
|
}
|
|
553
519
|
}
|
|
554
520
|
};
|
|
555
|
-
DefaultSocket.REAUTH_ACTION_LOCK_KEY = 'reauthActionLock';
|
|
556
521
|
__decorate([
|
|
557
522
|
observable
|
|
558
523
|
], DefaultSocket.prototype, "websocket", void 0);
|
|
@@ -568,7 +533,7 @@ __decorate([
|
|
|
568
533
|
__decorate([
|
|
569
534
|
observable
|
|
570
535
|
], DefaultSocket.prototype, "heartbeatIsEnabled", void 0);
|
|
571
|
-
DefaultSocket =
|
|
536
|
+
DefaultSocket = __decorate([
|
|
572
537
|
__param(0, MessageBuilder),
|
|
573
538
|
__param(1, Session),
|
|
574
539
|
__param(2, JSONSerializer),
|