@genesislcap/foundation-comms 15.67.0 → 15.68.1
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/reconnectStrategy.d.ts.map +1 -1
- package/dist/dts/connect/sessionRefresh.d.ts +17 -2
- package/dist/dts/connect/sessionRefresh.d.ts.map +1 -1
- package/dist/dts/connect/socket.d.ts +4 -0
- package/dist/dts/connect/socket.d.ts.map +1 -1
- package/dist/esm/connect/reconnectStrategy.js +3 -0
- package/dist/esm/connect/sessionRefresh.js +69 -15
- package/dist/esm/connect/socket.js +52 -23
- package/dist/foundation-comms.api.json +65 -5
- package/dist/foundation-comms.d.ts +22 -2
- package/package.json +12 -12
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconnectStrategy.d.ts","sourceRoot":"","sources":["../../../src/connect/reconnectStrategy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAK5C;;;GAGG;AACH,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC;;;GAGG;AACH,oBAAY,uBAAuB;IACjC,MAAM,WAAW;IACjB,WAAW,gBAAgB;CAC5B;AAED;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC;IAClC;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,uBAAuB,CAAC;CAC7C,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,wBAAwB,GAAG,CACrC,gBAAgB,EAAE,sBAAsB,EACxC,QAAQ,CAAC,EAAE,MAAM,KACd,MAAM,CAAC;AAEZ;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,GACxB,kBAAkB,sBAAsB,EACxC,WAAW,wBAAwB,
|
|
1
|
+
{"version":3,"file":"reconnectStrategy.d.ts","sourceRoot":"","sources":["../../../src/connect/reconnectStrategy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAK5C;;;GAGG;AACH,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC;;;GAGG;AACH,oBAAY,uBAAuB;IACjC,MAAM,WAAW;IACjB,WAAW,gBAAgB;CAC5B;AAED;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC;IAClC;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,uBAAuB,CAAC;CAC7C,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,wBAAwB,GAAG,CACrC,gBAAgB,EAAE,sBAAsB,EACxC,QAAQ,CAAC,EAAE,MAAM,KACd,MAAM,CAAC;AAEZ;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,GACxB,kBAAkB,sBAAsB,EACxC,WAAW,wBAAwB,uBAuBpC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,EAAE,wBAWlC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,wBAS7B,CAAC"}
|
|
@@ -23,12 +23,16 @@ export declare enum SessionRefreshResult {
|
|
|
23
23
|
* trying to reauthenticate simultaneously.
|
|
24
24
|
*
|
|
25
25
|
* @public
|
|
26
|
-
* @param options -
|
|
27
|
-
*
|
|
26
|
+
* @param options - `reloadOnFailure` reloads the page if the refresh fails (default: true). `signal` aborts the session
|
|
27
|
+
* check; a refresh that has started is never aborted, because the server revokes the old refresh token as soon as it
|
|
28
|
+
* issues a new one, so the new cookie must be allowed to land.
|
|
28
29
|
* @returns The result of the refresh attempt
|
|
30
|
+
* @throws If the session check or refresh could not complete (connection error, timeout, aborted check, 408, 429, 5xx,
|
|
31
|
+
* etc.)
|
|
29
32
|
*/
|
|
30
33
|
export declare function tryRefreshSession(options?: {
|
|
31
34
|
reloadOnFailure?: boolean;
|
|
35
|
+
signal?: AbortSignal;
|
|
32
36
|
}): Promise<SessionRefreshResult>;
|
|
33
37
|
/**
|
|
34
38
|
* Subscribes to storage events to detect when another tab has finished reauthenticating.
|
|
@@ -37,4 +41,15 @@ export declare function tryRefreshSession(options?: {
|
|
|
37
41
|
* @returns A function to unsubscribe from the storage events
|
|
38
42
|
*/
|
|
39
43
|
export declare function onOtherTabReauthComplete(callback: () => void): () => void;
|
|
44
|
+
/**
|
|
45
|
+
* Waits until another tab finishes reauthenticating, or until `signal` aborts.
|
|
46
|
+
*
|
|
47
|
+
* @remarks
|
|
48
|
+
* Relies on `storage` events, which don't fire in the tab that changed the key, so when this tab holds the reauth lock
|
|
49
|
+
* itself only `signal` ends the wait.
|
|
50
|
+
*
|
|
51
|
+
* @public
|
|
52
|
+
* @param signal - Ends the wait when aborted
|
|
53
|
+
*/
|
|
54
|
+
export declare function waitForOtherTabReauth(signal: AbortSignal): Promise<void>;
|
|
40
55
|
//# sourceMappingURL=sessionRefresh.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessionRefresh.d.ts","sourceRoot":"","sources":["../../../src/connect/sessionRefresh.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sessionRefresh.d.ts","sourceRoot":"","sources":["../../../src/connect/sessionRefresh.ts"],"names":[],"mappings":"AAcA;;;GAGG;AACH,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAE7C;;;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;AAuFD;;;;;;;;;;;;GAYG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,GAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAO,GAChE,OAAO,CAAC,oBAAoB,CAAC,CA+B/B;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CASzE;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAcxE"}
|
|
@@ -114,6 +114,7 @@ export declare class DefaultSocket implements Socket {
|
|
|
114
114
|
protected config: ConnectConfig;
|
|
115
115
|
private readonly defaultInterval;
|
|
116
116
|
private readonly secondsMultiplier;
|
|
117
|
+
private readonly sessionCheckTimeout;
|
|
117
118
|
private websocket;
|
|
118
119
|
private wsMessages;
|
|
119
120
|
private wsMessageHandlers;
|
|
@@ -153,6 +154,7 @@ export declare class DefaultSocket implements Socket {
|
|
|
153
154
|
* more info here https://docs.genesis.global/docs/develop/server-capabilities/access-control/authentication/#sessiontimeoutmins
|
|
154
155
|
*/
|
|
155
156
|
private handleExpiredSession;
|
|
157
|
+
private get usesCookieAuth();
|
|
156
158
|
private handleCookieBasedReauth;
|
|
157
159
|
private updateSessionAndSubscriptions;
|
|
158
160
|
/**
|
|
@@ -163,6 +165,8 @@ export declare class DefaultSocket implements Socket {
|
|
|
163
165
|
private applyServerHeartbeatInterval;
|
|
164
166
|
private prepareHeartbeat;
|
|
165
167
|
private reconnect;
|
|
168
|
+
private refreshSessionBeforeReconnect;
|
|
169
|
+
private reloadPage;
|
|
166
170
|
reset(): void;
|
|
167
171
|
private cleanMessages;
|
|
168
172
|
send<T>(message: Message<T | any>, needsHandling?: boolean): Promise<Message | any>;
|
|
@@ -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;AACpD,OAAO,EAAsB,cAAc,EAAE,IAAI,EAAE,MAAM,+BAA+B,CAAC;AAGzF,OAAO,
|
|
1
|
+
{"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../../../src/connect/socket.ts"],"names":[],"mappings":"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"}
|
|
@@ -28,6 +28,9 @@ export const retryInterval = (reconnectOptions, scheduler) => {
|
|
|
28
28
|
let timeoutId;
|
|
29
29
|
function recursiveTimeout() {
|
|
30
30
|
observer.next(count);
|
|
31
|
+
if (observer.closed) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
31
34
|
clearTimeout(timeoutId);
|
|
32
35
|
timeoutId = setTimeout(recursiveTimeout, scheduler(reconnectOptions, count));
|
|
33
36
|
count = count + 1;
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { __awaiter } from "tslib";
|
|
2
2
|
import { LOGIN_DETAILS_URL, LOGIN_URL } from '@genesislcap/foundation-utils';
|
|
3
3
|
import { DI } from '@genesislcap/web-core';
|
|
4
|
+
import { logger } from '../utils';
|
|
4
5
|
import { Http } from './http';
|
|
5
6
|
const REAUTH_ACTION_LOCK_KEY = 'reauthActionLock';
|
|
6
7
|
const HTTP_STATUS_UNAUTHORIZED = 401;
|
|
8
|
+
const HTTP_CLIENT_ERROR_MIN = 400;
|
|
9
|
+
const HTTP_SERVER_ERROR_MIN = 500;
|
|
10
|
+
const HTTP_STATUS_REQUEST_TIMEOUT = 408;
|
|
11
|
+
const HTTP_STATUS_TOO_MANY_REQUESTS = 429;
|
|
12
|
+
const TRANSIENT_CLIENT_ERRORS = [HTTP_STATUS_REQUEST_TIMEOUT, HTTP_STATUS_TOO_MANY_REQUESTS];
|
|
7
13
|
/**
|
|
8
14
|
* How long a reauth lock is honoured before {@link tryRefreshSession} treats it as abandoned and clears it.
|
|
9
15
|
* @public
|
|
@@ -57,9 +63,9 @@ function releaseLock() {
|
|
|
57
63
|
* Checks if the user is currently authenticated.
|
|
58
64
|
* @internal
|
|
59
65
|
*/
|
|
60
|
-
function isAuthenticated() {
|
|
66
|
+
function isAuthenticated(signal) {
|
|
61
67
|
return __awaiter(this, void 0, void 0, function* () {
|
|
62
|
-
const loginDetailsFetch = yield fetch(LOGIN_DETAILS_URL, { credentials: 'include' });
|
|
68
|
+
const loginDetailsFetch = yield fetch(LOGIN_DETAILS_URL, { credentials: 'include', signal });
|
|
63
69
|
// An expired session can answer 401 without a JSON body; that still means "refresh needed".
|
|
64
70
|
if (loginDetailsFetch.status === HTTP_STATUS_UNAUTHORIZED) {
|
|
65
71
|
return false;
|
|
@@ -68,22 +74,42 @@ function isAuthenticated() {
|
|
|
68
74
|
return loginDetails.MESSAGE_TYPE === 'EVENT_LOGIN_DETAILS_ACK';
|
|
69
75
|
});
|
|
70
76
|
}
|
|
77
|
+
const isRejectedByServer = (error) => {
|
|
78
|
+
const status = error === null || error === void 0 ? void 0 : error.status;
|
|
79
|
+
return (typeof status === 'number' &&
|
|
80
|
+
status >= HTTP_CLIENT_ERROR_MIN &&
|
|
81
|
+
status < HTTP_SERVER_ERROR_MIN &&
|
|
82
|
+
!TRANSIENT_CLIENT_ERRORS.includes(status));
|
|
83
|
+
};
|
|
71
84
|
/**
|
|
72
85
|
* Attempts to refresh the session via EVENT_LOGIN_AUTH.
|
|
73
86
|
* @internal
|
|
74
|
-
* @returns true if refresh was successful (
|
|
75
|
-
* @throws If the
|
|
87
|
+
* @returns true if refresh was successful; false if the server rejected it (NACK or 4xx other than 408/429)
|
|
88
|
+
* @throws If the request could not complete (connection error, timeout, 408, 429, 5xx, etc.)
|
|
76
89
|
*/
|
|
77
90
|
function performRefresh() {
|
|
78
91
|
return __awaiter(this, void 0, void 0, function* () {
|
|
79
92
|
const http = DI.getOrCreateDOMContainer().get(Http);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
93
|
+
try {
|
|
94
|
+
const refreshLoginResponse = yield http.post(LOGIN_URL, {
|
|
95
|
+
body: JSON.stringify({
|
|
96
|
+
DETAILS: {},
|
|
97
|
+
MESSAGE_TYPE: 'EVENT_LOGIN_AUTH',
|
|
98
|
+
}),
|
|
99
|
+
});
|
|
100
|
+
if (refreshLoginResponse.MESSAGE_TYPE === 'EVENT_LOGIN_AUTH_ACK') {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
logger.debug('Session refresh rejected: ', refreshLoginResponse.ERROR);
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (isRejectedByServer(error)) {
|
|
108
|
+
logger.debug(`Session refresh rejected with HTTP ${error.status}`);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
87
113
|
});
|
|
88
114
|
}
|
|
89
115
|
/**
|
|
@@ -92,13 +118,16 @@ function performRefresh() {
|
|
|
92
118
|
* trying to reauthenticate simultaneously.
|
|
93
119
|
*
|
|
94
120
|
* @public
|
|
95
|
-
* @param options -
|
|
96
|
-
*
|
|
121
|
+
* @param options - `reloadOnFailure` reloads the page if the refresh fails (default: true). `signal` aborts the session
|
|
122
|
+
* check; a refresh that has started is never aborted, because the server revokes the old refresh token as soon as it
|
|
123
|
+
* issues a new one, so the new cookie must be allowed to land.
|
|
97
124
|
* @returns The result of the refresh attempt
|
|
125
|
+
* @throws If the session check or refresh could not complete (connection error, timeout, aborted check, 408, 429, 5xx,
|
|
126
|
+
* etc.)
|
|
98
127
|
*/
|
|
99
128
|
export function tryRefreshSession() {
|
|
100
129
|
return __awaiter(this, arguments, void 0, function* (options = {}) {
|
|
101
|
-
const { reloadOnFailure = true } = options;
|
|
130
|
+
const { reloadOnFailure = true, signal } = options;
|
|
102
131
|
// Clear any stale locks from crashed tabs
|
|
103
132
|
clearStaleLock();
|
|
104
133
|
if (!tryAcquireLock()) {
|
|
@@ -108,7 +137,7 @@ export function tryRefreshSession() {
|
|
|
108
137
|
try {
|
|
109
138
|
// Session might already be extended on another tab, so we first check if user is
|
|
110
139
|
// authenticated. Cookies are shared between tabs so we can just retry.
|
|
111
|
-
if (yield isAuthenticated()) {
|
|
140
|
+
if (yield isAuthenticated(signal)) {
|
|
112
141
|
return SessionRefreshResult.ALREADY_VALID;
|
|
113
142
|
}
|
|
114
143
|
const refreshSuccessful = yield performRefresh();
|
|
@@ -141,3 +170,28 @@ export function onOtherTabReauthComplete(callback) {
|
|
|
141
170
|
window.addEventListener('storage', handler);
|
|
142
171
|
return () => window.removeEventListener('storage', handler);
|
|
143
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Waits until another tab finishes reauthenticating, or until `signal` aborts.
|
|
175
|
+
*
|
|
176
|
+
* @remarks
|
|
177
|
+
* Relies on `storage` events, which don't fire in the tab that changed the key, so when this tab holds the reauth lock
|
|
178
|
+
* itself only `signal` ends the wait.
|
|
179
|
+
*
|
|
180
|
+
* @public
|
|
181
|
+
* @param signal - Ends the wait when aborted
|
|
182
|
+
*/
|
|
183
|
+
export function waitForOtherTabReauth(signal) {
|
|
184
|
+
return new Promise((resolve) => {
|
|
185
|
+
const done = () => {
|
|
186
|
+
stopListening();
|
|
187
|
+
signal.removeEventListener('abort', done);
|
|
188
|
+
resolve();
|
|
189
|
+
};
|
|
190
|
+
const stopListening = onOtherTabReauthComplete(done);
|
|
191
|
+
if (signal.aborted) {
|
|
192
|
+
done();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
signal.addEventListener('abort', done);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
@@ -3,8 +3,8 @@ import { User } from '@genesislcap/foundation-user';
|
|
|
3
3
|
import { GENESIS_SOCKET_URL, JSONSerializer, UUID } from '@genesislcap/foundation-utils';
|
|
4
4
|
import { DOM, observable } from '@microsoft/fast-element';
|
|
5
5
|
import { DI } from '@microsoft/fast-foundation';
|
|
6
|
-
import { Observable, of, Subject, Subscription, throwError } from 'rxjs';
|
|
7
|
-
import { filter,
|
|
6
|
+
import { from, Observable, of, Subject, Subscription, throwError } from 'rxjs';
|
|
7
|
+
import { exhaustMap, filter, map, switchMap, takeWhile } from 'rxjs/operators';
|
|
8
8
|
import { WebSocketSubject } from 'rxjs/webSocket';
|
|
9
9
|
import { Session } from '../session';
|
|
10
10
|
import { logger } from '../utils';
|
|
@@ -13,7 +13,7 @@ import { ConnectConfig } from './connect.types';
|
|
|
13
13
|
import { Http } from './http';
|
|
14
14
|
import { EventMessageType, MessageBuilder, MessageType, } from './message';
|
|
15
15
|
import { exponentialScheduler, linearScheduler, MAX_RECONNECT_ATTEMPTS, retryInterval, SocketReconnectStrategy, } from './reconnectStrategy';
|
|
16
|
-
import { onOtherTabReauthComplete, SessionRefreshResult, tryRefreshSession, } from './sessionRefresh';
|
|
16
|
+
import { onOtherTabReauthComplete, SessionRefreshResult, tryRefreshSession, waitForOtherTabReauth, } from './sessionRefresh';
|
|
17
17
|
import { SocketStatus } from './socket.status';
|
|
18
18
|
import { Ticker } from './ticker';
|
|
19
19
|
/**
|
|
@@ -81,6 +81,9 @@ export class SocketMessageHandler {
|
|
|
81
81
|
return this._isSubscription;
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
+
const whenAborted = (signal) => new Promise((resolve) => {
|
|
85
|
+
signal.addEventListener('abort', () => resolve(undefined), { once: true });
|
|
86
|
+
});
|
|
84
87
|
/**
|
|
85
88
|
* Default Socket implementation.
|
|
86
89
|
* @public
|
|
@@ -131,6 +134,7 @@ let DefaultSocket = class DefaultSocket {
|
|
|
131
134
|
this.config = config;
|
|
132
135
|
this.defaultInterval = 10000;
|
|
133
136
|
this.secondsMultiplier = 1000;
|
|
137
|
+
this.sessionCheckTimeout = 10000;
|
|
134
138
|
this.heartbeatIsEnabled = true;
|
|
135
139
|
this.heartbeatInterval = this.defaultInterval;
|
|
136
140
|
this.socketMessages = () => this.wsMessages;
|
|
@@ -268,20 +272,23 @@ let DefaultSocket = class DefaultSocket {
|
|
|
268
272
|
*/
|
|
269
273
|
handleExpiredSession() {
|
|
270
274
|
return __awaiter(this, void 0, void 0, function* () {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
+
if (this.usesCookieAuth) {
|
|
276
|
+
yield this.handleCookieBasedReauth();
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
275
279
|
const refreshTokenMessage = this.messageBuilder.createRefreshTokenMessage();
|
|
276
280
|
const refreshTokenResult = yield this.send(refreshTokenMessage);
|
|
277
281
|
this.session.setAuthResult(refreshTokenResult);
|
|
278
282
|
this.updateSessionAndSubscriptions(refreshTokenResult);
|
|
279
283
|
}
|
|
280
|
-
else {
|
|
281
|
-
yield this.handleCookieBasedReauth();
|
|
282
|
-
}
|
|
283
284
|
});
|
|
284
285
|
}
|
|
286
|
+
// to detect which auth method is used we check if refreshToken is present in session storage
|
|
287
|
+
// it would not be available in cookies method as it's securely stored within cookie
|
|
288
|
+
// and not accessible from the client
|
|
289
|
+
get usesCookieAuth() {
|
|
290
|
+
return !this.session.getSessionStorageItem('refreshToken');
|
|
291
|
+
}
|
|
285
292
|
handleCookieBasedReauth() {
|
|
286
293
|
return __awaiter(this, void 0, void 0, function* () {
|
|
287
294
|
const result = yield tryRefreshSession();
|
|
@@ -368,21 +375,18 @@ let DefaultSocket = class DefaultSocket {
|
|
|
368
375
|
? exponentialScheduler
|
|
369
376
|
: linearScheduler;
|
|
370
377
|
const reconnectObservable = this.isConnectedSubject.asObservable().pipe(switchMap((isConnected) => !isConnected
|
|
371
|
-
? retryInterval(this.reconnectOptions, scheduler).pipe(
|
|
378
|
+
? retryInterval(this.reconnectOptions, scheduler).pipe(exhaustMap((tick, attempt) => {
|
|
372
379
|
var _a, _b, _c;
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
attemptsAmount
|
|
379
|
-
(_c = (_b = this.reconnectOptions) === null || _b === void 0 ? void 0 : _b.reconnectAttempts) !== null && _c !== void 0 ? _c : MAX_RECONNECT_ATTEMPTS;
|
|
380
|
+
const attemptsAmount = ((_a = this.connectOptions) === null || _a === void 0 ? void 0 : _a.alwaysOn)
|
|
381
|
+
? attempt + 1
|
|
382
|
+
: ((_c = (_b = this.reconnectOptions) === null || _b === void 0 ? void 0 : _b.reconnectAttempts) !== null && _c !== void 0 ? _c : MAX_RECONNECT_ATTEMPTS);
|
|
383
|
+
if (attempt >= attemptsAmount) {
|
|
384
|
+
this.status.reconnectionLimitReached = true;
|
|
385
|
+
return throwError(() => new Error(`Max reconnect attempts ${attemptsAmount} reached.`));
|
|
380
386
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
this.status.reconnectionLimitReached = true;
|
|
385
|
-
return throwError(() => new Error(`Max reconnect attempts ${attemptsAmount} reached.`));
|
|
387
|
+
return tick > 0 && this.user.isAuthenticated && this.usesCookieAuth
|
|
388
|
+
? from(this.refreshSessionBeforeReconnect()).pipe(map(() => attempt))
|
|
389
|
+
: of(attempt);
|
|
386
390
|
}))
|
|
387
391
|
: of(-1)), takeWhile(() => this.websocket && !this.isConnected));
|
|
388
392
|
reconnectObservable.subscribe((_e = (_d = this.reconnectOptions) === null || _d === void 0 ? void 0 : _d.reconnectObserver) !== null && _e !== void 0 ? _e : {
|
|
@@ -408,6 +412,31 @@ let DefaultSocket = class DefaultSocket {
|
|
|
408
412
|
},
|
|
409
413
|
});
|
|
410
414
|
}
|
|
415
|
+
// The router rejects a handshake carrying a dead session cookie with a 401, which the browser
|
|
416
|
+
// reports as a plain 1006 close, so the session can only be checked over HTTP.
|
|
417
|
+
refreshSessionBeforeReconnect() {
|
|
418
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
419
|
+
const signal = AbortSignal.timeout(this.sessionCheckTimeout);
|
|
420
|
+
// Stops waiting at the timeout without aborting a refresh in flight, so its new cookie still lands.
|
|
421
|
+
const result = yield Promise.race([
|
|
422
|
+
tryRefreshSession({ reloadOnFailure: false, signal }),
|
|
423
|
+
whenAborted(signal),
|
|
424
|
+
]).catch((error) => {
|
|
425
|
+
logger.debug('Could not check the session before reconnecting: ', error);
|
|
426
|
+
return undefined;
|
|
427
|
+
});
|
|
428
|
+
if (result === SessionRefreshResult.FAILED) {
|
|
429
|
+
this.reloadPage();
|
|
430
|
+
throw new Error('Session expired and could not be refreshed.');
|
|
431
|
+
}
|
|
432
|
+
if (result === SessionRefreshResult.HANDLED_BY_OTHER_TAB) {
|
|
433
|
+
yield waitForOtherTabReauth(signal);
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
reloadPage() {
|
|
438
|
+
window.location.reload();
|
|
439
|
+
}
|
|
411
440
|
reset() {
|
|
412
441
|
this.status.isConnected = false;
|
|
413
442
|
this.status.isConfigured = false;
|
|
@@ -37725,7 +37725,7 @@
|
|
|
37725
37725
|
{
|
|
37726
37726
|
"kind": "Function",
|
|
37727
37727
|
"canonicalReference": "@genesislcap/foundation-comms!tryRefreshSession:function(1)",
|
|
37728
|
-
"docComment": "/**\n * Attempts to refresh the session when authentication has expired. Uses a localStorage-based lock mechanism to prevent multiple tabs from trying to reauthenticate simultaneously.\n *\n * @param options -
|
|
37728
|
+
"docComment": "/**\n * Attempts to refresh the session when authentication has expired. Uses a localStorage-based lock mechanism to prevent multiple tabs from trying to reauthenticate simultaneously.\n *\n * @param options - `reloadOnFailure` reloads the page if the refresh fails (default: true). `signal` aborts the session check; a refresh that has started is never aborted, because the server revokes the old refresh token as soon as it issues a new one, so the new cookie must be allowed to land.\n *\n * @returns The result of the refresh attempt\n *\n * @throws\n *\n * If the session check or refresh could not complete (connection error, timeout, aborted check, 408, 429, 5xx, etc.)\n *\n * @public\n */\n",
|
|
37729
37729
|
"excerptTokens": [
|
|
37730
37730
|
{
|
|
37731
37731
|
"kind": "Content",
|
|
@@ -37733,7 +37733,16 @@
|
|
|
37733
37733
|
},
|
|
37734
37734
|
{
|
|
37735
37735
|
"kind": "Content",
|
|
37736
|
-
"text": "{\n reloadOnFailure?: boolean;\n
|
|
37736
|
+
"text": "{\n reloadOnFailure?: boolean;\n signal?: "
|
|
37737
|
+
},
|
|
37738
|
+
{
|
|
37739
|
+
"kind": "Reference",
|
|
37740
|
+
"text": "AbortSignal",
|
|
37741
|
+
"canonicalReference": "!AbortSignal:interface"
|
|
37742
|
+
},
|
|
37743
|
+
{
|
|
37744
|
+
"kind": "Content",
|
|
37745
|
+
"text": ";\n}"
|
|
37737
37746
|
},
|
|
37738
37747
|
{
|
|
37739
37748
|
"kind": "Content",
|
|
@@ -37764,8 +37773,8 @@
|
|
|
37764
37773
|
],
|
|
37765
37774
|
"fileUrlPath": "src/connect/sessionRefresh.ts",
|
|
37766
37775
|
"returnTypeTokenRange": {
|
|
37767
|
-
"startIndex":
|
|
37768
|
-
"endIndex":
|
|
37776
|
+
"startIndex": 5,
|
|
37777
|
+
"endIndex": 9
|
|
37769
37778
|
},
|
|
37770
37779
|
"releaseTag": "Public",
|
|
37771
37780
|
"overloadIndex": 1,
|
|
@@ -37774,7 +37783,7 @@
|
|
|
37774
37783
|
"parameterName": "options",
|
|
37775
37784
|
"parameterTypeTokenRange": {
|
|
37776
37785
|
"startIndex": 1,
|
|
37777
|
-
"endIndex":
|
|
37786
|
+
"endIndex": 4
|
|
37778
37787
|
},
|
|
37779
37788
|
"isOptional": true
|
|
37780
37789
|
}
|
|
@@ -37990,6 +37999,57 @@
|
|
|
37990
37999
|
"endIndex": 2
|
|
37991
38000
|
}
|
|
37992
38001
|
]
|
|
38002
|
+
},
|
|
38003
|
+
{
|
|
38004
|
+
"kind": "Function",
|
|
38005
|
+
"canonicalReference": "@genesislcap/foundation-comms!waitForOtherTabReauth:function(1)",
|
|
38006
|
+
"docComment": "/**\n * Waits until another tab finishes reauthenticating, or until `signal` aborts.\n *\n * @remarks\n *\n * Relies on `storage` events, which don't fire in the tab that changed the key, so when this tab holds the reauth lock itself only `signal` ends the wait.\n *\n * @param signal - Ends the wait when aborted\n *\n * @public\n */\n",
|
|
38007
|
+
"excerptTokens": [
|
|
38008
|
+
{
|
|
38009
|
+
"kind": "Content",
|
|
38010
|
+
"text": "export declare function waitForOtherTabReauth(signal: "
|
|
38011
|
+
},
|
|
38012
|
+
{
|
|
38013
|
+
"kind": "Reference",
|
|
38014
|
+
"text": "AbortSignal",
|
|
38015
|
+
"canonicalReference": "!AbortSignal:interface"
|
|
38016
|
+
},
|
|
38017
|
+
{
|
|
38018
|
+
"kind": "Content",
|
|
38019
|
+
"text": "): "
|
|
38020
|
+
},
|
|
38021
|
+
{
|
|
38022
|
+
"kind": "Reference",
|
|
38023
|
+
"text": "Promise",
|
|
38024
|
+
"canonicalReference": "!Promise:interface"
|
|
38025
|
+
},
|
|
38026
|
+
{
|
|
38027
|
+
"kind": "Content",
|
|
38028
|
+
"text": "<void>"
|
|
38029
|
+
},
|
|
38030
|
+
{
|
|
38031
|
+
"kind": "Content",
|
|
38032
|
+
"text": ";"
|
|
38033
|
+
}
|
|
38034
|
+
],
|
|
38035
|
+
"fileUrlPath": "src/connect/sessionRefresh.ts",
|
|
38036
|
+
"returnTypeTokenRange": {
|
|
38037
|
+
"startIndex": 3,
|
|
38038
|
+
"endIndex": 5
|
|
38039
|
+
},
|
|
38040
|
+
"releaseTag": "Public",
|
|
38041
|
+
"overloadIndex": 1,
|
|
38042
|
+
"parameters": [
|
|
38043
|
+
{
|
|
38044
|
+
"parameterName": "signal",
|
|
38045
|
+
"parameterTypeTokenRange": {
|
|
38046
|
+
"startIndex": 1,
|
|
38047
|
+
"endIndex": 2
|
|
38048
|
+
},
|
|
38049
|
+
"isOptional": false
|
|
38050
|
+
}
|
|
38051
|
+
],
|
|
38052
|
+
"name": "waitForOtherTabReauth"
|
|
37993
38053
|
}
|
|
37994
38054
|
]
|
|
37995
38055
|
}
|
|
@@ -2668,6 +2668,7 @@ export declare class DefaultSocket implements Socket {
|
|
|
2668
2668
|
protected config: ConnectConfig;
|
|
2669
2669
|
private readonly defaultInterval;
|
|
2670
2670
|
private readonly secondsMultiplier;
|
|
2671
|
+
private readonly sessionCheckTimeout;
|
|
2671
2672
|
private websocket;
|
|
2672
2673
|
private wsMessages;
|
|
2673
2674
|
private wsMessageHandlers;
|
|
@@ -2707,6 +2708,7 @@ export declare class DefaultSocket implements Socket {
|
|
|
2707
2708
|
* more info here https://docs.genesis.global/docs/develop/server-capabilities/access-control/authentication/#sessiontimeoutmins
|
|
2708
2709
|
*/
|
|
2709
2710
|
private handleExpiredSession;
|
|
2711
|
+
private get usesCookieAuth();
|
|
2710
2712
|
private handleCookieBasedReauth;
|
|
2711
2713
|
private updateSessionAndSubscriptions;
|
|
2712
2714
|
/**
|
|
@@ -2717,6 +2719,8 @@ export declare class DefaultSocket implements Socket {
|
|
|
2717
2719
|
private applyServerHeartbeatInterval;
|
|
2718
2720
|
private prepareHeartbeat;
|
|
2719
2721
|
private reconnect;
|
|
2722
|
+
private refreshSessionBeforeReconnect;
|
|
2723
|
+
private reloadPage;
|
|
2720
2724
|
reset(): void;
|
|
2721
2725
|
private cleanMessages;
|
|
2722
2726
|
send<T>(message: Message<T | any>, needsHandling?: boolean): Promise<Message | any>;
|
|
@@ -5339,12 +5343,16 @@ export declare const toFieldMetadata: (fieldDef: MetadataDetail[]) => FieldMetad
|
|
|
5339
5343
|
* trying to reauthenticate simultaneously.
|
|
5340
5344
|
*
|
|
5341
5345
|
* @public
|
|
5342
|
-
* @param options -
|
|
5343
|
-
*
|
|
5346
|
+
* @param options - `reloadOnFailure` reloads the page if the refresh fails (default: true). `signal` aborts the session
|
|
5347
|
+
* check; a refresh that has started is never aborted, because the server revokes the old refresh token as soon as it
|
|
5348
|
+
* issues a new one, so the new cookie must be allowed to land.
|
|
5344
5349
|
* @returns The result of the refresh attempt
|
|
5350
|
+
* @throws If the session check or refresh could not complete (connection error, timeout, aborted check, 408, 429, 5xx,
|
|
5351
|
+
* etc.)
|
|
5345
5352
|
*/
|
|
5346
5353
|
export declare function tryRefreshSession(options?: {
|
|
5347
5354
|
reloadOnFailure?: boolean;
|
|
5355
|
+
signal?: AbortSignal;
|
|
5348
5356
|
}): Promise<SessionRefreshResult>;
|
|
5349
5357
|
|
|
5350
5358
|
/**
|
|
@@ -5422,6 +5430,18 @@ export declare type UserPermission = CoreUserPermissions | UnknownUserPermission
|
|
|
5422
5430
|
*/
|
|
5423
5431
|
export declare type UserProfile = CoreUserProfiles | UnknownUserProfiles;
|
|
5424
5432
|
|
|
5433
|
+
/**
|
|
5434
|
+
* Waits until another tab finishes reauthenticating, or until `signal` aborts.
|
|
5435
|
+
*
|
|
5436
|
+
* @remarks
|
|
5437
|
+
* Relies on `storage` events, which don't fire in the tab that changed the key, so when this tab holds the reauth lock
|
|
5438
|
+
* itself only `signal` ends the wait.
|
|
5439
|
+
*
|
|
5440
|
+
* @public
|
|
5441
|
+
* @param signal - Ends the wait when aborted
|
|
5442
|
+
*/
|
|
5443
|
+
export declare function waitForOtherTabReauth(signal: AbortSignal): Promise<void>;
|
|
5444
|
+
|
|
5425
5445
|
/**
|
|
5426
5446
|
* The DI token for the Connect interface (WS-only).
|
|
5427
5447
|
* @internal
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genesislcap/foundation-comms",
|
|
3
3
|
"description": "Genesis Foundation UI Comms",
|
|
4
|
-
"version": "15.
|
|
4
|
+
"version": "15.68.1",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"license": "SEE LICENSE IN license.txt",
|
|
7
7
|
"main": "dist/esm/index.js",
|
|
@@ -74,23 +74,23 @@
|
|
|
74
74
|
}
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
|
-
"@genesislcap/foundation-testing": "15.
|
|
78
|
-
"@genesislcap/genx": "15.
|
|
79
|
-
"@genesislcap/ts-builder": "15.
|
|
80
|
-
"@genesislcap/uvu-playwright-builder": "15.
|
|
81
|
-
"@genesislcap/vite-builder": "15.
|
|
82
|
-
"@genesislcap/webpack-builder": "15.
|
|
77
|
+
"@genesislcap/foundation-testing": "15.68.1",
|
|
78
|
+
"@genesislcap/genx": "15.68.1",
|
|
79
|
+
"@genesislcap/ts-builder": "15.68.1",
|
|
80
|
+
"@genesislcap/uvu-playwright-builder": "15.68.1",
|
|
81
|
+
"@genesislcap/vite-builder": "15.68.1",
|
|
82
|
+
"@genesislcap/webpack-builder": "15.68.1",
|
|
83
83
|
"@types/js-cookie": "^3.0.2",
|
|
84
84
|
"@types/json-schema": "^7.0.11",
|
|
85
85
|
"@types/webappsec-credential-management": "^0.6.2",
|
|
86
86
|
"sinon": "^17.0.1"
|
|
87
87
|
},
|
|
88
88
|
"dependencies": {
|
|
89
|
-
"@genesislcap/foundation-broadcast-channel": "15.
|
|
90
|
-
"@genesislcap/foundation-logger": "15.
|
|
91
|
-
"@genesislcap/foundation-user": "15.
|
|
92
|
-
"@genesislcap/foundation-utils": "15.
|
|
93
|
-
"@genesislcap/web-core": "15.
|
|
89
|
+
"@genesislcap/foundation-broadcast-channel": "15.68.1",
|
|
90
|
+
"@genesislcap/foundation-logger": "15.68.1",
|
|
91
|
+
"@genesislcap/foundation-user": "15.68.1",
|
|
92
|
+
"@genesislcap/foundation-utils": "15.68.1",
|
|
93
|
+
"@genesislcap/web-core": "15.68.1",
|
|
94
94
|
"@microsoft/fast-element": "1.14.0",
|
|
95
95
|
"@microsoft/fast-foundation": "2.50.0",
|
|
96
96
|
"bowser": "^2.11.0",
|