@ikonai/sdk-react-ui 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/app/use-ikon-app.d.ts +26 -0
  2. package/auth/auth-service.d.ts +102 -4
  3. package/auth/index.d.ts +2 -1
  4. package/auth/oauth-continuation.d.ts +28 -0
  5. package/auth/storage.d.ts +25 -1
  6. package/auth/types.d.ts +19 -0
  7. package/fonts/files/crimson-pro-latin-ext-italic.woff2 +0 -0
  8. package/fonts/files/crimson-pro-latin-ext-normal.woff2 +0 -0
  9. package/fonts/files/crimson-pro-latin-italic.woff2 +0 -0
  10. package/fonts/files/crimson-pro-latin-normal.woff2 +0 -0
  11. package/fonts/files/crimson-pro-vietnamese-italic.woff2 +0 -0
  12. package/fonts/files/crimson-pro-vietnamese-normal.woff2 +0 -0
  13. package/fonts/files/inter-cyrillic-ext-italic.woff2 +0 -0
  14. package/fonts/files/inter-cyrillic-ext-normal.woff2 +0 -0
  15. package/fonts/files/inter-cyrillic-italic.woff2 +0 -0
  16. package/fonts/files/inter-cyrillic-normal.woff2 +0 -0
  17. package/fonts/files/inter-greek-ext-italic.woff2 +0 -0
  18. package/fonts/files/inter-greek-ext-normal.woff2 +0 -0
  19. package/fonts/files/inter-greek-italic.woff2 +0 -0
  20. package/fonts/files/inter-greek-normal.woff2 +0 -0
  21. package/fonts/files/inter-latin-ext-italic.woff2 +0 -0
  22. package/fonts/files/inter-latin-ext-normal.woff2 +0 -0
  23. package/fonts/files/inter-latin-italic.woff2 +0 -0
  24. package/fonts/files/inter-latin-normal.woff2 +0 -0
  25. package/fonts/files/inter-vietnamese-italic.woff2 +0 -0
  26. package/fonts/files/inter-vietnamese-normal.woff2 +0 -0
  27. package/fonts/files/jetbrains-mono-cyrillic-ext-italic.woff2 +0 -0
  28. package/fonts/files/jetbrains-mono-cyrillic-ext-normal.woff2 +0 -0
  29. package/fonts/files/jetbrains-mono-cyrillic-italic.woff2 +0 -0
  30. package/fonts/files/jetbrains-mono-cyrillic-normal.woff2 +0 -0
  31. package/fonts/files/jetbrains-mono-greek-italic.woff2 +0 -0
  32. package/fonts/files/jetbrains-mono-greek-normal.woff2 +0 -0
  33. package/fonts/files/jetbrains-mono-latin-ext-italic.woff2 +0 -0
  34. package/fonts/files/jetbrains-mono-latin-ext-normal.woff2 +0 -0
  35. package/fonts/files/jetbrains-mono-latin-italic.woff2 +0 -0
  36. package/fonts/files/jetbrains-mono-latin-normal.woff2 +0 -0
  37. package/fonts/files/jetbrains-mono-vietnamese-italic.woff2 +0 -0
  38. package/fonts/files/jetbrains-mono-vietnamese-normal.woff2 +0 -0
  39. package/fonts/files/poppins-devanagari-400-italic.woff2 +0 -0
  40. package/fonts/files/poppins-devanagari-400-normal.woff2 +0 -0
  41. package/fonts/files/poppins-devanagari-500-italic.woff2 +0 -0
  42. package/fonts/files/poppins-devanagari-500-normal.woff2 +0 -0
  43. package/fonts/files/poppins-devanagari-600-italic.woff2 +0 -0
  44. package/fonts/files/poppins-devanagari-600-normal.woff2 +0 -0
  45. package/fonts/files/poppins-devanagari-700-italic.woff2 +0 -0
  46. package/fonts/files/poppins-devanagari-700-normal.woff2 +0 -0
  47. package/fonts/files/poppins-latin-400-italic.woff2 +0 -0
  48. package/fonts/files/poppins-latin-400-normal.woff2 +0 -0
  49. package/fonts/files/poppins-latin-500-italic.woff2 +0 -0
  50. package/fonts/files/poppins-latin-500-normal.woff2 +0 -0
  51. package/fonts/files/poppins-latin-600-italic.woff2 +0 -0
  52. package/fonts/files/poppins-latin-600-normal.woff2 +0 -0
  53. package/fonts/files/poppins-latin-700-italic.woff2 +0 -0
  54. package/fonts/files/poppins-latin-700-normal.woff2 +0 -0
  55. package/fonts/files/poppins-latin-ext-400-italic.woff2 +0 -0
  56. package/fonts/files/poppins-latin-ext-400-normal.woff2 +0 -0
  57. package/fonts/files/poppins-latin-ext-500-italic.woff2 +0 -0
  58. package/fonts/files/poppins-latin-ext-500-normal.woff2 +0 -0
  59. package/fonts/files/poppins-latin-ext-600-italic.woff2 +0 -0
  60. package/fonts/files/poppins-latin-ext-600-normal.woff2 +0 -0
  61. package/fonts/files/poppins-latin-ext-700-italic.woff2 +0 -0
  62. package/fonts/files/poppins-latin-ext-700-normal.woff2 +0 -0
  63. package/fonts/ikon-fonts.css +1379 -0
  64. package/fonts/licenses/crimson-pro-OFL.txt +93 -0
  65. package/fonts/licenses/inter-OFL.txt +93 -0
  66. package/fonts/licenses/jetbrains-mono-OFL.txt +93 -0
  67. package/fonts/licenses/poppins-OFL.txt +93 -0
  68. package/hooks/index.d.ts +0 -1
  69. package/index.d.ts +1 -1
  70. package/index.js +1105 -741
  71. package/package.json +8 -3
  72. package/theme/ikon-app.css +246 -0
  73. package/theme/ikon-auth.css +311 -0
  74. package/theme/ikon-surface.css +112 -0
  75. package/theme/ikon-tokens.css +132 -0
  76. package/hooks/use-lazy-font.d.ts +0 -18
@@ -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.
@@ -122,6 +142,12 @@ export interface UseIkonAppResult {
122
142
  * say the session has ended rather than show a generic connection error.
123
143
  */
124
144
  isSessionExpired: boolean;
145
+ /**
146
+ * True when the app's server started and then died before it could serve the session — nearly
147
+ * always an exception out of the app's own startup code. Terminal: the same bundle crashes the
148
+ * same way, so the app should say it failed to start instead of waiting out the connect budget.
149
+ */
150
+ isStartupFailed: boolean;
125
151
  /**
126
152
  * UI stores for rendering.
127
153
  */
@@ -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
  */
@@ -7,6 +7,17 @@ export interface OAuthCallbackResult {
7
7
  token: string;
8
8
  provider: LoginMethod;
9
9
  }
10
+ /**
11
+ * Whether a same-origin auth response came from an LB with no `/ikon/auth` route — the app
12
+ * frontend answered instead of the auth service.
13
+ *
14
+ * The marker header settles it outright when present. The shape heuristic below is the fallback
15
+ * for an auth service deployed before the header existed, and it can only ever guess: it reads a
16
+ * 404/405 or an HTML 200 as the app frontend answering. That guess has misfired twice on
17
+ * /email/send's 204 — Express stamps its default text/html content type even on a bodyless
18
+ * response — which is why an HTML content type counts only together with a 200.
19
+ */
20
+ export declare function isRouteMissingResponse(response: Response): boolean;
10
21
  /**
11
22
  * Authenticate anonymously with the Ikon auth service.
12
23
  * Returns a session with a token for anonymous users. `flavor` names which not-signed-in identity
@@ -43,18 +54,76 @@ export declare function verifyLoginCode({ email, code, authUrl }: VerifyLoginCod
43
54
  * Returns the token and provider if present, or null if not an OAuth callback.
44
55
  */
45
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>;
82
+ /**
83
+ * Sign-in failure parsed from an OAuth callback redirect: the machine code from the `error` query
84
+ * param, plus the provider the callback names when it knows which one failed.
85
+ */
86
+ export interface OAuthErrorResult {
87
+ code: string;
88
+ provider: LoginMethod | null;
89
+ }
46
90
  /**
47
91
  * Get the error from an OAuth callback if present.
48
92
  */
49
- export declare function parseOAuthError(): string | null;
93
+ export declare function parseOAuthError(): OAuthErrorResult | null;
50
94
  /**
51
- * Clear OAuth-related parameters from the URL.
95
+ * True when the URL asks this tab to skip the local-dev auto-login (`?ikon-signed-out`).
96
+ *
97
+ * A local `ikon app run` signs the developer straight in, which is what you want while building —
98
+ * and exactly what you cannot have while checking the signed-out product. Without this, seeing the
99
+ * public site means logging out by hand in a throwaway browser profile, so it tends not to happen
100
+ * and the guest experience ships unlooked-at. Local dev only: it suppresses the injected developer
101
+ * token and nothing else, so it grants no access and does nothing in a deployed build.
102
+ */
103
+ export declare function wantsSignedOut(): boolean;
104
+ /**
105
+ * Human-readable text for an OAuth callback error code. Unknown codes pass through verbatim so a
106
+ * backend code this build does not know yet still surfaces rather than disappearing.
107
+ *
108
+ * The refused-link text is written for the visitor's mental model, not the system's: they clicked
109
+ * the provider because they have an account THERE, so it must say the pre-existing account is on
110
+ * this app, must not sound like their provider account is broken, and must point at a concrete
111
+ * next step — the email code sign-in when this app offers it, since that proves mailbox ownership
112
+ * and reaches the existing account.
113
+ */
114
+ export declare function describeAuthError(code: string, provider?: string | null, options?: {
115
+ emailSignInAvailable?: boolean;
116
+ }): string;
117
+ /**
118
+ * Clear OAuth-related parameters from the URL, preserving the app's own query parameters —
119
+ * a deep link like /projects/new?prompt=… must survive the OAuth round-trip so the app can
120
+ * read it after sign-in (the SDK sends path+query as the connect InitialPath).
52
121
  */
53
122
  export declare function clearOAuthParams(): void;
54
123
  /**
55
124
  * Build an OAuth redirect URL for the given provider.
56
125
  */
57
- 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>;
58
127
  /**
59
128
  * Check if passkey/WebAuthn is supported in the current browser.
60
129
  */
@@ -75,3 +144,32 @@ export declare function getPasskeyAuthenticationOptions(authUrl: string, email?:
75
144
  * Verify passkey authentication with the auth service.
76
145
  */
77
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>;
157
+ /**
158
+ * True when this document is nested in another. A sign-in cannot be a same-tab redirect here: the
159
+ * provider's consent screen sets X-Frame-Options and would simply refuse to paint.
160
+ *
161
+ * A cross-origin parent makes the property access itself throw, which is the embedded case too.
162
+ */
163
+ export declare function isEmbedded(): boolean;
164
+ /**
165
+ * Run the OAuth sign-in in a popup and resolve with the code it comes back with.
166
+ *
167
+ * The popup goes to the auth host directly rather than through an app's same-origin auth proxy: it
168
+ * is a top-level window, so there is no CORS to avoid, and the return has to land on the auth
169
+ * origin — the one origin that is allowed for every app, whatever address the app itself is on.
170
+ * The verifier for the code stays in THIS document, so the exchange must also happen here.
171
+ */
172
+ export declare function runOAuthPopup(provider: LoginMethod, spaceId: string): Promise<{
173
+ code: string;
174
+ provider: LoginMethod;
175
+ }>;
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, getPasskeyAuthenticationOptions, getPasskeyRegistrationOptions, isPasskeySupported, parseOAuthCallback, parseOAuthError, sendLoginCode, verifyLoginCode, verifyPasskeyAuthentication, verifyPasskeyRegistration, type OAuthCallbackResult, 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,28 @@
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 stashed in sessionStorage rather than left in the URL because signing in with an
12
+ * external provider navigates away and back, which is the same reason the pending-call handoff exists.
13
+ * It is not a credential: it names a parked authorization, and the user binding is made server-side from
14
+ * the token sent below, so a leaked pointer authorizes nobody.
15
+ */
16
+ export declare function captureOAuthContinuation(): boolean;
17
+ export declare function hasPendingOAuthContinuation(): boolean;
18
+ /**
19
+ * Report the completed sign-in and hand the browser to the consent screen.
20
+ *
21
+ * Only ever called after a REAL sign-in — `useAuthGuard` treats an anonymous session as not satisfying a
22
+ * prompt, which is the guest/global exclusion this flow needs and already had. A `global` visitor is one
23
+ * shared space-wide user, so authorizing as one would hand every client the same identity.
24
+ *
25
+ * Failure clears the pointer and returns false: the app then carries on as a normally signed-in session
26
+ * rather than trapping the user in a flow that cannot finish.
27
+ */
28
+ 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.