@ikonai/sdk-react-ui 1.3.0 → 1.3.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.
@@ -28,6 +28,14 @@ declare global {
28
28
  * Module registration function type.
29
29
  */
30
30
  export type IkonUiModuleRegistration = (registry: IkonUiRegistry) => void;
31
+ /**
32
+ * Connect parameters for one session: the query string, then whatever the app resolved itself.
33
+ *
34
+ * The app wins on a shared key. An app derives its parameters from something the visitor does not
35
+ * control — the hostname it was served on, a build-time flag — and those choices decide which
36
+ * SessionIdentity the connect resolves to, so a hand-typed query parameter must not displace one.
37
+ */
38
+ export declare function mergeConnectParameters(urlParameters: Record<string, string>, appParameters?: Record<string, string>): Record<string, string>;
31
39
  /**
32
40
  * Options for the useIkonApp hook.
33
41
  */
@@ -43,6 +51,18 @@ export interface UseIkonAppOptions {
43
51
  * modules: [registerStandardUiModule]
44
52
  */
45
53
  modules?: IkonUiModuleRegistration[];
54
+ /**
55
+ * Connect parameters the app supplies itself, merged over the ones parsed from the URL.
56
+ *
57
+ * They reach the app twice: as the `params` the backend hashes into SessionIdentity, and as
58
+ * `Context.Parameters` on each client. Without this, the only way to steer SessionIdentity is to
59
+ * put a value in the address bar — which a frontend deriving state from its own environment (the
60
+ * hostname it was served on, a build-time flag) cannot do without rewriting the URL.
61
+ *
62
+ * App-supplied keys win over the same key in the query string, so a hand-typed parameter cannot
63
+ * override what the app resolved.
64
+ */
65
+ parameters?: Record<string, string>;
46
66
  /**
47
67
  * Timeout configuration passed to IkonClient.
48
68
  * If not provided, SDK defaults are used.
@@ -1,5 +1,5 @@
1
1
  import { AuthenticationResponseJSON, PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialRequestOptionsJSON, RegistrationResponseJSON } from '@simplewebauthn/browser';
2
- import { AuthSession, LoginMethod } from './types';
2
+ import { AuthSession, LoginMethod, RefreshableSession } from './types';
3
3
  /**
4
4
  * Result of parsing an OAuth callback from the URL.
5
5
  */
@@ -54,6 +54,31 @@ export declare function verifyLoginCode({ email, code, authUrl }: VerifyLoginCod
54
54
  * Returns the token and provider if present, or null if not an OAuth callback.
55
55
  */
56
56
  export declare function parseOAuthCallback(): OAuthCallbackResult | null;
57
+ /** The single-use code an opted-in callback carries, waiting to be exchanged for a session. */
58
+ export declare function parseOAuthCodeCallback(): {
59
+ code: string;
60
+ provider: LoginMethod;
61
+ } | null;
62
+ /**
63
+ * Trades the callback's code for the session it stands for, using the verifier this browser kept.
64
+ *
65
+ * A missing verifier is a real failure rather than something to paper over: it means this is not
66
+ * the browser (or not the tab) that started the sign-in — which is exactly the case PKCE exists to
67
+ * refuse — so the caller reports a failed sign-in instead of silently leaving the visitor
68
+ * anonymous with a code still in the address bar.
69
+ */
70
+ export declare function exchangeOAuthCode(code: string, provider: LoginMethod, authUrlOverride?: string): Promise<AuthSession>;
71
+ /**
72
+ * Renews the access token from the refresh cookie. Returns null when the session is definitively
73
+ * over — the chain was spent, revoked, or never existed — and throws when the answer is simply
74
+ * unknown, so a caller can tell "signed out" from "the network is down" and only act on the first.
75
+ *
76
+ * Deliberately not routed through `authFetch`. The cookie is issued only when the exchange was
77
+ * answered same-origin, so the same-origin base is the only one that can hold it; authFetch's
78
+ * cross-origin fallback would send a cookie-less renewal to the shared auth host, take the 401 for
79
+ * a dead session, and sign out someone whose session was never in trouble.
80
+ */
81
+ export declare function refreshBrowserSession(marker: RefreshableSession): Promise<AuthSession | null>;
57
82
  /**
58
83
  * Sign-in failure parsed from an OAuth callback redirect: the machine code from the `error` query
59
84
  * param, plus the provider the callback names when it knows which one failed.
@@ -98,7 +123,7 @@ export declare function clearOAuthParams(): void;
98
123
  /**
99
124
  * Build an OAuth redirect URL for the given provider.
100
125
  */
101
- export declare function buildOAuthRedirectUrl(provider: LoginMethod, spaceId: string, authUrl: string, returnUrl?: string): string;
126
+ export declare function buildOAuthRedirectUrl(provider: LoginMethod, spaceId: string, authUrl: string, returnUrl?: string): Promise<string>;
102
127
  /**
103
128
  * Check if passkey/WebAuthn is supported in the current browser.
104
129
  */
@@ -119,3 +144,13 @@ export declare function getPasskeyAuthenticationOptions(authUrl: string, email?:
119
144
  * Verify passkey authentication with the auth service.
120
145
  */
121
146
  export declare function verifyPasskeyAuthentication(authUrl: string, response: AuthenticationResponseJSON): Promise<AuthSession>;
147
+ /**
148
+ * Ends the session on the server as well as locally, so a copy of the token taken from the URL or
149
+ * from storage stops working instead of outliving the sign-out by up to a week.
150
+ *
151
+ * Best effort by design: logging out must succeed even offline, against an older backend with no
152
+ * such route, or when the request is simply slow. The local session is cleared by the caller
153
+ * regardless — a client that cannot reach the server ends up exactly where it was before this
154
+ * existed, which is also where app bundles shipping an older SDK copy stay.
155
+ */
156
+ export declare function revokeSessionOnServer(token: string, authUrlOverride?: string): Promise<void>;
package/auth/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export type { AuthConfig, AuthContextValue, AuthSession, AuthState, AuthUser, LoginMethod } from './types';
2
2
  export { clearAuthSession, isSignedInBootLikely, loadAuthSession, saveAuthSession, sessionToUser } from './storage';
3
- export { authenticateAnonymous, buildOAuthRedirectUrl, clearOAuthParams, describeAuthError, getPasskeyAuthenticationOptions, getPasskeyRegistrationOptions, isPasskeySupported, parseOAuthCallback, parseOAuthError, sendLoginCode, verifyLoginCode, verifyPasskeyAuthentication, verifyPasskeyRegistration, type OAuthCallbackResult, type OAuthErrorResult, type SendLoginCodeOptions, type VerifyLoginCodeOptions, } from './auth-service';
3
+ export { authenticateAnonymous, buildOAuthRedirectUrl, clearOAuthParams, describeAuthError, exchangeOAuthCode, getPasskeyAuthenticationOptions, getPasskeyRegistrationOptions, isPasskeySupported, parseOAuthCallback, parseOAuthCodeCallback, parseOAuthError, revokeSessionOnServer, sendLoginCode, verifyLoginCode, verifyPasskeyAuthentication, verifyPasskeyRegistration, type OAuthCallbackResult, type OAuthErrorResult, type SendLoginCodeOptions, type VerifyLoginCodeOptions, } from './auth-service';
4
4
  export { AuthProvider, useAuth, useAuthOptional, type AuthProviderProps } from './auth-context';
5
5
  export { useAuthGuard, type UseAuthGuardOptions, type UseAuthGuardResult } from './use-auth-guard';
6
+ export { OAUTH_CONTINUE_PARAM, captureOAuthContinuation, completeOAuthContinuation, hasPendingOAuthContinuation } from './oauth-continuation';
@@ -0,0 +1,36 @@
1
+ /** What `/ikon/oauth/authorize` puts on the app's own URL when it sends the browser here to sign in. */
2
+ export declare const OAUTH_CONTINUE_PARAM = "ikon-oauth";
3
+ /**
4
+ * The app-rendered half of a space's OAuth flow.
5
+ *
6
+ * `/authorize` cannot show a sign-in screen itself — the app owns that screen, its methods and its
7
+ * branding, and the person is being asked to grant access to *this app*, so this app is what they should
8
+ * be looking at while they decide. So it parks the request and bounces the browser back here with a
9
+ * pointer to it; the SDK raises the login prompt it already has; and a completed sign-in reports back.
10
+ *
11
+ * The pointer is kept in BOTH the URL and sessionStorage until the flow ends, and that redundancy is
12
+ * the point. Signing in with an external provider hands the browser to another origin and back, and
13
+ * what comes back is not always the same browsing context — a popup, a reopened tab, a session-storage
14
+ * partition we did not choose. Storage alone lost the pointer in exactly that case, and because the
15
+ * param had already been stripped there was nothing left to recover it from: the authorization sat
16
+ * parked, the client waited for a code forever, and the user saw an ordinary app with no sign of it.
17
+ *
18
+ * The URL copy is what survives, because the sign-in return URL is built from the current location.
19
+ * That is safe here for a reason this flow states explicitly elsewhere: the pointer is NOT a
20
+ * credential. It names a parked authorization, and the user binding is made server-side from the
21
+ * session token posted below, so a copied or referrer-leaked pointer authorizes nobody. It is cleared
22
+ * from both places the moment the flow finishes or fails.
23
+ */
24
+ export declare function captureOAuthContinuation(): boolean;
25
+ export declare function hasPendingOAuthContinuation(): boolean;
26
+ /**
27
+ * Report the completed sign-in and hand the browser to the consent screen.
28
+ *
29
+ * Only ever called after a REAL sign-in — `useAuthGuard` treats an anonymous session as not satisfying a
30
+ * prompt, which is the guest/global exclusion this flow needs and already had. A `global` visitor is one
31
+ * shared space-wide user, so authorizing as one would hand every client the same identity.
32
+ *
33
+ * Failure clears the pointer and returns false: the app then carries on as a normally signed-in session
34
+ * rather than trapping the user in a flow that cannot finish.
35
+ */
36
+ export declare function completeOAuthContinuation(): Promise<boolean>;
package/auth/storage.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AuthSession, AuthUser } from './types';
1
+ import { AuthSession, AuthUser, RefreshableSession } from './types';
2
2
  /**
3
3
  * Check whether a JWT token has expired by decoding its `exp` claim.
4
4
  * Returns true when the token is expired or cannot be decoded.
@@ -13,6 +13,30 @@ export declare function saveAuthSession(session: AuthSession): void;
13
13
  * Returns null if no session exists or if the session has expired.
14
14
  */
15
15
  export declare function loadAuthSession(): AuthSession | null;
16
+ /**
17
+ * Record that this browser has a refresh cookie behind the given sign-in. Replaces the stored
18
+ * session rather than joining it: the two describe the same thing, and a leftover token record
19
+ * would be preferred over the live one on the next load.
20
+ */
21
+ export declare function saveRefreshableSession(session: RefreshableSession): void;
22
+ /**
23
+ * The refresh-cookie marker, or null when there is none or it is old enough that the cookie behind
24
+ * it has certainly expired. Dropping a stale one is what stops a long-abandoned browser starting
25
+ * every load with a renewal that cannot succeed.
26
+ */
27
+ export declare function loadRefreshableSession(): RefreshableSession | null;
28
+ /** Move the marker's renewal stamp forward, mirroring the cookie the server just replaced. */
29
+ export declare function touchRefreshableSession(): void;
30
+ export declare function clearRefreshableSession(): void;
31
+ /**
32
+ * Drop a stored anonymous session — token record and refresh marker alike.
33
+ *
34
+ * For an app whose sign-in wall offers no not-signed-in entry. Such a session can only date from a
35
+ * time when one was offered, or from another app sharing the origin, and it must not be what walks
36
+ * a visitor through the wall. Discarded rather than ignored: a session the app will not honour has
37
+ * no business leaving a usable token in storage.
38
+ */
39
+ export declare function discardAnonymousSession(): void;
16
40
  /**
17
41
  * Pre-auth heuristic for "this load will connect as a signed-in user": a stored non-anonymous
18
42
  * session, or an OAuth callback in flight (the provider just redirected back with `ikon_token` in
package/auth/types.d.ts CHANGED
@@ -15,6 +15,12 @@ export interface AuthUser {
15
15
  provider: LoginMethod | 'anonymous' | 'dev';
16
16
  token: string;
17
17
  authenticatedAt: number;
18
+ /**
19
+ * When {@link token} stops being accepted, for the sign-ins whose token is short-lived enough to
20
+ * need renewing before then. Absent on the long-lived tokens, which are simply used until they
21
+ * expire.
22
+ */
23
+ expiresAt?: string;
18
24
  }
19
25
  /**
20
26
  * Authentication state.
@@ -64,6 +70,19 @@ export interface AuthSession {
64
70
  token: string;
65
71
  provider: LoginMethod | 'anonymous' | 'dev';
66
72
  authenticatedAt: number;
73
+ /** Set when the token is short-lived and a refresh cookie stands behind it. */
74
+ expiresAt?: string;
75
+ }
76
+ /**
77
+ * What is persisted for a sign-in whose access token is NOT persisted: enough to know on the next
78
+ * load that a session exists and whose it is, and nothing that can be used as a credential.
79
+ */
80
+ export interface RefreshableSession {
81
+ provider: LoginMethod | 'anonymous' | 'dev';
82
+ /** When the person actually signed in. Unchanged by renewals — those renew a token, not a login. */
83
+ authenticatedAt: number;
84
+ /** Last successful renewal, which is what the cookie's own lifetime runs from. */
85
+ renewedAt: number;
67
86
  }
68
87
  /**
69
88
  * Auth context value returned by useAuth hook.
@@ -37,6 +37,13 @@ export interface UseAuthGuardResult {
37
37
  * Dismiss an on-demand login prompt and return to the app. No effect on a hard sign-in wall.
38
38
  */
39
39
  dismissLoginPrompt: () => void;
40
+ /**
41
+ * Why the prompt was raised, when whoever raised it said. Render it: a sign-in screen that appears
42
+ * without explanation is one a person cannot judge. Someone sent here by an application asking to
43
+ * act on their behalf needs to know that is what they are agreeing to before they choose an account,
44
+ * not after.
45
+ */
46
+ loginPromptReason: string | null;
40
47
  }
41
48
  /**
42
49
  * Headless hook for route protection logic.