@oneidentity/safeguard 8.0.1 → 8.0.2
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/README.md +57 -10
- package/dist/browser.d.ts +3 -23
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +13 -1
- package/dist/browser.js.map +1 -1
- package/dist/events.cjs +24 -5
- package/dist/events.cjs.map +1 -1
- package/dist/events.d.cts +1 -8
- package/dist/events.d.cts.map +1 -0
- package/dist/events.d.ts +1 -8
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +24 -5
- package/dist/events.js.map +1 -1
- package/dist/index.cjs +14 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -28
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +5 -28
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/browser-Df-ljVNJ.d.ts +0 -447
- package/dist/browser-Df-ljVNJ.d.ts.map +0 -1
- package/dist/events-RcqBZBsR.d.ts +0 -185
- package/dist/events-RcqBZBsR.d.ts.map +0 -1
- package/dist/events-zHsrmRBj.d.cts +0 -185
- package/dist/events-zHsrmRBj.d.cts.map +0 -1
- package/dist/index-53AwvPfY.d.ts +0 -670
- package/dist/index-53AwvPfY.d.ts.map +0 -1
- package/dist/index-Bny9XduO.d.cts +0 -670
- package/dist/index-Bny9XduO.d.cts.map +0 -1
package/dist/events.d.cts
CHANGED
|
@@ -30,7 +30,6 @@ interface PersistentListenerOptions {
|
|
|
30
30
|
/** Maximum number of reconnect attempts before giving up. Default: unlimited. */
|
|
31
31
|
maxRetries?: number;
|
|
32
32
|
}
|
|
33
|
-
//# sourceMappingURL=types.d.ts.map
|
|
34
33
|
//#endregion
|
|
35
34
|
//#region src/events/listener.d.ts
|
|
36
35
|
/**
|
|
@@ -55,7 +54,6 @@ declare class SafeguardEventListener {
|
|
|
55
54
|
*/
|
|
56
55
|
stop(): Promise<void>;
|
|
57
56
|
}
|
|
58
|
-
//# sourceMappingURL=listener.d.ts.map
|
|
59
57
|
//#endregion
|
|
60
58
|
//#region src/http/types.d.ts
|
|
61
59
|
/**
|
|
@@ -82,7 +80,6 @@ interface HttpClient {
|
|
|
82
80
|
request(options: HttpRequestOptions): Promise<HttpResponse>;
|
|
83
81
|
dispose?(): void;
|
|
84
82
|
}
|
|
85
|
-
//# sourceMappingURL=types.d.ts.map
|
|
86
83
|
//#endregion
|
|
87
84
|
//#region src/storage/types.d.ts
|
|
88
85
|
/**
|
|
@@ -94,7 +91,6 @@ interface StorageProvider {
|
|
|
94
91
|
remove(key: string): void;
|
|
95
92
|
clear(): void;
|
|
96
93
|
}
|
|
97
|
-
/** Well-known storage keys used by the SDK. */
|
|
98
94
|
//#endregion
|
|
99
95
|
//#region src/auth/types.d.ts
|
|
100
96
|
/**
|
|
@@ -126,7 +122,6 @@ interface Auth {
|
|
|
126
122
|
/** Human-readable description of this auth strategy (for logging). */
|
|
127
123
|
readonly description: string;
|
|
128
124
|
}
|
|
129
|
-
//# sourceMappingURL=types.d.ts.map
|
|
130
125
|
//#endregion
|
|
131
126
|
//#region src/events/persistent.d.ts
|
|
132
127
|
/**
|
|
@@ -159,8 +154,6 @@ declare class PersistentSafeguardEventListener {
|
|
|
159
154
|
*/
|
|
160
155
|
stop(): Promise<void>;
|
|
161
156
|
}
|
|
162
|
-
//# sourceMappingURL=persistent.d.ts.map
|
|
163
|
-
|
|
164
157
|
//#endregion
|
|
165
158
|
export { type EventHandler, type EventListenerState, type PersistentListenerOptions, PersistentSafeguardEventListener, type SafeguardEvent, SafeguardEventListener, type StateChangeHandler };
|
|
166
|
-
//# sourceMappingURL=events
|
|
159
|
+
//# sourceMappingURL=events.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.cts","names":[],"sources":["../src/events/types.ts","../src/events/listener.ts","../src/http/types.ts","../src/storage/types.ts","../src/secret.ts","../src/auth/types.ts","../src/events/persistent.ts"],"mappings":";;;;;;KAGY,kBAAA;;;;KAUA,YAAA,IAAgB,KAAqB,EAAd,cAAc;AAAjD;;;AAAA,KAKY,kBAAA,IAAsB,KAAyB,EAAlB,kBAAkB;AALV;AAKjD;;AALiD,UAUhC,cAAA;EACf,SAAA;EAAA,CACC,GAAW;AAAA;;;;UAMG,yBAAA;EAAA;EAEf,eAAA;;EAEA,UAAU;AAAA;;;;AAhCZ;;;cCIa,sBAAA;EAAA;cAKC,UAAA,EAAY,aAAA;EDCF;EAAA,ICuBlB,KAAA,CAAA,GAAS,kBAAA;EDvBoB;AAAc;AAKjD;ECyBE,EAAA,CAAG,OAAA,sBAA6B,OAAA,EAAS,YAAA;;;ADzBgB;ECiCnD,KAAA,CAAA,GAAS,OAAA;ED5Bc;;;ECqCvB,IAAA,CAAA,GAAQ,OAAA;AAAA;;;;;;UCxDC,kBAAA;EACf,GAAA;EACA,MAAA;EACA,OAAA,GAAU,MAAA;EACV,IAAA,YAAgB,MAAA;EAChB,MAAA,GAAS,WAAA;EACT,OAAA;AAAA;AAAA,UAGe,YAAA;EACf,MAAA;EACA,OAAA,EAAS,MAAM;EACf,IAAA;AAAA;;;;ADTF;UC6BiB,UAAA;EACf,OAAA,CAAQ,OAAA,EAAS,kBAAA,GAAqB,OAAA,CAAQ,YAAA;EAC9C,OAAA;AAAA;;;;;;UCnCe,eAAA;EACf,GAAA,CAAI,GAAA;EACJ,GAAA,CAAI,GAAA,UAAa,KAAA;EACjB,MAAA,CAAO,GAAA;EACP,KAAA;AAAA;;;;;;AHJF;;;;AAA8B;cIOjB,WAAA;EAAA;cAGC,KAAA;EJAqB;EIKjC,MAAA,CAAA;EAIA,QAAA,CAAA;EAIA,MAAA,CAAA;EAAA,KASK,MAAA,CAAO,WAAW;AAAA;;;AJhCzB;;;AAAA,UKIiB,QAAA;ELJa;EKM5B,WAAA,EAAa,WAAW;ELIF;EKFtB,SAAA;ELEiC;EKAjC,UAAA;AAAA;;;;UAMe,IAAA;ELIA;;;;AAEH;AAMd;EKLE,YAAA,CAAa,IAAA,UAAc,UAAA,EAAY,UAAA,EAAY,OAAA,EAAS,eAAA,GAAkB,OAAA,CAAQ,QAAA;;;ALS5E;EKJV,YAAA,EAAc,IAAA,UAAc,UAAA,EAAY,UAAA,EAAY,OAAA,EAAS,eAAA,GAAkB,OAAA,CAAQ,QAAA;;WAG9E,WAAA;AAAA;;;;;AL/BmB;AAU9B;;;;AAAiD;cMUpC,gCAAA;EAAA;cAoBT,UAAA,EAAY,aAAA,EACZ,IAAA,EAAM,IAAA,EACN,IAAA,UACA,UAAA,EAAY,UAAA,EACZ,OAAA,EAAS,eAAA,EACT,OAAA,GAAU,yBAAA,EACV,QAAA,GAAW,QAAA;EN/B0B;EAAA,IMkEnC,KAAA,CAAA,GAAS,kBAAA;EN7DE;;;EMoEf,EAAA,CAAG,OAAA,sBAA6B,OAAA,EAAS,YAAA;ENlE7B;AAMd;;EMoEE,aAAA,CAAc,OAAA,EAAS,kBAAA;ENlEvB;AAEU;;EMwEJ,KAAA,CAAA,GAAS,OAAA;;ALpGjB;;EK+GQ,IAAA,CAAA,GAAQ,OAAA;AAAA"}
|
package/dist/events.d.ts
CHANGED
|
@@ -30,7 +30,6 @@ interface PersistentListenerOptions {
|
|
|
30
30
|
/** Maximum number of reconnect attempts before giving up. Default: unlimited. */
|
|
31
31
|
maxRetries?: number;
|
|
32
32
|
}
|
|
33
|
-
//# sourceMappingURL=types.d.ts.map
|
|
34
33
|
//#endregion
|
|
35
34
|
//#region src/events/listener.d.ts
|
|
36
35
|
/**
|
|
@@ -55,7 +54,6 @@ declare class SafeguardEventListener {
|
|
|
55
54
|
*/
|
|
56
55
|
stop(): Promise<void>;
|
|
57
56
|
}
|
|
58
|
-
//# sourceMappingURL=listener.d.ts.map
|
|
59
57
|
//#endregion
|
|
60
58
|
//#region src/http/types.d.ts
|
|
61
59
|
/**
|
|
@@ -82,7 +80,6 @@ interface HttpClient {
|
|
|
82
80
|
request(options: HttpRequestOptions): Promise<HttpResponse>;
|
|
83
81
|
dispose?(): void;
|
|
84
82
|
}
|
|
85
|
-
//# sourceMappingURL=types.d.ts.map
|
|
86
83
|
//#endregion
|
|
87
84
|
//#region src/storage/types.d.ts
|
|
88
85
|
/**
|
|
@@ -94,7 +91,6 @@ interface StorageProvider {
|
|
|
94
91
|
remove(key: string): void;
|
|
95
92
|
clear(): void;
|
|
96
93
|
}
|
|
97
|
-
/** Well-known storage keys used by the SDK. */
|
|
98
94
|
//#endregion
|
|
99
95
|
//#region src/auth/types.d.ts
|
|
100
96
|
/**
|
|
@@ -126,7 +122,6 @@ interface Auth {
|
|
|
126
122
|
/** Human-readable description of this auth strategy (for logging). */
|
|
127
123
|
readonly description: string;
|
|
128
124
|
}
|
|
129
|
-
//# sourceMappingURL=types.d.ts.map
|
|
130
125
|
//#endregion
|
|
131
126
|
//#region src/events/persistent.d.ts
|
|
132
127
|
/**
|
|
@@ -159,8 +154,6 @@ declare class PersistentSafeguardEventListener {
|
|
|
159
154
|
*/
|
|
160
155
|
stop(): Promise<void>;
|
|
161
156
|
}
|
|
162
|
-
//# sourceMappingURL=persistent.d.ts.map
|
|
163
|
-
|
|
164
157
|
//#endregion
|
|
165
158
|
export { type EventHandler, type EventListenerState, type PersistentListenerOptions, PersistentSafeguardEventListener, type SafeguardEvent, SafeguardEventListener, type StateChangeHandler };
|
|
166
|
-
//# sourceMappingURL=events
|
|
159
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","names":[],"sources":["../src/events/types.ts","../src/events/listener.ts","../src/http/types.ts","../src/storage/types.ts","../src/secret.ts","../src/auth/types.ts","../src/events/persistent.ts"],"mappings":";;;;;;KAGY,kBAAA;;;;KAUA,YAAA,IAAgB,KAAqB,EAAd,cAAc;AAAjD;;;AAAA,KAKY,kBAAA,IAAsB,KAAyB,EAAlB,kBAAkB;AALV;AAKjD;;AALiD,UAUhC,cAAA;EACf,SAAA;EAAA,CACC,GAAW;AAAA;;;;UAMG,yBAAA;EAAA;EAEf,eAAA;;EAEA,UAAU;AAAA;;;;AAhCZ;;;cCIa,sBAAA;EAAA;cAKC,UAAA,EAAY,aAAA;EDCF;EAAA,ICuBlB,KAAA,CAAA,GAAS,kBAAA;EDvBoB;AAAc;AAKjD;ECyBE,EAAA,CAAG,OAAA,sBAA6B,OAAA,EAAS,YAAA;;;ADzBgB;ECiCnD,KAAA,CAAA,GAAS,OAAA;ED5Bc;;;ECqCvB,IAAA,CAAA,GAAQ,OAAA;AAAA;;;;;;UCxDC,kBAAA;EACf,GAAA;EACA,MAAA;EACA,OAAA,GAAU,MAAA;EACV,IAAA,YAAgB,MAAA;EAChB,MAAA,GAAS,WAAA;EACT,OAAA;AAAA;AAAA,UAGe,YAAA;EACf,MAAA;EACA,OAAA,EAAS,MAAM;EACf,IAAA;AAAA;;;;ADTF;UC6BiB,UAAA;EACf,OAAA,CAAQ,OAAA,EAAS,kBAAA,GAAqB,OAAA,CAAQ,YAAA;EAC9C,OAAA;AAAA;;;;;;UCnCe,eAAA;EACf,GAAA,CAAI,GAAA;EACJ,GAAA,CAAI,GAAA,UAAa,KAAA;EACjB,MAAA,CAAO,GAAA;EACP,KAAA;AAAA;;;;;;AHJF;;;;AAA8B;cIOjB,WAAA;EAAA;cAGC,KAAA;EJAqB;EIKjC,MAAA,CAAA;EAIA,QAAA,CAAA;EAIA,MAAA,CAAA;EAAA,KASK,MAAA,CAAO,WAAW;AAAA;;;AJhCzB;;;AAAA,UKIiB,QAAA;ELJa;EKM5B,WAAA,EAAa,WAAW;ELIF;EKFtB,SAAA;ELEiC;EKAjC,UAAA;AAAA;;;;UAMe,IAAA;ELIA;;;;AAEH;AAMd;EKLE,YAAA,CAAa,IAAA,UAAc,UAAA,EAAY,UAAA,EAAY,OAAA,EAAS,eAAA,GAAkB,OAAA,CAAQ,QAAA;;;ALS5E;EKJV,YAAA,EAAc,IAAA,UAAc,UAAA,EAAY,UAAA,EAAY,OAAA,EAAS,eAAA,GAAkB,OAAA,CAAQ,QAAA;;WAG9E,WAAA;AAAA;;;;;AL/BmB;AAU9B;;;;AAAiD;cMUpC,gCAAA;EAAA;cAoBT,UAAA,EAAY,aAAA,EACZ,IAAA,EAAM,IAAA,EACN,IAAA,UACA,UAAA,EAAY,UAAA,EACZ,OAAA,EAAS,eAAA,EACT,OAAA,GAAU,yBAAA,EACV,QAAA,GAAW,QAAA;EN/B0B;EAAA,IMkEnC,KAAA,CAAA,GAAS,kBAAA;EN7DE;;;EMoEf,EAAA,CAAG,OAAA,sBAA6B,OAAA,EAAS,YAAA;ENlE7B;AAMd;;EMoEE,aAAA,CAAc,OAAA,EAAS,kBAAA;ENlEvB;AAEU;;EMwEJ,KAAA,CAAA,GAAS,OAAA;;ALpGjB;;EK+GQ,IAAA,CAAA,GAAQ,OAAA;AAAA"}
|
package/dist/events.js
CHANGED
|
@@ -79,6 +79,8 @@ var PersistentSafeguardEventListener = class {
|
|
|
79
79
|
#manualStop = false;
|
|
80
80
|
#retryCount = 0;
|
|
81
81
|
#retryTimer;
|
|
82
|
+
/** Single-flight token refresh promise to prevent thundering herd. */
|
|
83
|
+
#tokenRefreshPromise;
|
|
82
84
|
constructor(connection, auth, host, httpClient, storage, options, tokenSet) {
|
|
83
85
|
this.#connection = connection;
|
|
84
86
|
this.#auth = auth;
|
|
@@ -162,11 +164,7 @@ var PersistentSafeguardEventListener = class {
|
|
|
162
164
|
if (this.#manualStop) return;
|
|
163
165
|
this.#retryCount++;
|
|
164
166
|
try {
|
|
165
|
-
if (this.#isTokenExpired())
|
|
166
|
-
const refreshed = await this.#auth.refreshToken(this.#host, this.#httpClient, this.#storage);
|
|
167
|
-
if (refreshed) this.#tokenSet = refreshed;
|
|
168
|
-
else this.#tokenSet = await this.#auth.authenticate(this.#host, this.#httpClient, this.#storage);
|
|
169
|
-
} else this.#tokenSet = await this.#auth.authenticate(this.#host, this.#httpClient, this.#storage);
|
|
167
|
+
if (this.#isTokenExpired()) await this.#refreshTokenSingleFlight();
|
|
170
168
|
await this.#connection.start();
|
|
171
169
|
this.#retryCount = 0;
|
|
172
170
|
this.#setState("connected");
|
|
@@ -174,6 +172,27 @@ var PersistentSafeguardEventListener = class {
|
|
|
174
172
|
this.#scheduleReconnect();
|
|
175
173
|
}
|
|
176
174
|
}
|
|
175
|
+
async #refreshTokenSingleFlight() {
|
|
176
|
+
if (this.#tokenRefreshPromise) {
|
|
177
|
+
await this.#tokenRefreshPromise;
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
this.#tokenRefreshPromise = (async () => {
|
|
181
|
+
if (this.#auth.refreshToken) {
|
|
182
|
+
const refreshed = await this.#auth.refreshToken(this.#host, this.#httpClient, this.#storage);
|
|
183
|
+
if (refreshed) {
|
|
184
|
+
this.#tokenSet = refreshed;
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
this.#tokenSet = await this.#auth.authenticate(this.#host, this.#httpClient, this.#storage);
|
|
189
|
+
})();
|
|
190
|
+
try {
|
|
191
|
+
await this.#tokenRefreshPromise;
|
|
192
|
+
} finally {
|
|
193
|
+
this.#tokenRefreshPromise = void 0;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
177
196
|
#isTokenExpired() {
|
|
178
197
|
if (!this.#tokenSet) return true;
|
|
179
198
|
if (!this.#tokenSet.expiresIn) return true;
|
package/dist/events.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","names":["#connection","#handlers","#setState","#state","#connection","#auth","#host","#httpClient","#storage","#retryIntervalMs","#maxRetries","#handlers","#stateChangeHandlers","#tokenSet","#setState","#manualStop","#scheduleReconnect","#retryCount","#state","#retryTimer","#reconnect","#isTokenExpired"],"sources":["../src/events/listener.ts","../src/events/persistent.ts"],"sourcesContent":["import type { HubConnection } from '@microsoft/signalr';\nimport type { EventHandler, EventListenerState, SafeguardEvent } from './types.js';\n\n/**\n * One-shot event listener wrapping a SignalR HubConnection.\n * For persistent auto-reconnect behavior, use PersistentSafeguardEventListener.\n */\nexport class SafeguardEventListener {\n readonly #connection: HubConnection;\n readonly #handlers: EventHandler[] = [];\n #state: EventListenerState = 'stopped';\n\n constructor(connection: HubConnection) {\n this.#connection = connection;\n\n this.#connection.onclose(() => {\n this.#setState('disconnected');\n });\n\n this.#connection.onreconnecting(() => {\n this.#setState('reconnecting');\n });\n\n this.#connection.onreconnected(() => {\n this.#setState('connected');\n });\n\n // Register for the Safeguard event method\n this.#connection.on('NotifyEventAsync', (event: SafeguardEvent) => {\n for (const handler of this.#handlers) {\n handler(event);\n }\n });\n }\n\n /** Current connection state. */\n get state(): EventListenerState {\n return this.#state;\n }\n\n /**\n * Register an event handler for Safeguard notifications.\n */\n on(_method: 'NotifyEventAsync', handler: EventHandler): this {\n this.#handlers.push(handler);\n return this;\n }\n\n /**\n * Start the SignalR connection.\n */\n async start(): Promise<void> {\n this.#setState('starting');\n await this.#connection.start();\n this.#setState('connected');\n }\n\n /**\n * Stop the SignalR connection.\n */\n async stop(): Promise<void> {\n await this.#connection.stop();\n this.#setState('stopped');\n }\n\n #setState(state: EventListenerState): void {\n this.#state = state;\n }\n}\n","import type { HubConnection } from '@microsoft/signalr';\nimport type { Auth, TokenSet } from '../auth/types.js';\nimport type { HttpClient } from '../http/types.js';\nimport type { StorageProvider } from '../storage/types.js';\nimport type {\n EventHandler,\n EventListenerState,\n PersistentListenerOptions,\n SafeguardEvent,\n StateChangeHandler,\n} from './types.js';\n\nconst DEFAULT_RETRY_INTERVAL_MS = 5_000;\nconst TOKEN_REFRESH_MARGIN_MS = 60_000; // refresh 60s before expiry\n\n/**\n * Persistent event listener with automatic reconnection and token refresh.\n *\n * Wraps SignalR's built-in `withAutomaticReconnect()` AND adds:\n * - Token refresh on reconnect (re-authenticates if needed)\n * - State change event notifications\n * - Manual `stop()` that prevents further auto-reconnect\n */\nexport class PersistentSafeguardEventListener {\n readonly #connection: HubConnection;\n readonly #auth: Auth;\n readonly #host: string;\n readonly #httpClient: HttpClient;\n readonly #storage: StorageProvider;\n readonly #retryIntervalMs: number;\n readonly #maxRetries: number | undefined;\n readonly #handlers: EventHandler[] = [];\n readonly #stateChangeHandlers: StateChangeHandler[] = [];\n\n #state: EventListenerState = 'stopped';\n #tokenSet: TokenSet | undefined;\n #manualStop = false;\n #retryCount = 0;\n #retryTimer: ReturnType<typeof setTimeout> | undefined;\n\n constructor(\n connection: HubConnection,\n auth: Auth,\n host: string,\n httpClient: HttpClient,\n storage: StorageProvider,\n options?: PersistentListenerOptions,\n tokenSet?: TokenSet,\n ) {\n this.#connection = connection;\n this.#auth = auth;\n this.#host = host;\n this.#httpClient = httpClient;\n this.#storage = storage;\n this.#retryIntervalMs = options?.retryIntervalMs ?? DEFAULT_RETRY_INTERVAL_MS;\n this.#maxRetries = options?.maxRetries;\n this.#tokenSet = tokenSet;\n\n this.#connection.onclose(() => {\n this.#setState('disconnected');\n if (!this.#manualStop) {\n void this.#scheduleReconnect();\n }\n });\n\n this.#connection.onreconnecting(() => {\n this.#setState('reconnecting');\n });\n\n this.#connection.onreconnected(() => {\n this.#retryCount = 0;\n this.#setState('connected');\n });\n\n this.#connection.on('NotifyEventAsync', (event: SafeguardEvent) => {\n for (const handler of this.#handlers) {\n handler(event);\n }\n });\n }\n\n /** Current connection state. */\n get state(): EventListenerState {\n return this.#state;\n }\n\n /**\n * Register an event handler for Safeguard notifications.\n */\n on(_method: 'NotifyEventAsync', handler: EventHandler): this {\n this.#handlers.push(handler);\n return this;\n }\n\n /**\n * Register a state change handler.\n */\n onStateChange(handler: StateChangeHandler): this {\n this.#stateChangeHandlers.push(handler);\n return this;\n }\n\n /**\n * Start the persistent connection.\n */\n async start(): Promise<void> {\n this.#manualStop = false;\n this.#retryCount = 0;\n this.#setState('starting');\n await this.#connection.start();\n this.#setState('connected');\n }\n\n /**\n * Stop the connection. Prevents auto-reconnect.\n */\n async stop(): Promise<void> {\n this.#manualStop = true;\n if (this.#retryTimer != null) {\n clearTimeout(this.#retryTimer);\n this.#retryTimer = undefined;\n }\n await this.#connection.stop();\n this.#setState('stopped');\n }\n\n // ─── Private ────────────────────────────────────────────────────────\n\n #setState(state: EventListenerState): void {\n this.#state = state;\n for (const handler of this.#stateChangeHandlers) {\n handler(state);\n }\n }\n\n async #scheduleReconnect(): Promise<void> {\n if (this.#manualStop) return;\n if (this.#maxRetries != null && this.#retryCount >= this.#maxRetries) {\n this.#setState('stopped');\n return;\n }\n\n this.#setState('reconnecting');\n this.#retryTimer = setTimeout(() => {\n void this.#reconnect();\n }, this.#retryIntervalMs);\n }\n\n async #reconnect(): Promise<void> {\n if (this.#manualStop) return;\n this.#retryCount++;\n\n try {\n // Only refresh the token if it's expired or near-expiry\n if (this.#isTokenExpired()) {\n if (this.#auth.refreshToken) {\n const refreshed = await this.#auth.refreshToken(this.#host, this.#httpClient, this.#storage);\n if (refreshed) {\n this.#tokenSet = refreshed;\n } else {\n // Refresh failed — full re-auth\n this.#tokenSet = await this.#auth.authenticate(this.#host, this.#httpClient, this.#storage);\n }\n } else {\n this.#tokenSet = await this.#auth.authenticate(this.#host, this.#httpClient, this.#storage);\n }\n }\n\n await this.#connection.start();\n this.#retryCount = 0;\n this.#setState('connected');\n } catch {\n // Reconnect failed — try again\n void this.#scheduleReconnect();\n }\n }\n\n #isTokenExpired(): boolean {\n if (!this.#tokenSet) return true; // no token at all — must authenticate\n if (!this.#tokenSet.expiresIn) return true; // can't determine lifetime — refresh to be safe\n\n const elapsed = Date.now() - this.#tokenSet.acquiredAt;\n const expiresInMs = this.#tokenSet.expiresIn * 1000;\n return elapsed + TOKEN_REFRESH_MARGIN_MS >= expiresInMs;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"events.js","names":["#connection","#handlers","#setState","#state","#connection","#auth","#host","#httpClient","#storage","#retryIntervalMs","#maxRetries","#handlers","#stateChangeHandlers","#tokenSet","#setState","#manualStop","#scheduleReconnect","#retryCount","#state","#retryTimer","#reconnect","#isTokenExpired","#refreshTokenSingleFlight","#tokenRefreshPromise"],"sources":["../src/events/listener.ts","../src/events/persistent.ts"],"sourcesContent":["import type { HubConnection } from '@microsoft/signalr';\nimport type { EventHandler, EventListenerState, SafeguardEvent } from './types.js';\n\n/**\n * One-shot event listener wrapping a SignalR HubConnection.\n * For persistent auto-reconnect behavior, use PersistentSafeguardEventListener.\n */\nexport class SafeguardEventListener {\n readonly #connection: HubConnection;\n readonly #handlers: EventHandler[] = [];\n #state: EventListenerState = 'stopped';\n\n constructor(connection: HubConnection) {\n this.#connection = connection;\n\n this.#connection.onclose(() => {\n this.#setState('disconnected');\n });\n\n this.#connection.onreconnecting(() => {\n this.#setState('reconnecting');\n });\n\n this.#connection.onreconnected(() => {\n this.#setState('connected');\n });\n\n // Register for the Safeguard event method\n this.#connection.on('NotifyEventAsync', (event: SafeguardEvent) => {\n for (const handler of this.#handlers) {\n handler(event);\n }\n });\n }\n\n /** Current connection state. */\n get state(): EventListenerState {\n return this.#state;\n }\n\n /**\n * Register an event handler for Safeguard notifications.\n */\n on(_method: 'NotifyEventAsync', handler: EventHandler): this {\n this.#handlers.push(handler);\n return this;\n }\n\n /**\n * Start the SignalR connection.\n */\n async start(): Promise<void> {\n this.#setState('starting');\n await this.#connection.start();\n this.#setState('connected');\n }\n\n /**\n * Stop the SignalR connection.\n */\n async stop(): Promise<void> {\n await this.#connection.stop();\n this.#setState('stopped');\n }\n\n #setState(state: EventListenerState): void {\n this.#state = state;\n }\n}\n","import type { HubConnection } from '@microsoft/signalr';\nimport type { Auth, TokenSet } from '../auth/types.js';\nimport type { HttpClient } from '../http/types.js';\nimport type { StorageProvider } from '../storage/types.js';\nimport type {\n EventHandler,\n EventListenerState,\n PersistentListenerOptions,\n SafeguardEvent,\n StateChangeHandler,\n} from './types.js';\n\nconst DEFAULT_RETRY_INTERVAL_MS = 5_000;\nconst TOKEN_REFRESH_MARGIN_MS = 60_000; // refresh 60s before expiry\n\n/**\n * Persistent event listener with automatic reconnection and token refresh.\n *\n * Wraps SignalR's built-in `withAutomaticReconnect()` AND adds:\n * - Token refresh on reconnect (re-authenticates if needed)\n * - State change event notifications\n * - Manual `stop()` that prevents further auto-reconnect\n */\nexport class PersistentSafeguardEventListener {\n readonly #connection: HubConnection;\n readonly #auth: Auth;\n readonly #host: string;\n readonly #httpClient: HttpClient;\n readonly #storage: StorageProvider;\n readonly #retryIntervalMs: number;\n readonly #maxRetries: number | undefined;\n readonly #handlers: EventHandler[] = [];\n readonly #stateChangeHandlers: StateChangeHandler[] = [];\n\n #state: EventListenerState = 'stopped';\n #tokenSet: TokenSet | undefined;\n #manualStop = false;\n #retryCount = 0;\n #retryTimer: ReturnType<typeof setTimeout> | undefined;\n /** Single-flight token refresh promise to prevent thundering herd. */\n #tokenRefreshPromise: Promise<void> | undefined;\n\n constructor(\n connection: HubConnection,\n auth: Auth,\n host: string,\n httpClient: HttpClient,\n storage: StorageProvider,\n options?: PersistentListenerOptions,\n tokenSet?: TokenSet,\n ) {\n this.#connection = connection;\n this.#auth = auth;\n this.#host = host;\n this.#httpClient = httpClient;\n this.#storage = storage;\n this.#retryIntervalMs = options?.retryIntervalMs ?? DEFAULT_RETRY_INTERVAL_MS;\n this.#maxRetries = options?.maxRetries;\n this.#tokenSet = tokenSet;\n\n this.#connection.onclose(() => {\n this.#setState('disconnected');\n if (!this.#manualStop) {\n void this.#scheduleReconnect();\n }\n });\n\n this.#connection.onreconnecting(() => {\n this.#setState('reconnecting');\n });\n\n this.#connection.onreconnected(() => {\n this.#retryCount = 0;\n this.#setState('connected');\n });\n\n this.#connection.on('NotifyEventAsync', (event: SafeguardEvent) => {\n for (const handler of this.#handlers) {\n handler(event);\n }\n });\n }\n\n /** Current connection state. */\n get state(): EventListenerState {\n return this.#state;\n }\n\n /**\n * Register an event handler for Safeguard notifications.\n */\n on(_method: 'NotifyEventAsync', handler: EventHandler): this {\n this.#handlers.push(handler);\n return this;\n }\n\n /**\n * Register a state change handler.\n */\n onStateChange(handler: StateChangeHandler): this {\n this.#stateChangeHandlers.push(handler);\n return this;\n }\n\n /**\n * Start the persistent connection.\n */\n async start(): Promise<void> {\n this.#manualStop = false;\n this.#retryCount = 0;\n this.#setState('starting');\n await this.#connection.start();\n this.#setState('connected');\n }\n\n /**\n * Stop the connection. Prevents auto-reconnect.\n */\n async stop(): Promise<void> {\n this.#manualStop = true;\n if (this.#retryTimer != null) {\n clearTimeout(this.#retryTimer);\n this.#retryTimer = undefined;\n }\n await this.#connection.stop();\n this.#setState('stopped');\n }\n\n // ─── Private ────────────────────────────────────────────────────────\n\n #setState(state: EventListenerState): void {\n this.#state = state;\n for (const handler of this.#stateChangeHandlers) {\n handler(state);\n }\n }\n\n async #scheduleReconnect(): Promise<void> {\n if (this.#manualStop) return;\n if (this.#maxRetries != null && this.#retryCount >= this.#maxRetries) {\n this.#setState('stopped');\n return;\n }\n\n this.#setState('reconnecting');\n this.#retryTimer = setTimeout(() => {\n void this.#reconnect();\n }, this.#retryIntervalMs);\n }\n\n async #reconnect(): Promise<void> {\n if (this.#manualStop) return;\n this.#retryCount++;\n\n try {\n // Only refresh the token if it's expired or near-expiry. Single-flight\n // so overlapping reconnect attempts don't double-call refreshToken or\n // trample the storage provider.\n if (this.#isTokenExpired()) {\n await this.#refreshTokenSingleFlight();\n }\n\n await this.#connection.start();\n this.#retryCount = 0;\n this.#setState('connected');\n } catch {\n // Reconnect failed — try again\n void this.#scheduleReconnect();\n }\n }\n\n async #refreshTokenSingleFlight(): Promise<void> {\n if (this.#tokenRefreshPromise) {\n await this.#tokenRefreshPromise;\n return;\n }\n this.#tokenRefreshPromise = (async (): Promise<void> => {\n if (this.#auth.refreshToken) {\n const refreshed = await this.#auth.refreshToken(this.#host, this.#httpClient, this.#storage);\n if (refreshed) {\n this.#tokenSet = refreshed;\n return;\n }\n }\n this.#tokenSet = await this.#auth.authenticate(this.#host, this.#httpClient, this.#storage);\n })();\n try {\n await this.#tokenRefreshPromise;\n } finally {\n this.#tokenRefreshPromise = undefined;\n }\n }\n\n #isTokenExpired(): boolean {\n if (!this.#tokenSet) return true; // no token at all — must authenticate\n if (!this.#tokenSet.expiresIn) return true; // can't determine lifetime — refresh to be safe\n\n const elapsed = Date.now() - this.#tokenSet.acquiredAt;\n const expiresInMs = this.#tokenSet.expiresIn * 1000;\n return elapsed + TOKEN_REFRESH_MARGIN_MS >= expiresInMs;\n }\n}\n"],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -684,7 +684,7 @@ var PkceNonInteractiveAuth = class {
|
|
|
684
684
|
const state = generateState();
|
|
685
685
|
const redirectUri = `http://localhost:${String(this.#callbackPort)}/callback`;
|
|
686
686
|
const server = createServer();
|
|
687
|
-
server.listen(this.#callbackPort);
|
|
687
|
+
server.listen(this.#callbackPort, "127.0.0.1");
|
|
688
688
|
const authUrl = `https://${host}/RSTS/Login?${new URLSearchParams({
|
|
689
689
|
response_type: "code",
|
|
690
690
|
client_id: "safeguard.js",
|
|
@@ -875,6 +875,8 @@ var SafeguardClient = class {
|
|
|
875
875
|
#storage;
|
|
876
876
|
#tokenSet;
|
|
877
877
|
#connected = false;
|
|
878
|
+
/** Single-flight refresh promise to prevent thundering herd on token expiry. */
|
|
879
|
+
#tokenRefreshPromise;
|
|
878
880
|
constructor(host, options) {
|
|
879
881
|
if (!host) throw new ConfigurationError("SafeguardClient requires a host");
|
|
880
882
|
if (!options.auth) throw new ConfigurationError("SafeguardClient requires an auth strategy");
|
|
@@ -1010,7 +1012,12 @@ var SafeguardClient = class {
|
|
|
1010
1012
|
if (!this.#tokenSet.expiresIn) return;
|
|
1011
1013
|
const elapsed = Date.now() - this.#tokenSet.acquiredAt;
|
|
1012
1014
|
const expiresInMs = this.#tokenSet.expiresIn * 1e3;
|
|
1013
|
-
if (elapsed + TOKEN_REFRESH_MARGIN_MS
|
|
1015
|
+
if (elapsed + TOKEN_REFRESH_MARGIN_MS < expiresInMs) return;
|
|
1016
|
+
if (this.#tokenRefreshPromise) {
|
|
1017
|
+
await this.#tokenRefreshPromise;
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
this.#tokenRefreshPromise = (async () => {
|
|
1014
1021
|
if (this.#auth.refreshToken) {
|
|
1015
1022
|
const refreshed = await this.#auth.refreshToken(this.#host, this.#httpClient, this.#storage);
|
|
1016
1023
|
if (refreshed) {
|
|
@@ -1019,6 +1026,11 @@ var SafeguardClient = class {
|
|
|
1019
1026
|
}
|
|
1020
1027
|
}
|
|
1021
1028
|
this.#tokenSet = await this.#auth.authenticate(this.#host, this.#httpClient, this.#storage);
|
|
1029
|
+
})();
|
|
1030
|
+
try {
|
|
1031
|
+
await this.#tokenRefreshPromise;
|
|
1032
|
+
} finally {
|
|
1033
|
+
this.#tokenRefreshPromise = void 0;
|
|
1022
1034
|
}
|
|
1023
1035
|
}
|
|
1024
1036
|
};
|