@baukit/integrations-client 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Patrick Koss
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # `@baukit/integrations-client`
2
+
3
+ `@baukit/integrations-client` provides provider-neutral client behavior for integration settings:
4
+
5
+ - a connection reducer with fixed states and actions;
6
+ - an OAuth coordinator with persisted state checks and strict return URL validation; and
7
+ - a typed provider registry built from product data.
8
+
9
+ The package has no runtime dependencies. Its root entry does not import React, Expo, or browser
10
+ globals.
11
+
12
+ ## Connection state
13
+
14
+ Map the server's connection health before applying client events:
15
+
16
+ ```ts
17
+ import { connectionStateFromServer, reduceConnectionState } from '@baukit/integrations-client';
18
+
19
+ const state = connectionStateFromServer({
20
+ state: 'needs_reconnect',
21
+ diagnosticCode: 'authorization_revoked',
22
+ providerDiagnostic: upstreamError,
23
+ });
24
+ const connecting = reduceConnectionState(state, { type: 'connect_requested' });
25
+ ```
26
+
27
+ `providerDiagnostic` is untrusted input and is always dropped. `diagnostic.code` accepts only a
28
+ bounded snake_case code. Products map `status` and `availableActions` to their own localized copy.
29
+ They must not render the diagnostic code.
30
+
31
+ ## OAuth sessions
32
+
33
+ Create one coordinator with product-owned ports for storage, timers, nonce creation, and the web or
34
+ native authorization runner. The coordinator saves the nonce before it opens the authorization URL.
35
+ It returns `success`, `cancelled`, `timed_out`, `invalid_return`, or `storage_mismatch`.
36
+
37
+ ```ts
38
+ import { OAuthSessionCoordinator, createReturnUrlValidator } from '@baukit/integrations-client';
39
+
40
+ const coordinator = new OAuthSessionCoordinator({
41
+ storage,
42
+ clock,
43
+ timeoutMs: 15_000,
44
+ createStateNonce,
45
+ validateReturnUrl: createReturnUrlValidator({
46
+ origin: 'https://app.example',
47
+ paths: ['/settings/integrations/callback'],
48
+ }),
49
+ browser,
50
+ native,
51
+ redirect,
52
+ });
53
+
54
+ const outcome = await coordinator.authorize({
55
+ platform: 'web',
56
+ returnUrl: 'https://app.example/settings/integrations/callback',
57
+ createAuthorizationUrl: startConnection,
58
+ });
59
+ ```
60
+
61
+ On web, `browser.openPlaceholder` runs before any asynchronous work so a click can reserve the
62
+ popup. If it returns `null`, the coordinator uses the injected redirect handler. After a full-page
63
+ return, call `handleRedirect` with the current URL. Native products supply `native.run` and
64
+ `native.cancel` using their auth-session library.
65
+
66
+ The validator requires the exact configured origin and one exact allowlisted path. Callback checks
67
+ reject lookalike origins, path prefixes, credentials in URLs, malformed URLs, and duplicate
68
+ parameters.
69
+
70
+ ## Provider registry
71
+
72
+ The product owns provider IDs, translation keys, and capabilities:
73
+
74
+ ```ts
75
+ import { createProviderRegistry } from '@baukit/integrations-client';
76
+
77
+ const providers = createProviderRegistry([
78
+ {
79
+ id: 'calendar',
80
+ labelKey: 'integrations.calendar',
81
+ capabilities: ['read_events'],
82
+ connection: state,
83
+ },
84
+ ] as const);
85
+ ```
86
+
87
+ The registry copies caller-owned arrays, rejects duplicate IDs, and exposes each provider's current
88
+ state and available actions. Persistence, provider OAuth parameters, identity, copy, and policy stay
89
+ in the product.
@@ -0,0 +1,44 @@
1
+ export type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'needs_reconnect' | 'disconnecting' | 'revocation_pending' | 'error';
2
+ export type ConnectionAction = 'connect' | 'reconnect' | 'disconnect' | 'retry';
3
+ export type ConnectionOperation = 'connect' | 'reconnect' | 'disconnect';
4
+ export type ServerConnectionState = 'disconnected' | 'active' | 'connected' | 'healthy' | 'degraded' | 'needs_reconnect' | 'failed' | 'revoked' | 'pending_revocation';
5
+ export interface ConnectionDiagnostic {
6
+ readonly code: string;
7
+ readonly source: 'client' | 'server';
8
+ }
9
+ export interface ConnectionState {
10
+ readonly status: ConnectionStatus;
11
+ readonly availableActions: readonly ConnectionAction[];
12
+ readonly diagnostic?: ConnectionDiagnostic;
13
+ readonly operation?: ConnectionOperation;
14
+ }
15
+ export interface ServerConnectionSnapshot {
16
+ readonly state: ServerConnectionState;
17
+ readonly diagnosticCode?: string | null;
18
+ readonly providerDiagnostic?: unknown;
19
+ }
20
+ export type ConnectionEvent = {
21
+ readonly type: 'server_state';
22
+ readonly snapshot: ServerConnectionSnapshot;
23
+ } | {
24
+ readonly type: 'connect_requested';
25
+ } | {
26
+ readonly type: 'auth_started';
27
+ } | {
28
+ readonly type: 'auth_returned';
29
+ } | {
30
+ readonly type: 'auth_cancelled';
31
+ } | {
32
+ readonly type: 'auth_timed_out';
33
+ readonly diagnosticCode?: string;
34
+ } | {
35
+ readonly type: 'disconnect_requested';
36
+ } | {
37
+ readonly type: 'revocation_pending';
38
+ readonly diagnosticCode?: string;
39
+ } | {
40
+ readonly type: 'retry';
41
+ };
42
+ export declare function connectionStateFromServer(snapshot: ServerConnectionSnapshot): ConnectionState;
43
+ export declare function reduceConnectionState(state: ConnectionState, event: ConnectionEvent): ConnectionState;
44
+ //# sourceMappingURL=connection-health.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-health.d.ts","sourceRoot":"","sources":["../src/connection-health.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GACxB,cAAc,GACd,YAAY,GACZ,WAAW,GACX,iBAAiB,GACjB,eAAe,GACf,oBAAoB,GACpB,OAAO,CAAC;AAEZ,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,WAAW,GAAG,YAAY,GAAG,OAAO,CAAC;AAEhF,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,WAAW,GAAG,YAAY,CAAC;AAEzE,MAAM,MAAM,qBAAqB,GAC7B,cAAc,GACd,QAAQ,GACR,WAAW,GACX,SAAS,GACT,UAAU,GACV,iBAAiB,GACjB,QAAQ,GACR,SAAS,GACT,oBAAoB,CAAC;AAEzB,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAC;CACtC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,gBAAgB,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACvD,QAAQ,CAAC,UAAU,CAAC,EAAE,oBAAoB,CAAC;IAC3C,QAAQ,CAAC,SAAS,CAAC,EAAE,mBAAmB,CAAC;CAC1C;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,KAAK,EAAE,qBAAqB,CAAC;IACtC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;CACvC;AAED,MAAM,MAAM,eAAe,GACvB;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,wBAAwB,CAAA;CAAE,GAC9E;IAAE,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAA;CAAE,GACtC;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAA;CAAE,GACjC;IAAE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAA;CAAE,GAClC;IAAE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAA;CAAE,GACnC;IAAE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GACrE;IAAE,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAA;CAAE,GACzC;IAAE,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GACzE;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAe/B,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,wBAAwB,GAAG,eAAe,CAkB7F;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,eAAe,EACtB,KAAK,EAAE,eAAe,GACrB,eAAe,CA2CjB"}
@@ -0,0 +1,100 @@
1
+ const MAX_DIAGNOSTIC_CODE_LENGTH = 128;
2
+ const DIAGNOSTIC_CODE_PATTERN = /^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$/;
3
+ const ACTIONS = {
4
+ disconnected: ['connect'],
5
+ connecting: [],
6
+ connected: ['disconnect'],
7
+ needs_reconnect: ['reconnect'],
8
+ disconnecting: [],
9
+ revocation_pending: ['retry'],
10
+ error: ['retry'],
11
+ };
12
+ export function connectionStateFromServer(snapshot) {
13
+ const diagnostic = serverDiagnostic(snapshot.diagnosticCode);
14
+ switch (snapshot.state) {
15
+ case 'active':
16
+ case 'connected':
17
+ case 'healthy':
18
+ return makeConnectionState('connected');
19
+ case 'needs_reconnect':
20
+ case 'revoked':
21
+ return makeConnectionState('needs_reconnect', { diagnostic, operation: 'reconnect' });
22
+ case 'degraded':
23
+ case 'failed':
24
+ return makeConnectionState('error', { diagnostic, operation: 'reconnect' });
25
+ case 'pending_revocation':
26
+ return makeConnectionState('revocation_pending', { diagnostic, operation: 'disconnect' });
27
+ case 'disconnected':
28
+ return makeConnectionState('disconnected');
29
+ }
30
+ }
31
+ export function reduceConnectionState(state, event) {
32
+ switch (event.type) {
33
+ case 'server_state':
34
+ return connectionStateFromServer(event.snapshot);
35
+ case 'connect_requested': {
36
+ const action = state.status === 'needs_reconnect' ? 'reconnect' : 'connect';
37
+ if (!state.availableActions.includes(action))
38
+ return state;
39
+ return makeConnectionState('connecting', { operation: action });
40
+ }
41
+ case 'auth_started':
42
+ return state.status === 'connecting'
43
+ ? state
44
+ : makeConnectionState('connecting', {
45
+ operation: state.status === 'needs_reconnect' ? 'reconnect' : 'connect',
46
+ });
47
+ case 'auth_returned':
48
+ return makeConnectionState('connected');
49
+ case 'auth_cancelled':
50
+ return state.operation === 'reconnect'
51
+ ? makeConnectionState('needs_reconnect', { operation: 'reconnect' })
52
+ : makeConnectionState('disconnected');
53
+ case 'auth_timed_out':
54
+ return makeConnectionState('error', {
55
+ diagnostic: clientDiagnostic(event.diagnosticCode ?? 'oauth_timed_out'),
56
+ operation: state.operation === 'reconnect' ? 'reconnect' : 'connect',
57
+ });
58
+ case 'disconnect_requested':
59
+ return state.availableActions.includes('disconnect')
60
+ ? makeConnectionState('disconnecting', { operation: 'disconnect' })
61
+ : state;
62
+ case 'revocation_pending':
63
+ return makeConnectionState('revocation_pending', {
64
+ diagnostic: clientDiagnostic(event.diagnosticCode ?? 'revocation_pending'),
65
+ operation: 'disconnect',
66
+ });
67
+ case 'retry':
68
+ if (!state.availableActions.includes('retry'))
69
+ return state;
70
+ return state.operation === 'disconnect'
71
+ ? makeConnectionState('disconnecting', { operation: 'disconnect' })
72
+ : makeConnectionState('connecting', {
73
+ operation: state.operation === 'reconnect' ? 'reconnect' : 'connect',
74
+ });
75
+ }
76
+ }
77
+ function makeConnectionState(status, options = {}) {
78
+ return {
79
+ status,
80
+ availableActions: ACTIONS[status],
81
+ ...(options.diagnostic === undefined ? {} : { diagnostic: options.diagnostic }),
82
+ ...(options.operation === undefined ? {} : { operation: options.operation }),
83
+ };
84
+ }
85
+ function serverDiagnostic(code) {
86
+ return diagnostic(code, 'server');
87
+ }
88
+ function clientDiagnostic(code) {
89
+ return diagnostic(code, 'client');
90
+ }
91
+ function diagnostic(code, source) {
92
+ if (code === null ||
93
+ code === undefined ||
94
+ code.length > MAX_DIAGNOSTIC_CODE_LENGTH ||
95
+ !DIAGNOSTIC_CODE_PATTERN.test(code)) {
96
+ return undefined;
97
+ }
98
+ return { code, source };
99
+ }
100
+ //# sourceMappingURL=connection-health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-health.js","sourceRoot":"","sources":["../src/connection-health.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export * from './connection-health.js';
2
+ export * from './oauth-session.js';
3
+ export * from './provider-registry.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from './connection-health.js';
2
+ export * from './oauth-session.js';
3
+ export * from './provider-registry.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC"}
@@ -0,0 +1,98 @@
1
+ export interface OAuthInFlightSession {
2
+ readonly stateNonce: string;
3
+ readonly returnUrl: string;
4
+ readonly createdAt: number;
5
+ }
6
+ export interface OAuthSessionStorage {
7
+ load(): Promise<OAuthInFlightSession | null>;
8
+ save(session: OAuthInFlightSession): Promise<void>;
9
+ clear(): Promise<void>;
10
+ }
11
+ export interface OAuthClock {
12
+ now(): number;
13
+ setTimeout(callback: () => void, timeoutMs: number): unknown;
14
+ clearTimeout(handle: unknown): void;
15
+ }
16
+ export type OAuthSessionResult = {
17
+ readonly type: 'success';
18
+ readonly returnUrl: string;
19
+ } | {
20
+ readonly type: 'cancelled';
21
+ };
22
+ export interface BrowserPopupSession {
23
+ readonly result: Promise<OAuthSessionResult>;
24
+ navigate(authorizationUrl: string): void;
25
+ close(): void;
26
+ }
27
+ export interface BrowserPopupOpener {
28
+ openPlaceholder(input: {
29
+ readonly placeholderUrl: string;
30
+ readonly returnUrl: string;
31
+ readonly windowName: string;
32
+ }): BrowserPopupSession | null;
33
+ }
34
+ export interface NativeAuthSessionRunner {
35
+ run(input: {
36
+ readonly authorizationUrl: string;
37
+ readonly returnUrl: string;
38
+ }): Promise<OAuthSessionResult>;
39
+ cancel(): void;
40
+ }
41
+ export interface OAuthRedirectHandler {
42
+ authorize(input: {
43
+ readonly authorizationUrl: string;
44
+ readonly returnUrl: string;
45
+ }): Promise<OAuthSessionResult>;
46
+ }
47
+ export interface OAuthReturnUrlPolicy {
48
+ readonly origin: string;
49
+ readonly paths: readonly string[];
50
+ }
51
+ export type OAuthReturnUrlValidator = (returnUrl: string) => boolean;
52
+ export type OAuthSessionOutcome = {
53
+ readonly type: 'success';
54
+ readonly callbackParams: Readonly<Record<string, string>>;
55
+ } | {
56
+ readonly type: 'cancelled';
57
+ } | {
58
+ readonly type: 'timed_out';
59
+ } | {
60
+ readonly type: 'invalid_return';
61
+ } | {
62
+ readonly type: 'storage_mismatch';
63
+ };
64
+ export interface OAuthSessionCoordinatorOptions {
65
+ readonly storage: OAuthSessionStorage;
66
+ readonly clock: OAuthClock;
67
+ readonly timeoutMs: number;
68
+ readonly createStateNonce: () => string;
69
+ readonly validateReturnUrl: OAuthReturnUrlValidator;
70
+ readonly browser?: BrowserPopupOpener;
71
+ readonly native?: NativeAuthSessionRunner;
72
+ readonly redirect?: OAuthRedirectHandler;
73
+ readonly placeholderUrl?: string;
74
+ readonly windowName?: string;
75
+ }
76
+ export interface OAuthAuthorizationRequest {
77
+ readonly platform: 'web' | 'native';
78
+ readonly returnUrl: string;
79
+ readonly createAuthorizationUrl: (input: {
80
+ readonly stateNonce: string;
81
+ readonly returnUrl: string;
82
+ }) => Promise<string>;
83
+ }
84
+ export declare class OAuthSessionCoordinator {
85
+ #private;
86
+ constructor(options: OAuthSessionCoordinatorOptions);
87
+ authorize(request: OAuthAuthorizationRequest): Promise<OAuthSessionOutcome>;
88
+ handleRedirect(returnUrl: string): Promise<OAuthSessionOutcome>;
89
+ discardStaleSession(): Promise<boolean>;
90
+ private openBrowserPopup;
91
+ private performAuthorization;
92
+ private completeReturn;
93
+ private isStale;
94
+ private clearIfCurrent;
95
+ private withTimeout;
96
+ }
97
+ export declare function createReturnUrlValidator(policy: OAuthReturnUrlPolicy): OAuthReturnUrlValidator;
98
+ //# sourceMappingURL=oauth-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-session.d.ts","sourceRoot":"","sources":["../src/oauth-session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,204 @@
1
+ const DEFAULT_PLACEHOLDER_URL = 'about:blank';
2
+ const DEFAULT_WINDOW_NAME = 'baukit-integration-oauth';
3
+ const TIMED_OUT = Symbol('timed_out');
4
+ export class OAuthSessionCoordinator {
5
+ #options;
6
+ constructor(options) {
7
+ if (!Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0) {
8
+ throw new TypeError('OAuth timeout must be a positive finite number.');
9
+ }
10
+ this.#options = options;
11
+ }
12
+ async authorize(request) {
13
+ if (!this.#options.validateReturnUrl(request.returnUrl))
14
+ return { type: 'invalid_return' };
15
+ const stateNonce = this.#options.createStateNonce();
16
+ if (stateNonce.length === 0)
17
+ throw new TypeError('OAuth state nonce must not be empty.');
18
+ const popup = request.platform === 'web' ? this.openBrowserPopup(request.returnUrl) : null;
19
+ const operation = this.performAuthorization(request, stateNonce, popup);
20
+ const result = await this.withTimeout(operation, () => {
21
+ popup?.close();
22
+ if (request.platform === 'native')
23
+ this.#options.native?.cancel();
24
+ });
25
+ if (result === TIMED_OUT) {
26
+ await this.clearIfCurrent(stateNonce);
27
+ return { type: 'timed_out' };
28
+ }
29
+ return result;
30
+ }
31
+ async handleRedirect(returnUrl) {
32
+ return this.completeReturn(returnUrl);
33
+ }
34
+ async discardStaleSession() {
35
+ const stored = await this.#options.storage.load();
36
+ if (stored === null || !this.isStale(stored))
37
+ return false;
38
+ await this.#options.storage.clear();
39
+ return true;
40
+ }
41
+ openBrowserPopup(returnUrl) {
42
+ const browser = this.#options.browser;
43
+ if (browser === undefined)
44
+ return null;
45
+ return browser.openPlaceholder({
46
+ placeholderUrl: this.#options.placeholderUrl ?? DEFAULT_PLACEHOLDER_URL,
47
+ returnUrl,
48
+ windowName: this.#options.windowName ?? DEFAULT_WINDOW_NAME,
49
+ });
50
+ }
51
+ async performAuthorization(request, stateNonce, popup) {
52
+ await this.discardStaleSession();
53
+ await this.#options.storage.save({
54
+ stateNonce,
55
+ returnUrl: request.returnUrl,
56
+ createdAt: this.#options.clock.now(),
57
+ });
58
+ const authorizationUrl = await request.createAuthorizationUrl({
59
+ stateNonce,
60
+ returnUrl: request.returnUrl,
61
+ });
62
+ let sessionResult;
63
+ if (request.platform === 'native') {
64
+ const native = this.#options.native;
65
+ if (native === undefined)
66
+ throw new TypeError('Native OAuth runner is required.');
67
+ sessionResult = await native.run({ authorizationUrl, returnUrl: request.returnUrl });
68
+ }
69
+ else if (popup !== null) {
70
+ popup.navigate(authorizationUrl);
71
+ sessionResult = await popup.result;
72
+ }
73
+ else {
74
+ const redirect = this.#options.redirect;
75
+ if (redirect === undefined)
76
+ throw new TypeError('Browser OAuth handler is required.');
77
+ sessionResult = await redirect.authorize({
78
+ authorizationUrl,
79
+ returnUrl: request.returnUrl,
80
+ });
81
+ }
82
+ if (sessionResult.type === 'cancelled') {
83
+ await this.clearIfCurrent(stateNonce);
84
+ return { type: 'cancelled' };
85
+ }
86
+ return this.completeReturn(sessionResult.returnUrl, stateNonce);
87
+ }
88
+ async completeReturn(returnUrl, expectedStateNonce) {
89
+ if (!this.#options.validateReturnUrl(returnUrl))
90
+ return { type: 'invalid_return' };
91
+ const callbackParams = parseCallbackParams(returnUrl);
92
+ if (callbackParams === null)
93
+ return { type: 'invalid_return' };
94
+ const stored = await this.#options.storage.load();
95
+ if (stored === null)
96
+ return { type: 'storage_mismatch' };
97
+ if (this.isStale(stored)) {
98
+ await this.#options.storage.clear();
99
+ return { type: 'timed_out' };
100
+ }
101
+ if ((expectedStateNonce !== undefined && stored.stateNonce !== expectedStateNonce) ||
102
+ callbackParams['state'] !== stored.stateNonce) {
103
+ return { type: 'storage_mismatch' };
104
+ }
105
+ if (!sameReturnEndpoint(stored.returnUrl, returnUrl))
106
+ return { type: 'invalid_return' };
107
+ await this.#options.storage.clear();
108
+ return { type: 'success', callbackParams };
109
+ }
110
+ isStale(session) {
111
+ const elapsed = this.#options.clock.now() - session.createdAt;
112
+ return elapsed < 0 || elapsed >= this.#options.timeoutMs;
113
+ }
114
+ async clearIfCurrent(stateNonce) {
115
+ const stored = await this.#options.storage.load();
116
+ if (stored?.stateNonce === stateNonce)
117
+ await this.#options.storage.clear();
118
+ }
119
+ async withTimeout(operation, onTimeout) {
120
+ let timer;
121
+ const timeout = new Promise((resolve) => {
122
+ timer = this.#options.clock.setTimeout(() => {
123
+ try {
124
+ onTimeout();
125
+ }
126
+ finally {
127
+ resolve(TIMED_OUT);
128
+ }
129
+ }, this.#options.timeoutMs);
130
+ });
131
+ try {
132
+ return await Promise.race([operation, timeout]);
133
+ }
134
+ finally {
135
+ this.#options.clock.clearTimeout(timer);
136
+ }
137
+ }
138
+ }
139
+ export function createReturnUrlValidator(policy) {
140
+ const origin = parseOrigin(policy.origin);
141
+ const paths = new Set(policy.paths.map(normalizeAllowedPath));
142
+ if (paths.size === 0)
143
+ throw new TypeError('At least one OAuth return path is required.');
144
+ return (returnUrl) => {
145
+ const parsed = parseUrl(returnUrl);
146
+ return (parsed !== null &&
147
+ parsed.username === '' &&
148
+ parsed.password === '' &&
149
+ exactOrigin(parsed) === origin &&
150
+ paths.has(parsed.pathname));
151
+ };
152
+ }
153
+ function parseOrigin(origin) {
154
+ const parsed = parseUrl(origin);
155
+ if (parsed?.username !== '' ||
156
+ parsed.password !== '' ||
157
+ (parsed.pathname !== '' && parsed.pathname !== '/') ||
158
+ parsed.search !== '' ||
159
+ parsed.hash !== '') {
160
+ throw new TypeError('OAuth return origin must contain only a scheme and authority.');
161
+ }
162
+ return exactOrigin(parsed);
163
+ }
164
+ function normalizeAllowedPath(path) {
165
+ if (!path.startsWith('/') || path.includes('?') || path.includes('#')) {
166
+ throw new TypeError('OAuth return paths must be absolute paths without a query or fragment.');
167
+ }
168
+ const parsed = new URL(path, 'https://baukit.invalid');
169
+ if (parsed.pathname !== path)
170
+ throw new TypeError('OAuth return paths must be normalized.');
171
+ return path;
172
+ }
173
+ function parseCallbackParams(returnUrl) {
174
+ const parsed = parseUrl(returnUrl);
175
+ if (parsed === null)
176
+ return null;
177
+ const values = {};
178
+ for (const [key, value] of parsed.searchParams) {
179
+ if (Object.hasOwn(values, key))
180
+ return null;
181
+ values[key] = value;
182
+ }
183
+ return Object.freeze(values);
184
+ }
185
+ function sameReturnEndpoint(expected, actual) {
186
+ const expectedUrl = parseUrl(expected);
187
+ const actualUrl = parseUrl(actual);
188
+ return (expectedUrl !== null &&
189
+ actualUrl !== null &&
190
+ exactOrigin(expectedUrl) === exactOrigin(actualUrl) &&
191
+ expectedUrl.pathname === actualUrl.pathname);
192
+ }
193
+ function exactOrigin(url) {
194
+ return url.origin === 'null' ? `${url.protocol}//${url.host}` : url.origin;
195
+ }
196
+ function parseUrl(value) {
197
+ try {
198
+ return new URL(value);
199
+ }
200
+ catch {
201
+ return null;
202
+ }
203
+ }
204
+ //# sourceMappingURL=oauth-session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-session.js","sourceRoot":"","sources":["../src/oauth-session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { ConnectionAction, ConnectionState, ConnectionStatus } from './connection-health.js';
2
+ export interface ProviderRegistration<ProviderId extends string, LabelKey extends string, Capability extends string> {
3
+ readonly id: ProviderId;
4
+ readonly labelKey: LabelKey;
5
+ readonly capabilities: readonly Capability[];
6
+ readonly connection: ConnectionState;
7
+ }
8
+ export interface RegisteredProvider<ProviderId extends string, LabelKey extends string, Capability extends string> {
9
+ readonly id: ProviderId;
10
+ readonly labelKey: LabelKey;
11
+ readonly capabilities: readonly Capability[];
12
+ readonly currentState: ConnectionStatus;
13
+ readonly availableActions: readonly ConnectionAction[];
14
+ }
15
+ export declare class ProviderRegistry<ProviderId extends string, LabelKey extends string, Capability extends string> {
16
+ #private;
17
+ constructor(registrations: readonly ProviderRegistration<ProviderId, LabelKey, Capability>[]);
18
+ list(): readonly RegisteredProvider<ProviderId, LabelKey, Capability>[];
19
+ get(providerId: ProviderId): RegisteredProvider<ProviderId, LabelKey, Capability> | undefined;
20
+ }
21
+ export declare function createProviderRegistry<ProviderId extends string, LabelKey extends string, Capability extends string>(registrations: readonly ProviderRegistration<ProviderId, LabelKey, Capability>[]): ProviderRegistry<ProviderId, LabelKey, Capability>;
22
+ //# sourceMappingURL=provider-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-registry.d.ts","sourceRoot":"","sources":["../src/provider-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAElG,MAAM,WAAW,oBAAoB,CACnC,UAAU,SAAS,MAAM,EACzB,QAAQ,SAAS,MAAM,EACvB,UAAU,SAAS,MAAM;IAEzB,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,SAAS,UAAU,EAAE,CAAC;IAC7C,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC;CACtC;AAED,MAAM,WAAW,kBAAkB,CACjC,UAAU,SAAS,MAAM,EACzB,QAAQ,SAAS,MAAM,EACvB,UAAU,SAAS,MAAM;IAEzB,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,SAAS,UAAU,EAAE,CAAC;IAC7C,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC;IACxC,QAAQ,CAAC,gBAAgB,EAAE,SAAS,gBAAgB,EAAE,CAAC;CACxD;AAED,qBAAa,gBAAgB,CAC3B,UAAU,SAAS,MAAM,EACzB,QAAQ,SAAS,MAAM,EACvB,UAAU,SAAS,MAAM;;gBAMvB,aAAa,EAAE,SAAS,oBAAoB,CAAC,UAAU,EAAE,QAAQ,EAAE,UAAU,CAAC,EAAE;IAiB3E,IAAI,IAAI,SAAS,kBAAkB,CAAC,UAAU,EAAE,QAAQ,EAAE,UAAU,CAAC,EAAE;IAIvE,GAAG,CACR,UAAU,EAAE,UAAU,GACrB,kBAAkB,CAAC,UAAU,EAAE,QAAQ,EAAE,UAAU,CAAC,GAAG,SAAS;CAGpE;AAED,wBAAgB,sBAAsB,CACpC,UAAU,SAAS,MAAM,EACzB,QAAQ,SAAS,MAAM,EACvB,UAAU,SAAS,MAAM,EAEzB,aAAa,EAAE,SAAS,oBAAoB,CAAC,UAAU,EAAE,QAAQ,EAAE,UAAU,CAAC,EAAE,GAC/E,gBAAgB,CAAC,UAAU,EAAE,QAAQ,EAAE,UAAU,CAAC,CAEpD"}
@@ -0,0 +1,28 @@
1
+ export class ProviderRegistry {
2
+ #providers;
3
+ #byId;
4
+ constructor(registrations) {
5
+ const providers = registrations.map((registration) => Object.freeze({
6
+ id: registration.id,
7
+ labelKey: registration.labelKey,
8
+ capabilities: Object.freeze([...registration.capabilities]),
9
+ currentState: registration.connection.status,
10
+ availableActions: Object.freeze([...registration.connection.availableActions]),
11
+ }));
12
+ const byId = new Map(providers.map((provider) => [provider.id, provider]));
13
+ if (byId.size !== providers.length)
14
+ throw new TypeError('Provider IDs must be unique.');
15
+ this.#providers = Object.freeze(providers);
16
+ this.#byId = byId;
17
+ }
18
+ list() {
19
+ return this.#providers;
20
+ }
21
+ get(providerId) {
22
+ return this.#byId.get(providerId);
23
+ }
24
+ }
25
+ export function createProviderRegistry(registrations) {
26
+ return new ProviderRegistry(registrations);
27
+ }
28
+ //# sourceMappingURL=provider-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-registry.js","sourceRoot":"","sources":["../src/provider-registry.ts"],"names":[],"mappings":"AAyBA,MAAM,OAAO,gBAAgB;IAKlB,UAAU,CAAkE;IAC5E,KAAK,CAAgF;IAE9F,YACE,aAAgF;QAEhF,MAAM,SAAS,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,EAAE,CACnD,MAAM,CAAC,MAAM,CAAC;YACZ,EAAE,EAAE,YAAY,CAAC,EAAE;YACnB,QAAQ,EAAE,YAAY,CAAC,QAAQ;YAC/B,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;YAC3D,YAAY,EAAE,YAAY,CAAC,UAAU,CAAC,MAAM;YAC5C,gBAAgB,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,YAAY,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;SAC/E,CAAC,CACH,CAAC;QACF,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;QAC3E,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,MAAM;YAAE,MAAM,IAAI,SAAS,CAAC,8BAA8B,CAAC,CAAC;QACxF,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC3C,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACpB,CAAC;IAEM,IAAI;QACT,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAEM,GAAG,CACR,UAAsB;QAEtB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACpC,CAAC;CACF;AAED,MAAM,UAAU,sBAAsB,CAKpC,aAAgF;IAEhF,OAAO,IAAI,gBAAgB,CAAC,aAAa,CAAC,CAAC;AAC7C,CAAC"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@baukit/integrations-client",
3
+ "version": "0.2.0",
4
+ "description": "Provider-neutral connection state, OAuth session coordination, and provider registration for web and native clients.",
5
+ "license": "MIT",
6
+ "author": "Patrick Koss",
7
+ "homepage": "https://github.com/PatrickKoss/baukit#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/PatrickKoss/baukit.git",
11
+ "directory": "typescript/packages/integrations-client"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/PatrickKoss/baukit/issues"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "type": "module",
20
+ "engines": {
21
+ "node": ">=24.0.0"
22
+ },
23
+ "types": "./dist/index.d.ts",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js"
28
+ }
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "README.md",
33
+ "LICENSE"
34
+ ],
35
+ "scripts": {
36
+ "build": "tsc -p tsconfig.json",
37
+ "prepare": "pnpm run build",
38
+ "test": "vitest run --config ../../vitest.config.ts",
39
+ "lint": "eslint src --max-warnings=0",
40
+ "format:check": "prettier --check src package.json tsconfig.json"
41
+ },
42
+ "devDependencies": {
43
+ "typescript": "6.0.3",
44
+ "vitest": "4.1.10"
45
+ }
46
+ }