@lilaquadrat/frontend 0.1.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 (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +132 -0
  3. package/dist/auth.d.ts +194 -0
  4. package/dist/auth.d.ts.map +1 -0
  5. package/dist/auth.js +280 -0
  6. package/dist/auth.js.map +1 -0
  7. package/dist/index.d.ts +5 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +4 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/interfaces/AuthCallbacks.d.ts +29 -0
  12. package/dist/interfaces/AuthCallbacks.d.ts.map +1 -0
  13. package/dist/interfaces/AuthCallbacks.js +2 -0
  14. package/dist/interfaces/AuthCallbacks.js.map +1 -0
  15. package/dist/interfaces/AuthInitOptions.d.ts +15 -0
  16. package/dist/interfaces/AuthInitOptions.d.ts.map +1 -0
  17. package/dist/interfaces/AuthInitOptions.js +2 -0
  18. package/dist/interfaces/AuthInitOptions.js.map +1 -0
  19. package/dist/interfaces/AuthOptions.d.ts +21 -0
  20. package/dist/interfaces/AuthOptions.d.ts.map +1 -0
  21. package/dist/interfaces/AuthOptions.js +2 -0
  22. package/dist/interfaces/AuthOptions.js.map +1 -0
  23. package/dist/interfaces/KeycloakClient.d.ts +36 -0
  24. package/dist/interfaces/KeycloakClient.d.ts.map +1 -0
  25. package/dist/interfaces/KeycloakClient.js +2 -0
  26. package/dist/interfaces/KeycloakClient.js.map +1 -0
  27. package/dist/interfaces/KeycloakRedirectOptions.d.ts +13 -0
  28. package/dist/interfaces/KeycloakRedirectOptions.d.ts.map +1 -0
  29. package/dist/interfaces/KeycloakRedirectOptions.js +2 -0
  30. package/dist/interfaces/KeycloakRedirectOptions.js.map +1 -0
  31. package/dist/interfaces/KeycloakSilentCheckOptions.d.ts +28 -0
  32. package/dist/interfaces/KeycloakSilentCheckOptions.d.ts.map +1 -0
  33. package/dist/interfaces/KeycloakSilentCheckOptions.js +2 -0
  34. package/dist/interfaces/KeycloakSilentCheckOptions.js.map +1 -0
  35. package/dist/resize.d.ts +165 -0
  36. package/dist/resize.d.ts.map +1 -0
  37. package/dist/resize.js +249 -0
  38. package/dist/resize.js.map +1 -0
  39. package/package.json +69 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 lilaquadrat
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,132 @@
1
+ # @lilaquadrat/frontend
2
+
3
+ Shared frontend libraries for lilaquadrat applications.
4
+
5
+ The package is ESM-only and ships TypeScript declarations. Its public API is
6
+ defined exclusively by exports from `src/index.ts`.
7
+
8
+ ## Resize
9
+
10
+ The Vue-compatible resize singleton publishes `resized` and `media` events on
11
+ `window`, tracks the viewport height in `--vh`, and reads the active media name
12
+ from the visible child of `#mediadetection`.
13
+
14
+ ```ts
15
+ import resize, { useResize } from '@lilaquadrat/frontend/resize';
16
+
17
+ const { media, realHeight, resized } = useResize();
18
+ ```
19
+
20
+ Vue `2.7` and Vue `3` are supported as peer dependencies.
21
+
22
+ `resize` is the shared singleton and is also the default export. Importing it in
23
+ a browser immediately initializes viewport height and listens for native resize
24
+ events. `useResize()` returns Vue refs: `media` is the current visible media
25
+ class, `realHeight` is the visual viewport height, and `resized` changes after
26
+ each update so components can react to it. Its `plugin` property is the shared
27
+ singleton for advanced use such as setting `debounceTime` or calling `trigger`.
28
+
29
+ Add a `#mediadetection` element containing children whose class names are the
30
+ media labels. CSS must show exactly one child with `display: block`; that class
31
+ becomes `media`. The singleton sets `--vh` on `document.documentElement` and
32
+ dispatches `resized` after every update and `media` only when the media class
33
+ changes. Both events are dispatched on `window`.
34
+
35
+ ## Auth
36
+
37
+ `auth` is framework-independent. It owns the Keycloak lifecycle and token
38
+ refreshing; the application owns its own user, permission, and routing state.
39
+
40
+ `keycloak-js` remains an application dependency. Its client instance conforms
41
+ to the exported `KeycloakClient` interface without this package importing it.
42
+
43
+ ### Setup
44
+
45
+ ```ts
46
+ import { createAuth } from '@lilaquadrat/frontend/auth';
47
+ import Keycloak from 'keycloak-js';
48
+
49
+ const auth = createAuth({
50
+ createKeycloak: (configuration) => new Keycloak(configuration),
51
+ callbacks: {
52
+ setToken: (token) => store.commit('setAuthToken', token),
53
+ isSessionLoaded: () => Boolean(store.state.authToken),
54
+ onAuthenticated: () => store.dispatch('loadCurrentUser'),
55
+ onUnauthenticated: () => store.dispatch('clearCurrentUser'),
56
+ onError: (error) => logger.error(error),
57
+ },
58
+ });
59
+
60
+ await auth.init({ development: false, keycloakConfig });
61
+ ```
62
+
63
+ `createAuth(options)` constructs the service and infers the type of
64
+ `keycloakConfig` from `createKeycloak`. Use `new Auth(options)` only when an
65
+ explicit class instance is required.
66
+
67
+ `AuthOptions.callbacks.setToken` is required and receives every token update,
68
+ including `null` after logout or a token failure. `isSessionLoaded` should
69
+ return `true` after the application has loaded the user and permissions. This
70
+ prevents `onAuthenticated` from loading the same session repeatedly.
71
+
72
+ `onAuthenticated` loads application-specific authenticated state. It is called
73
+ after a successful authentication check. `onUnauthenticated` clears that state.
74
+ `onError` receives failed automatic token refreshes triggered by Keycloak.
75
+
76
+ `AuthOptions.createKeycloak` builds a client from the value passed as
77
+ `keycloakConfig` to `init`. `origin` defaults to `window.location.origin` and
78
+ is used to create Keycloak redirect URLs. Provide it explicitly in tests or
79
+ non-browser environments. `developmentToken` defaults to `DEVTOKEN`.
80
+
81
+ ### Lifecycle
82
+
83
+ `auth.init({ development, keycloakConfig })` must run before protected content
84
+ is displayed. In production it initializes Keycloak with silent SSO checking,
85
+ then calls `checkAuth`. Host `silent-check-sso.html` at the application origin.
86
+ In development mode no Keycloak client is created and `developmentToken` is
87
+ stored through `setToken`.
88
+
89
+ `auth.checkAuth()` verifies the active session, refreshes its token, and runs
90
+ `onAuthenticated` if `isSessionLoaded` is absent or returns `false`. Use it in
91
+ an app-specific route guard; redirect to the login route when it rejects.
92
+
93
+ `auth.refreshToken(force)` returns an access token and synchronizes it through
94
+ `setToken`. Concurrent refreshes share one request. Pass `true` after a 401 to
95
+ force a refresh even if the current token has more than 30 seconds remaining.
96
+
97
+ `auth.authorize()` starts Keycloak login, and `auth.register()` starts
98
+ registration. Both return to `/login/callback`. `auth.logout()` clears host
99
+ state before starting Keycloak logout. `auth.getAuthHeader()` returns the last
100
+ stored bearer token; `auth.getFreshAuthHeader()` refreshes before returning it.
101
+
102
+ `setAuthTokenRefresher(refresher)` registers an application-wide refresh
103
+ function. `refreshAuthToken(force)` invokes the function registered by the most
104
+ recent `auth.init`; use it only in HTTP helpers that cannot receive an `Auth`
105
+ instance. Prefer `auth.getFreshAuthHeader()` when an instance is available.
106
+
107
+ ### Types
108
+
109
+ `AuthCallbacks` defines the host-state callbacks described above.
110
+ `AuthOptions<T>` defines service construction. `AuthInitOptions<T>` defines the
111
+ per-initialization development flag and Keycloak configuration. `KeycloakClient`
112
+ is the minimal client contract. `KeycloakRedirectOptions` and
113
+ `KeycloakSilentCheckOptions` describe the redirect and silent-session options
114
+ passed to that client. All type definitions are exported from the package root
115
+ and `@lilaquadrat/frontend/auth`.
116
+
117
+ ## Install
118
+
119
+ ```sh
120
+ yarn add @lilaquadrat/frontend
121
+ ```
122
+
123
+ ## Development
124
+
125
+ ```sh
126
+ yarn build
127
+ yarn typecheck
128
+ yarn lint
129
+ yarn test
130
+ ```
131
+
132
+ `yarn prepack` runs all validation before the package is packed or published.
package/dist/auth.d.ts ADDED
@@ -0,0 +1,194 @@
1
+ import type { AuthInitOptions } from './interfaces/AuthInitOptions.js';
2
+ import type { AuthOptions } from './interfaces/AuthOptions.js';
3
+ import type { KeycloakClient } from './interfaces/KeycloakClient.js';
4
+ /**
5
+ * Framework-independent Keycloak authentication.
6
+ *
7
+ * The service manages Keycloak and tokens. The host application provides state
8
+ * callbacks, so this module works with Vue, React, Angular, or no UI framework.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import Keycloak from 'keycloak-js';
13
+ * import { createAuth } from '@lilaquadrat/frontend/auth';
14
+ *
15
+ * const auth = createAuth({
16
+ * createKeycloak: (configuration) => new Keycloak(configuration),
17
+ * callbacks: {
18
+ * setToken: (token) => apiClient.setToken(token),
19
+ * isSessionLoaded: () => session.loaded,
20
+ * onAuthenticated: () => session.loadCurrentUser(),
21
+ * onUnauthenticated: () => session.clear(),
22
+ * onError: (error) => logger.error(error),
23
+ * },
24
+ * });
25
+ *
26
+ * await auth.init({ development: false, keycloakConfig });
27
+ * ```
28
+ */
29
+ export type { AuthCallbacks, } from './interfaces/AuthCallbacks.js';
30
+ export type { AuthInitOptions, } from './interfaces/AuthInitOptions.js';
31
+ export type { AuthOptions, } from './interfaces/AuthOptions.js';
32
+ export type { KeycloakClient, } from './interfaces/KeycloakClient.js';
33
+ export type { KeycloakRedirectOptions } from './interfaces/KeycloakRedirectOptions.js';
34
+ export type { KeycloakSilentCheckOptions } from './interfaces/KeycloakSilentCheckOptions.js';
35
+ type AuthTokenRefresher = (force?: boolean) => Promise<string>;
36
+ /**
37
+ * Registers the function used by {@link refreshAuthToken} to obtain the current access token.
38
+ *
39
+ * Most applications do not call this directly: `Auth.init` registers its own
40
+ * refresh method. Register a custom refresher only when an HTTP integration is
41
+ * initialized before the `Auth` instance.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * setAuthTokenRefresher((force) => auth.refreshToken(force));
46
+ * ```
47
+ */
48
+ export declare function setAuthTokenRefresher(refresher: AuthTokenRefresher): void;
49
+ /**
50
+ * Refreshes the access token through the most recently initialized `Auth` instance.
51
+ *
52
+ * Use this in HTTP clients that cannot receive an `Auth` instance directly. Pass
53
+ * `true` only when a request was rejected and must force an immediate refresh.
54
+ *
55
+ * @example
56
+ * ```ts
57
+ * const token = await refreshAuthToken(response.status === 401);
58
+ * ```
59
+ */
60
+ export declare function refreshAuthToken(force?: boolean): Promise<string | undefined>;
61
+ /**
62
+ * Framework-independent Keycloak authentication service.
63
+ *
64
+ * Construct it with {@link createAuth}, then call {@link Auth.init} before using
65
+ * route guards, API clients, or any other authenticated application feature.
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * const auth = new Auth({ callbacks, createKeycloak, origin: 'https://app.example.com' });
70
+ * await auth.init({ development: false, keycloakConfig });
71
+ * ```
72
+ */
73
+ export declare class Auth<KeycloakConfiguration = unknown> {
74
+ /** Indicates whether the service currently holds an authenticated token. */
75
+ authenticated: boolean;
76
+ /** Indicates whether `checkAuth` is currently checking or loading a session. */
77
+ inProgress: boolean;
78
+ /** The most recently acquired access token, if authentication succeeded. */
79
+ token?: string;
80
+ /** The initialized Keycloak client. It is available after production `init`. */
81
+ keycloak?: KeycloakClient;
82
+ private readonly callbacks;
83
+ private readonly createKeycloak?;
84
+ private readonly developmentToken;
85
+ private readonly origin?;
86
+ private development;
87
+ private refreshPromise?;
88
+ private refreshForced;
89
+ /** Creates an auth service. Prefer {@link createAuth} for inferred configuration types. */
90
+ constructor(options: AuthOptions<KeycloakConfiguration>);
91
+ /**
92
+ * Initializes development authentication or a Keycloak client, then verifies the session.
93
+ *
94
+ * Production mode requires both `keycloakConfig` and `createKeycloak`. The application must
95
+ * host `silent-check-sso.html` at its origin for Keycloak silent session checking.
96
+ *
97
+ * @example
98
+ * ```ts
99
+ * await auth.init({ development: import.meta.env.DEV, keycloakConfig });
100
+ * ```
101
+ */
102
+ init(options: AuthInitOptions<KeycloakConfiguration>): Promise<boolean>;
103
+ /**
104
+ * Refreshes the Keycloak token and stores it through `callbacks.setToken`.
105
+ *
106
+ * Concurrent calls share one request. A forced refresh waits for an active normal refresh,
107
+ * then starts an independent request with Keycloak's `-1` minimum validity.
108
+ *
109
+ * @example
110
+ * ```ts
111
+ * const token = await auth.refreshToken();
112
+ * const retryToken = await auth.refreshToken(true);
113
+ * ```
114
+ */
115
+ refreshToken(force?: boolean): Promise<string>;
116
+ /**
117
+ * Verifies the current session and runs `onAuthenticated` when host data is not loaded.
118
+ *
119
+ * Call this from application startup or an app-specific route guard. It rejects with
120
+ * `NOT_AUTHENTICATED` when no active Keycloak session exists.
121
+ *
122
+ * @example
123
+ * ```ts
124
+ * try {
125
+ * await auth.checkAuth();
126
+ * } catch {
127
+ * router.navigate('/login');
128
+ * }
129
+ * ```
130
+ */
131
+ checkAuth(): Promise<boolean>;
132
+ /**
133
+ * Redirects the browser to Keycloak's login flow and returns to `/login/callback`.
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * await auth.authorize();
138
+ * ```
139
+ */
140
+ authorize(): Promise<unknown> | unknown;
141
+ /**
142
+ * Redirects the browser to Keycloak's registration flow and returns to `/login/callback`.
143
+ *
144
+ * @example
145
+ * ```ts
146
+ * await auth.register();
147
+ * ```
148
+ */
149
+ register(): Promise<unknown> | unknown;
150
+ /**
151
+ * Clears host authentication state, then redirects the browser to Keycloak's logout flow.
152
+ *
153
+ * @example
154
+ * ```ts
155
+ * await auth.logout();
156
+ * ```
157
+ */
158
+ logout(): Promise<unknown> | unknown;
159
+ /**
160
+ * Returns an HTTP `Authorization` header using the most recently stored token.
161
+ *
162
+ * @example
163
+ * ```ts
164
+ * await fetch('/api/me', { headers: auth.getAuthHeader() });
165
+ * ```
166
+ */
167
+ getAuthHeader(): {
168
+ Authorization: string;
169
+ };
170
+ /**
171
+ * Refreshes the token and returns an HTTP `Authorization` header with the fresh value.
172
+ *
173
+ * @example
174
+ * ```ts
175
+ * await fetch('/api/me', { headers: await auth.getFreshAuthHeader() });
176
+ * ```
177
+ */
178
+ getFreshAuthHeader(): Promise<{
179
+ Authorization: string;
180
+ }>;
181
+ private clearAuthentication;
182
+ private requireKeycloak;
183
+ private requireOrigin;
184
+ }
185
+ /**
186
+ * Creates a framework-independent `Auth` service with inferred Keycloak configuration types.
187
+ *
188
+ * @example
189
+ * ```ts
190
+ * const auth = createAuth({ callbacks, createKeycloak: (configuration) => new Keycloak(configuration) });
191
+ * ```
192
+ */
193
+ export declare function createAuth<KeycloakConfiguration>(options: AuthOptions<KeycloakConfiguration>): Auth<KeycloakConfiguration>;
194
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAErE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,YAAY,EACV,aAAa,GACd,MAAM,+BAA+B,CAAC;AACvC,YAAY,EACV,eAAe,GAChB,MAAM,iCAAiC,CAAC;AACzC,YAAY,EACV,WAAW,GACZ,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,cAAc,GACf,MAAM,gCAAgC,CAAC;AACxC,YAAY,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AACvF,YAAY,EAAE,0BAA0B,EAAE,MAAM,4CAA4C,CAAC;AAE7F,KAAK,kBAAkB,GAAG,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAI/D;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,kBAAkB,GAAG,IAAI,CAEzE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,UAAQ,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAE3E;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,IAAI,CAAC,qBAAqB,GAAG,OAAO;IAC/C,4EAA4E;IAC5E,aAAa,UAAS;IAEtB,gFAAgF;IAChF,UAAU,UAAS;IAEnB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,gFAAgF;IAChF,QAAQ,CAAC,EAAE,cAAc,CAAC;IAE1B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgB;IAC1C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAA2D;IAC3F,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAS;IACjC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,cAAc,CAAC,CAAkB;IACzC,OAAO,CAAC,aAAa,CAAS;IAE9B,2FAA2F;gBAC/E,OAAO,EAAE,WAAW,CAAC,qBAAqB,CAAC;IAOvD;;;;;;;;;;OAUG;IACG,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,qBAAqB,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAmC7E;;;;;;;;;;;OAWG;IACG,YAAY,CAAC,KAAK,UAAQ,GAAG,OAAO,CAAC,MAAM,CAAC;IAwClD;;;;;;;;;;;;;;OAcG;IACG,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IA0BnC;;;;;;;OAOG;IACH,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO;IAIvC;;;;;;;OAOG;IACH,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO;IAItC;;;;;;;OAOG;IACH,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO;IAMpC;;;;;;;OAOG;IACH,aAAa,IAAI;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE;IAI1C;;;;;;;OAOG;IACG,kBAAkB,IAAI,OAAO,CAAC;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;IAI9D,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,aAAa;CAItB;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,qBAAqB,EAAE,OAAO,EAAE,WAAW,CAAC,qBAAqB,CAAC,GAAG,IAAI,CAAC,qBAAqB,CAAC,CAE1H"}
package/dist/auth.js ADDED
@@ -0,0 +1,280 @@
1
+ let authTokenRefresher;
2
+ /**
3
+ * Registers the function used by {@link refreshAuthToken} to obtain the current access token.
4
+ *
5
+ * Most applications do not call this directly: `Auth.init` registers its own
6
+ * refresh method. Register a custom refresher only when an HTTP integration is
7
+ * initialized before the `Auth` instance.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * setAuthTokenRefresher((force) => auth.refreshToken(force));
12
+ * ```
13
+ */
14
+ export function setAuthTokenRefresher(refresher) {
15
+ authTokenRefresher = refresher;
16
+ }
17
+ /**
18
+ * Refreshes the access token through the most recently initialized `Auth` instance.
19
+ *
20
+ * Use this in HTTP clients that cannot receive an `Auth` instance directly. Pass
21
+ * `true` only when a request was rejected and must force an immediate refresh.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const token = await refreshAuthToken(response.status === 401);
26
+ * ```
27
+ */
28
+ export function refreshAuthToken(force = false) {
29
+ return Promise.resolve(authTokenRefresher?.(force));
30
+ }
31
+ /**
32
+ * Framework-independent Keycloak authentication service.
33
+ *
34
+ * Construct it with {@link createAuth}, then call {@link Auth.init} before using
35
+ * route guards, API clients, or any other authenticated application feature.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * const auth = new Auth({ callbacks, createKeycloak, origin: 'https://app.example.com' });
40
+ * await auth.init({ development: false, keycloakConfig });
41
+ * ```
42
+ */
43
+ export class Auth {
44
+ /** Indicates whether the service currently holds an authenticated token. */
45
+ authenticated = false;
46
+ /** Indicates whether `checkAuth` is currently checking or loading a session. */
47
+ inProgress = false;
48
+ /** The most recently acquired access token, if authentication succeeded. */
49
+ token;
50
+ /** The initialized Keycloak client. It is available after production `init`. */
51
+ keycloak;
52
+ callbacks;
53
+ createKeycloak;
54
+ developmentToken;
55
+ origin;
56
+ development = false;
57
+ refreshPromise;
58
+ refreshForced = false;
59
+ /** Creates an auth service. Prefer {@link createAuth} for inferred configuration types. */
60
+ constructor(options) {
61
+ this.callbacks = options.callbacks;
62
+ this.createKeycloak = options.createKeycloak;
63
+ this.developmentToken = options.developmentToken ?? 'DEVTOKEN';
64
+ this.origin = options.origin ?? (typeof window === 'undefined' ? undefined : window.location.origin);
65
+ }
66
+ /**
67
+ * Initializes development authentication or a Keycloak client, then verifies the session.
68
+ *
69
+ * Production mode requires both `keycloakConfig` and `createKeycloak`. The application must
70
+ * host `silent-check-sso.html` at its origin for Keycloak silent session checking.
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * await auth.init({ development: import.meta.env.DEV, keycloakConfig });
75
+ * ```
76
+ */
77
+ async init(options) {
78
+ this.development = options.development;
79
+ if (this.development) {
80
+ setAuthTokenRefresher((force) => this.refreshToken(force));
81
+ return this.checkAuth();
82
+ }
83
+ if (options.keycloakConfig === undefined)
84
+ throw new Error('KEYCLOAK_CONFIG_MISSING');
85
+ if (!this.createKeycloak)
86
+ throw new Error('KEYCLOAK_FACTORY_MISSING');
87
+ if (!this.origin)
88
+ throw new Error('BROWSER_ORIGIN_MISSING');
89
+ this.keycloak = this.createKeycloak(options.keycloakConfig);
90
+ this.keycloak.onAuthLogout = () => this.clearAuthentication();
91
+ this.keycloak.onTokenExpired = () => {
92
+ void this.refreshToken().catch((error) => {
93
+ this.callbacks.onError?.(error);
94
+ this.keycloak?.clearToken();
95
+ this.clearAuthentication();
96
+ });
97
+ };
98
+ setAuthTokenRefresher((force) => this.refreshToken(force));
99
+ await this.keycloak.init({
100
+ onLoad: 'check-sso',
101
+ flow: 'standard',
102
+ pkceMethod: 'S256',
103
+ scope: 'profile',
104
+ silentCheckSsoRedirectUri: `${this.origin}/silent-check-sso.html`,
105
+ silentCheckSsoFallback: true,
106
+ });
107
+ return this.checkAuth();
108
+ }
109
+ /**
110
+ * Refreshes the Keycloak token and stores it through `callbacks.setToken`.
111
+ *
112
+ * Concurrent calls share one request. A forced refresh waits for an active normal refresh,
113
+ * then starts an independent request with Keycloak's `-1` minimum validity.
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * const token = await auth.refreshToken();
118
+ * const retryToken = await auth.refreshToken(true);
119
+ * ```
120
+ */
121
+ async refreshToken(force = false) {
122
+ if (this.development)
123
+ return this.developmentToken;
124
+ const keycloak = this.keycloak;
125
+ if (!keycloak?.authenticated)
126
+ throw new Error('NOT_AUTHENTICATED');
127
+ if (this.refreshPromise) {
128
+ if (!force || this.refreshForced)
129
+ return this.refreshPromise;
130
+ try {
131
+ await this.refreshPromise;
132
+ }
133
+ catch {
134
+ // A forced refresh gets an independent result after the active request settles.
135
+ }
136
+ return this.refreshToken(true);
137
+ }
138
+ const refreshPromise = keycloak.updateToken(force ? -1 : 30).then(() => {
139
+ if (!keycloak.token)
140
+ throw new Error('TOKEN_MISSING');
141
+ this.token = keycloak.token;
142
+ this.authenticated = true;
143
+ this.callbacks.setToken(keycloak.token);
144
+ return keycloak.token;
145
+ });
146
+ this.refreshPromise = refreshPromise;
147
+ this.refreshForced = force;
148
+ try {
149
+ return await refreshPromise;
150
+ }
151
+ finally {
152
+ if (this.refreshPromise === refreshPromise) {
153
+ this.refreshPromise = undefined;
154
+ this.refreshForced = false;
155
+ }
156
+ }
157
+ }
158
+ /**
159
+ * Verifies the current session and runs `onAuthenticated` when host data is not loaded.
160
+ *
161
+ * Call this from application startup or an app-specific route guard. It rejects with
162
+ * `NOT_AUTHENTICATED` when no active Keycloak session exists.
163
+ *
164
+ * @example
165
+ * ```ts
166
+ * try {
167
+ * await auth.checkAuth();
168
+ * } catch {
169
+ * router.navigate('/login');
170
+ * }
171
+ * ```
172
+ */
173
+ async checkAuth() {
174
+ this.inProgress = true;
175
+ try {
176
+ if (this.development) {
177
+ this.token = this.developmentToken;
178
+ this.authenticated = true;
179
+ this.callbacks.setToken(this.developmentToken);
180
+ await this.callbacks.onAuthenticated?.();
181
+ return true;
182
+ }
183
+ if (!this.keycloak?.authenticated)
184
+ throw new Error('NOT_AUTHENTICATED');
185
+ await this.refreshToken();
186
+ if (!this.callbacks.isSessionLoaded?.()) {
187
+ await this.callbacks.onAuthenticated?.();
188
+ }
189
+ return true;
190
+ }
191
+ finally {
192
+ this.inProgress = false;
193
+ }
194
+ }
195
+ /**
196
+ * Redirects the browser to Keycloak's login flow and returns to `/login/callback`.
197
+ *
198
+ * @example
199
+ * ```ts
200
+ * await auth.authorize();
201
+ * ```
202
+ */
203
+ authorize() {
204
+ return this.requireKeycloak().login({ redirectUri: `${this.requireOrigin()}/login/callback` });
205
+ }
206
+ /**
207
+ * Redirects the browser to Keycloak's registration flow and returns to `/login/callback`.
208
+ *
209
+ * @example
210
+ * ```ts
211
+ * await auth.register();
212
+ * ```
213
+ */
214
+ register() {
215
+ return this.requireKeycloak().register({ redirectUri: `${this.requireOrigin()}/login/callback` });
216
+ }
217
+ /**
218
+ * Clears host authentication state, then redirects the browser to Keycloak's logout flow.
219
+ *
220
+ * @example
221
+ * ```ts
222
+ * await auth.logout();
223
+ * ```
224
+ */
225
+ logout() {
226
+ const keycloak = this.requireKeycloak();
227
+ this.clearAuthentication();
228
+ return keycloak.logout({ redirectUri: this.requireOrigin() });
229
+ }
230
+ /**
231
+ * Returns an HTTP `Authorization` header using the most recently stored token.
232
+ *
233
+ * @example
234
+ * ```ts
235
+ * await fetch('/api/me', { headers: auth.getAuthHeader() });
236
+ * ```
237
+ */
238
+ getAuthHeader() {
239
+ return { Authorization: `Bearer ${this.token ?? ''}` };
240
+ }
241
+ /**
242
+ * Refreshes the token and returns an HTTP `Authorization` header with the fresh value.
243
+ *
244
+ * @example
245
+ * ```ts
246
+ * await fetch('/api/me', { headers: await auth.getFreshAuthHeader() });
247
+ * ```
248
+ */
249
+ async getFreshAuthHeader() {
250
+ return { Authorization: `Bearer ${await this.refreshToken()}` };
251
+ }
252
+ clearAuthentication() {
253
+ this.token = undefined;
254
+ this.authenticated = false;
255
+ this.callbacks.setToken(null);
256
+ void this.callbacks.onUnauthenticated?.();
257
+ }
258
+ requireKeycloak() {
259
+ if (!this.keycloak)
260
+ throw new Error('AUTH_NOT_INITIALIZED');
261
+ return this.keycloak;
262
+ }
263
+ requireOrigin() {
264
+ if (!this.origin)
265
+ throw new Error('BROWSER_ORIGIN_MISSING');
266
+ return this.origin;
267
+ }
268
+ }
269
+ /**
270
+ * Creates a framework-independent `Auth` service with inferred Keycloak configuration types.
271
+ *
272
+ * @example
273
+ * ```ts
274
+ * const auth = createAuth({ callbacks, createKeycloak: (configuration) => new Keycloak(configuration) });
275
+ * ```
276
+ */
277
+ export function createAuth(options) {
278
+ return new Auth(options);
279
+ }
280
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export { default as resize } from './resize.js';
2
+ export { Resize, useResize } from './resize.js';
3
+ export { Auth, createAuth, refreshAuthToken, setAuthTokenRefresher } from './auth.js';
4
+ export type { AuthCallbacks, AuthInitOptions, AuthOptions, KeycloakClient, KeycloakRedirectOptions, KeycloakSilentCheckOptions, } from './auth.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AACtF,YAAY,EACV,aAAa,EACb,eAAe,EACf,WAAW,EACX,cAAc,EACd,uBAAuB,EACvB,0BAA0B,GAC3B,MAAM,WAAW,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { default as resize } from './resize.js';
2
+ export { Resize, useResize } from './resize.js';
3
+ export { Auth, createAuth, refreshAuthToken, setAuthTokenRefresher } from './auth.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC"}