@oneidentity/safeguard 8.1.0 → 8.2.0-pre58935

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.
@@ -1 +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,IAAS,kBAAA;EDvBoB;AAAc;AAKjD;ECyBE,EAAA,CAAG,OAAA,sBAA6B,OAAA,EAAS,YAAA;;;ADzBgB;ECiCnD,KAAA,IAAS,OAAA;ED5Bc;;;ECqCvB,IAAA,IAAQ,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;EAIA,QAAA;EAIA,MAAA;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,IAAS,kBAAA;EN7DE;;;EMoEf,EAAA,CAAG,OAAA,sBAA6B,OAAA,EAAS,YAAA;ENlE7B;AAMd;;EMoEE,aAAA,CAAc,OAAA,EAAS,kBAAA;ENlEvB;AAEU;;EMwEJ,KAAA,IAAS,OAAA;;ALpGjB;;EK+GQ,IAAA,IAAQ,OAAA;AAAA"}
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;;;;KAUA,gBAAgB,OAAO;;;;KAKvB,sBAAsB,OAAO;;;;UAKxB;EACf;GACC;;;;;UAMc;;EAEf;;EAEA;;;;;;;;cC5BW;;EAKC,YAAA,YAAY;;MAwBpB,SAAS;;;;EAOb,GAAG,6BAA6B,SAAS;;;;EAQnC,SAAS;;;;EAST,QAAQ;;;;;;;UCxDC;EACf;EACA;EACA,UAAU;EACV,gBAAgB;EAChB,SAAS;EACT;;UAGe;EACf;EACA,SAAS;EACT;;;;;;UA+Ce;EACf,QAAQ,SAAS,qBAAqB,QAAQ;EAC9C;;;;;;;UC9De;EACf,IAAI;EACJ,IAAI,aAAa;EACjB,OAAO;EACP;;;;;;;;;;;;cCGW;;EAGC,YAAA;;EAKZ;EAIA;EAIA;OASK,OAAO;;;;;;;UC5BG;;EAEf,aAAa;;EAEb;;EAEA;;;;;UAMe;;;;;;;EAOf,aAAa,cAAc,YAAY,YAAY,SAAS,kBAAkB,QAAQ;;;;EAKtF,cAAc,cAAc,YAAY,YAAY,SAAS,kBAAkB,QAAQ;;WAG9E;;;;;;;;;;;;cCXE;;EAoBT,YAAA,YAAY,eACZ,MAAM,MACN,cACA,YAAY,YACZ,SAAS,iBACT,UAAU,2BACV,WAAW;;MAmCT,SAAS;;;;EAOb,GAAG,6BAA6B,SAAS;;;;EAQzC,cAAc,SAAS;;;;EAQjB,SAAS;;;;EAWT,QAAQ"}
@@ -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","#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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"}
1
+ {"version":3,"file":"events.js","names":[],"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":";;;;;AAOA,IAAa,yBAAb,MAAoC;CAClC;CACA,YAAqC,CAAC;CACtC,SAA6B;CAE7B,YAAY,YAA2B;EACrC,KAAK,cAAc;EAEnB,KAAK,YAAY,cAAc;GAC7B,KAAK,UAAU,cAAc;EAC/B,CAAC;EAED,KAAK,YAAY,qBAAqB;GACpC,KAAK,UAAU,cAAc;EAC/B,CAAC;EAED,KAAK,YAAY,oBAAoB;GACnC,KAAK,UAAU,WAAW;EAC5B,CAAC;EAGD,KAAK,YAAY,GAAG,qBAAqB,UAA0B;GACjE,KAAK,MAAM,WAAW,KAAK,WACzB,QAAQ,KAAK;EAEjB,CAAC;CACH;;CAGA,IAAI,QAA4B;EAC9B,OAAO,KAAK;CACd;;;;CAKA,GAAG,SAA6B,SAA6B;EAC3D,KAAK,UAAU,KAAK,OAAO;EAC3B,OAAO;CACT;;;;CAKA,MAAM,QAAuB;EAC3B,KAAK,UAAU,UAAU;EACzB,MAAM,KAAK,YAAY,MAAM;EAC7B,KAAK,UAAU,WAAW;CAC5B;;;;CAKA,MAAM,OAAsB;EAC1B,MAAM,KAAK,YAAY,KAAK;EAC5B,KAAK,UAAU,SAAS;CAC1B;CAEA,UAAU,OAAiC;EACzC,KAAK,SAAS;CAChB;AACF;;;ACxDA,MAAM,4BAA4B;AAClC,MAAM,0BAA0B;;;;;;;;;AAUhC,IAAa,mCAAb,MAA8C;CAC5C;CACA;CACA;CACA;CACA;CACA;CACA;CACA,YAAqC,CAAC;CACtC,uBAAsD,CAAC;CAEvD,SAA6B;CAC7B;CACA,cAAc;CACd,cAAc;CACd;;CAEA;CAEA,YACE,YACA,MACA,MACA,YACA,SACA,SACA,UACA;EACA,KAAK,cAAc;EACnB,KAAK,QAAQ;EACb,KAAK,QAAQ;EACb,KAAK,cAAc;EACnB,KAAK,WAAW;EAChB,KAAK,mBAAmB,SAAS,mBAAmB;EACpD,KAAK,cAAc,SAAS;EAC5B,KAAK,YAAY;EAEjB,KAAK,YAAY,cAAc;GAC7B,KAAK,UAAU,cAAc;GAC7B,IAAI,CAAC,KAAK,aACR,KAAU,mBAAmB;EAEjC,CAAC;EAED,KAAK,YAAY,qBAAqB;GACpC,KAAK,UAAU,cAAc;EAC/B,CAAC;EAED,KAAK,YAAY,oBAAoB;GACnC,KAAK,cAAc;GACnB,KAAK,UAAU,WAAW;EAC5B,CAAC;EAED,KAAK,YAAY,GAAG,qBAAqB,UAA0B;GACjE,KAAK,MAAM,WAAW,KAAK,WACzB,QAAQ,KAAK;EAEjB,CAAC;CACH;;CAGA,IAAI,QAA4B;EAC9B,OAAO,KAAK;CACd;;;;CAKA,GAAG,SAA6B,SAA6B;EAC3D,KAAK,UAAU,KAAK,OAAO;EAC3B,OAAO;CACT;;;;CAKA,cAAc,SAAmC;EAC/C,KAAK,qBAAqB,KAAK,OAAO;EACtC,OAAO;CACT;;;;CAKA,MAAM,QAAuB;EAC3B,KAAK,cAAc;EACnB,KAAK,cAAc;EACnB,KAAK,UAAU,UAAU;EACzB,MAAM,KAAK,YAAY,MAAM;EAC7B,KAAK,UAAU,WAAW;CAC5B;;;;CAKA,MAAM,OAAsB;EAC1B,KAAK,cAAc;EACnB,IAAI,KAAK,eAAe,MAAM;GAC5B,aAAa,KAAK,WAAW;GAC7B,KAAK,cAAc,KAAA;EACrB;EACA,MAAM,KAAK,YAAY,KAAK;EAC5B,KAAK,UAAU,SAAS;CAC1B;CAIA,UAAU,OAAiC;EACzC,KAAK,SAAS;EACd,KAAK,MAAM,WAAW,KAAK,sBACzB,QAAQ,KAAK;CAEjB;CAEA,MAAM,qBAAoC;EACxC,IAAI,KAAK,aAAa;EACtB,IAAI,KAAK,eAAe,QAAQ,KAAK,eAAe,KAAK,aAAa;GACpE,KAAK,UAAU,SAAS;GACxB;EACF;EAEA,KAAK,UAAU,cAAc;EAC7B,KAAK,cAAc,iBAAiB;GAClC,KAAU,WAAW;EACvB,GAAG,KAAK,gBAAgB;CAC1B;CAEA,MAAM,aAA4B;EAChC,IAAI,KAAK,aAAa;EACtB,KAAK;EAEL,IAAI;GAIF,IAAI,KAAK,gBAAgB,GACvB,MAAM,KAAK,0BAA0B;GAGvC,MAAM,KAAK,YAAY,MAAM;GAC7B,KAAK,cAAc;GACnB,KAAK,UAAU,WAAW;EAC5B,QAAQ;GAEN,KAAU,mBAAmB;EAC/B;CACF;CAEA,MAAM,4BAA2C;EAC/C,IAAI,KAAK,sBAAsB;GAC7B,MAAM,KAAK;GACX;EACF;EACA,KAAK,wBAAwB,YAA2B;GACtD,IAAI,KAAK,MAAM,cAAc;IAC3B,MAAM,YAAY,MAAM,KAAK,MAAM,aAAa,KAAK,OAAO,KAAK,aAAa,KAAK,QAAQ;IAC3F,IAAI,WAAW;KACb,KAAK,YAAY;KACjB;IACF;GACF;GACA,KAAK,YAAY,MAAM,KAAK,MAAM,aAAa,KAAK,OAAO,KAAK,aAAa,KAAK,QAAQ;EAC5F,EAAA,CAAG;EACH,IAAI;GACF,MAAM,KAAK;EACb,UAAU;GACR,KAAK,uBAAuB,KAAA;EAC9B;CACF;CAEA,kBAA2B;EACzB,IAAI,CAAC,KAAK,WAAW,OAAO;EAC5B,IAAI,CAAC,KAAK,UAAU,WAAW,OAAO;EAEtC,MAAM,UAAU,KAAK,IAAI,IAAI,KAAK,UAAU;EAC5C,MAAM,cAAc,KAAK,UAAU,YAAY;EAC/C,OAAO,UAAU,2BAA2B;CAC9C;AACF"}
package/dist/index.cjs CHANGED
@@ -218,7 +218,7 @@ function base64UrlEncode(bytes) {
218
218
  * Returns a 43-character base64url string (32 random bytes).
219
219
  */
220
220
  function generateCodeVerifier() {
221
- const bytes = new Uint8Array(32);
221
+ const bytes = /* @__PURE__ */ new Uint8Array(32);
222
222
  crypto.getRandomValues(bytes);
223
223
  return base64UrlEncode(bytes);
224
224
  }
@@ -235,7 +235,7 @@ async function generateCodeChallenge(verifier) {
235
235
  * Generates a random state parameter for OAuth flows.
236
236
  */
237
237
  function generateState() {
238
- const bytes = new Uint8Array(16);
238
+ const bytes = /* @__PURE__ */ new Uint8Array(16);
239
239
  crypto.getRandomValues(bytes);
240
240
  return base64UrlEncode(bytes);
241
241
  }
@@ -308,19 +308,46 @@ const StorageKeys = {
308
308
  //#endregion
309
309
  //#region src/http/node.ts
310
310
  /**
311
+ * Resolve the undici Agent `connect` (TLS) options from {@link TlsOptions}.
312
+ *
313
+ * Applies the opt-in `minVersion`/`maxVersion` pins. When a client certificate
314
+ * is present and the caller has pinned neither bound, the connection is capped
315
+ * at TLS 1.2: Node has no client-side TLS 1.3 post-handshake authentication
316
+ * (see https://github.com/nodejs/node/issues/46120), so on TLS 1.3 the client
317
+ * never presents its certificate and cert/A2A auth fails (error 60094) on the
318
+ * appliance's Standard binding. Capping at TLS 1.2 keeps the cert request
319
+ * in-handshake so it works by default. Setting either bound explicitly (for
320
+ * example `minVersion: 'TLSv1.3'` when targeting a Cert SNI hostname) disables
321
+ * the auto-cap. Password/token connections carry no certificate and keep
322
+ * negotiating up to TLS 1.3.
323
+ */
324
+ function resolveTlsConnectOptions(tlsOptions) {
325
+ const connect = {
326
+ ca: tlsOptions?.ca,
327
+ cert: tlsOptions?.cert,
328
+ key: tlsOptions?.key,
329
+ pfx: tlsOptions?.pfx,
330
+ passphrase: tlsOptions?.passphrase,
331
+ rejectUnauthorized: tlsOptions?.rejectUnauthorized ?? true
332
+ };
333
+ const hasClientCert = Boolean(tlsOptions?.cert ?? tlsOptions?.key ?? tlsOptions?.pfx);
334
+ if (tlsOptions?.minVersion) connect.minVersion = tlsOptions.minVersion;
335
+ if (tlsOptions?.maxVersion) connect.maxVersion = tlsOptions.maxVersion;
336
+ else if (hasClientCert && !tlsOptions?.minVersion) connect.maxVersion = "TLSv1.2";
337
+ return connect;
338
+ }
339
+ /**
311
340
  * Node.js HTTP client using undici for full TLS control.
312
341
  * Creates a per-instance Agent — no global state mutation.
313
342
  */
314
343
  var NodeHttpClient = class {
315
344
  #agent;
316
345
  constructor(tlsOptions) {
317
- this.#agent = new undici.Agent({ connect: {
318
- ca: tlsOptions?.ca,
319
- cert: tlsOptions?.cert,
320
- key: tlsOptions?.key,
321
- passphrase: tlsOptions?.passphrase,
322
- rejectUnauthorized: tlsOptions?.rejectUnauthorized ?? true
323
- } });
346
+ const hasClientCert = Boolean(tlsOptions?.cert ?? tlsOptions?.key ?? tlsOptions?.pfx);
347
+ this.#agent = new undici.Agent({
348
+ connect: resolveTlsConnectOptions(tlsOptions),
349
+ ...hasClientCert ? { allowH2: false } : {}
350
+ });
324
351
  }
325
352
  async request(options) {
326
353
  const { url, method, headers, body, signal, timeout } = options;
@@ -1256,7 +1283,8 @@ var A2AClient = class {
1256
1283
  * @param apiKey - The API key for the retrievable account
1257
1284
  */
1258
1285
  async retrievePassword(apiKey) {
1259
- return new SecretValue(await this.#a2aRequest(apiKey, "GET", "Credentials", { type: "Password" }));
1286
+ const body = await this.#a2aRequest(apiKey, "GET", "Credentials", { type: "Password" });
1287
+ return new SecretValue(JSON.parse(body));
1260
1288
  }
1261
1289
  /**
1262
1290
  * Retrieve an SSH private key credential via A2A.
@@ -1265,10 +1293,11 @@ var A2AClient = class {
1265
1293
  */
1266
1294
  async retrievePrivateKey(apiKey, format) {
1267
1295
  const keyType = format ?? "OpenSsh";
1268
- return new SecretValue(await this.#a2aRequest(apiKey, "GET", "Credentials", {
1269
- type: "SshKey",
1296
+ const body = await this.#a2aRequest(apiKey, "GET", "Credentials", {
1297
+ type: "PrivateKey",
1270
1298
  keyFormat: keyType
1271
- }));
1299
+ });
1300
+ return new SecretValue(JSON.parse(body));
1272
1301
  }
1273
1302
  /**
1274
1303
  * Retrieve an API key secret credential via A2A.
@@ -1279,16 +1308,32 @@ var A2AClient = class {
1279
1308
  }
1280
1309
  /**
1281
1310
  * Get the list of retrievable accounts available to this certificate.
1311
+ *
1312
+ * Retrievable accounts live under the Core service and are authorized by the
1313
+ * client certificate alone (no per-account API key). Enumerate every A2A
1314
+ * registration bound to the certificate, then collect each registration's
1315
+ * retrievable accounts.
1282
1316
  */
1283
1317
  async getRetrievableAccounts() {
1284
1318
  this.#ensureHttpClient();
1285
- const response = await this.#httpClient.request({
1286
- url: `https://${this.#host}/service/a2a/${this.#apiVersion}/A2ARegistrations`,
1319
+ const regResponse = await this.#httpClient.request({
1320
+ url: `https://${this.#host}/service/core/${this.#apiVersion}/A2ARegistrations`,
1287
1321
  method: "GET",
1288
1322
  headers: { Accept: "application/json" }
1289
1323
  });
1290
- if (response.status !== 200) throw ApiError.fromResponse(response.status, response.body);
1291
- return JSON.parse(response.body);
1324
+ if (regResponse.status !== 200) throw ApiError.fromResponse(regResponse.status, regResponse.body);
1325
+ const registrations = JSON.parse(regResponse.body);
1326
+ const accounts = [];
1327
+ for (const reg of registrations) {
1328
+ const acctResponse = await this.#httpClient.request({
1329
+ url: `https://${this.#host}/service/core/${this.#apiVersion}/A2ARegistrations/${reg.Id}/RetrievableAccounts`,
1330
+ method: "GET",
1331
+ headers: { Accept: "application/json" }
1332
+ });
1333
+ if (acctResponse.status !== 200) throw ApiError.fromResponse(acctResponse.status, acctResponse.body);
1334
+ accounts.push(...JSON.parse(acctResponse.body));
1335
+ }
1336
+ return accounts;
1292
1337
  }
1293
1338
  /**
1294
1339
  * Set a password on a managed account via A2A.
@@ -1298,7 +1343,7 @@ var A2AClient = class {
1298
1343
  async setPassword(apiKey, password) {
1299
1344
  this.#ensureHttpClient();
1300
1345
  const response = await this.#httpClient.request({
1301
- url: `https://${this.#host}/service/a2a/${this.#apiVersion}/Credentials?type=Password`,
1346
+ url: `https://${this.#host}/service/a2a/${this.#apiVersion}/Credentials/Password`,
1302
1347
  method: "PUT",
1303
1348
  headers: {
1304
1349
  Authorization: `A2A ${apiKey}`,
@@ -1318,13 +1363,11 @@ var A2AClient = class {
1318
1363
  */
1319
1364
  async setPrivateKey(apiKey, key, passphrase, format) {
1320
1365
  this.#ensureHttpClient();
1321
- const payload = {
1322
- PrivateKey: key,
1323
- KeyFormat: format ?? "OpenSsh"
1324
- };
1366
+ const keyFormat = format ?? "OpenSsh";
1367
+ const payload = { PrivateKey: key };
1325
1368
  if (passphrase) payload["Passphrase"] = passphrase;
1326
1369
  const response = await this.#httpClient.request({
1327
- url: `https://${this.#host}/service/a2a/${this.#apiVersion}/Credentials?type=SshKey`,
1370
+ url: `https://${this.#host}/service/a2a/${this.#apiVersion}/Credentials/SshKey?keyFormat=${keyFormat}`,
1328
1371
  method: "PUT",
1329
1372
  headers: {
1330
1373
  Authorization: `A2A ${apiKey}`,