nuxt-feathers-zod 6.4.13 → 6.4.40

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/CHANGELOG.md CHANGED
@@ -1,3 +1,58 @@
1
+ # Changelog
2
+
3
+ ## 6.4.37
4
+
5
+ - remote + Keycloak: SSO authentication now hydrates the local Feathers client auth store immediately
6
+ - remote handshake can use the configured strategy (for example `sso`) while preserving a coherent local fallback state
7
+ - `useAuth()` exposes separate SSO and Feathers state to avoid mixed `user/token` semantics
8
+ - docs updated for the remote Keycloak contract and backend requirement
9
+
10
+ ## 6.4.34
11
+
12
+ - Remote mode: hardened Feathers-Pinia bootstrap by waiting for `nuxtApp.$pinia` before creating the client, avoiding false fallback to raw `$api` during early client initialization.
13
+ - Remote mode: delayed remote auth bootstrap until after Pinia readiness so auth store hydration and Feathers-Pinia stay coherent.
14
+ - DevTools: restored NFZ plume branding from `public/plume-light.png` / `public/plume-dark.png` instead of the embedded fallback icon.
15
+ - DevTools: icon route now varies on `Sec-CH-Prefers-Color-Scheme` and iframe theme sync with parent remains enabled by default.
16
+
17
+ ## 6.4.33
18
+ - remote + Keycloak auth sync hardening: the Keycloak plugin no longer marks Pinia auth as authenticated when Feathers `authenticate()` actually failed.
19
+ - remote client bootstrap now mirrors successful `authenticate()` / `reAuthenticate()` results into the Pinia auth store so Feathers session state is visible immediately in remote mode.
20
+ - auth token extraction now accepts `accessToken`, `access_token`, and `token` (including nested `authentication.*` variants) for better IdP/backend interoperability.
21
+
22
+ ## 6.4.31
23
+ - Fixed NFZ DevTools icon routing by registering `/__nfz-devtools-icon.png` before the iframe route `/__nfz-devtools` to avoid route capture in consumer apps.
24
+ - Kept parent theme auto-sync logic for the NFZ DevTools iframe.
25
+
26
+ ## 6.4.31
27
+
28
+ - DevTools tab icon now uses theme-aware NFZ plume assets via `public/plume-light.png` and `public/plume-dark.png`.
29
+ - NFZ DevTools iframe now applies the parent DevTools theme before first paint to match the active DevTools theme by default.
30
+ - Added a dedicated DevTools icon route `'/__nfz-devtools-icon.svg'` and switched the custom tab icon from Carbon to the NFZ plume.
31
+
32
+ ## 6.4.31
33
+
34
+ - Fixed Windows CLI build wrapper by invoking Bun through cmd.exe /d /s /c on Windows and sh -lc elsewhere.
35
+ - Removed fragile direct spawn of bun.cmd that was failing with spawnSync EINVAL on Node 22 / Windows.
36
+ - Kept dist/cli/package.json generation after successful bundle.
37
+
38
+
39
+ ## v6.4.17
40
+ - Fixed template-string escaping regressions in `src/cli/core.ts` and `src/runtime/templates/client/plugin.ts`.
41
+ - Restored build/typecheck compatibility for generated Keycloak route middleware and Pinia warning fallback message.
42
+ - Updated `package.json` version to `6.4.17`.
43
+
44
+ - v6.4.16: docs add clear explanation/examples for plugin, server-module, module, client-module, hook, policy CLI targets.
45
+
46
+ ## v6.4.15
47
+ - client remote/runtime: when `feathers.client.pinia` is enabled but `nuxtApp.$pinia` is missing, log a clear warning and fall back to the raw Feathers client for `$api` instead of crashing in `createPiniaClient`.
48
+
49
+ ## v6.4.31
50
+
51
+ - docs(cli): resynchronized README, FR/EN CLI guides, and CLI reference pages around the public v6.4.31 command surface
52
+ - docs(schema): documented `--validate`, `--repair-auth`, and `--diff` as first-class schema maintenance flags
53
+ - docs(help): clarified `add middleware` target guidance (`nitro` and `route` as public targets, others as advanced)
54
+ - cli(help): refreshed built-in help text and command descriptions for middleware and schema maintenance
55
+
1
56
  ## v6.4.12
2
57
 
3
58
  - fix(keycloak-sso): clean OIDC callback hashes like `#state=...&session_state=...&code=...` after Keycloak `check-sso` / callback to avoid Vue Router selector warnings in Nuxt 4 consumer apps
@@ -153,3 +208,35 @@
153
208
  - Fixed Bun/VitePress docs build flow.
154
209
  - Synced NFZ DevTools tab theme with the parent Nuxt DevTools theme by default.
155
210
  - Hardened CLI tests, doctor diagnostics, and Mongo management configuration handling.
211
+
212
+ ## v6.4.14
213
+
214
+ - CLI: updated `renderAuthKeycloakRouteMiddleware()` so generated Nuxt route middleware now cleans OIDC Keycloak callback hash fragments (`#state=...&session_state=...&code=...`) via `history.replaceState(...)` before auth init.
215
+ - Prevents Vue Router warnings caused by invalid CSS selector hashes after `check-sso` redirects.
216
+
217
+ ## v6.4.31
218
+ - Fix CLI build regression in renderAuthKeycloakRouteMiddleware by replacing an invalid nested template literal with string concatenation (`window.location.pathname + window.location.search`).
219
+ - Keep package.json version in sync.
220
+
221
+ ## 6.4.31
222
+ - DevTools registration switched from untyped `nuxt.hook('devtools:customTabs', ...)` to `addCustomTab(...)`.
223
+ - NFZ tab icon is now served by the module on `/__nfz-devtools-icon.png` for consumer-app reliability.
224
+ - Keeps parent-theme sync logic for the NFZ DevTools iframe.
225
+
226
+ ## 6.4.31
227
+ - DevTools: local icon route now serves an embedded 64x64 plume PNG buffer instead of reading public/plume-light.png at runtime.
228
+ - DevTools: parent theme auto-sync now retries after load and observes both html/body with data-theme and data-color-mode support.
229
+
230
+
231
+ ## 6.4.32
232
+ - Remote mode + payloadMode=keycloak: Keycloak authenticated state now synchronizes the Feathers client auth session automatically.
233
+ - The auth store is marked authenticated with a token fallback to keycloak.token, and protected services using authentication('jwt') are authorized on boot and token refresh.
234
+ - feathers-auth bootstrap skips reAuthenticate() in remote Keycloak payload mode; keycloak-sso becomes the source of truth for auth session bootstrap.
235
+
236
+
237
+ ## 6.4.35
238
+ - devtools asset loading made lazy and fault-tolerant to avoid module prepare failure masked as src/module.ts load error
239
+ - public release metadata resynchronized to 6.4.35
240
+ - devtools plume icon/theme-parent behavior preserved
241
+
242
+ - 6.4.37: remote Keycloak Option B contract (`strategy: 'sso'`, `user: loginuser`, `authenticated: true`) is now first-class in the runtime and docs; generated route middleware no longer re-runs `auth.init()` or reuses callback hashes in redirect URIs.
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
- [v6.4.3] CLI typecheck/test cleanup applied.
2
-
3
1
  # nuxt-feathers-zod
4
2
 
3
+ > OSS reference snapshot: **v6.4.40** — optional Mongo management options aligned and release metadata synchronized.
4
+
5
5
  [Documentation](https://vevedh.github.io/nuxt-feathers-zod/)
6
6
 
7
7
  `nuxt-feathers-zod` is the official **Nuxt 4** module that embeds or connects to **FeathersJS v5 (Dove)** with a **CLI-first** workflow and optional **Zod-first** service generation.
8
8
 
9
- Current OSS release target: **6.3.9**.
9
+ Current OSS release target: **6.4.40**.
10
10
 
11
11
  It supports two main usage patterns:
12
12
 
@@ -120,7 +120,7 @@ bunx nuxt-feathers-zod add mongodb-compose
120
120
  bunx nuxt-feathers-zod mongo management --url mongodb://root:change-me@127.0.0.1:27017/app?authSource=admin --auth false
121
121
  ```
122
122
 
123
- ### Open OSS asset commands
123
+ ### Secondary OSS helper commands
124
124
 
125
125
  ```bash
126
126
  bunx nuxt-feathers-zod templates list
@@ -132,17 +132,74 @@ bunx nuxt-feathers-zod middlewares list --target nitro
132
132
  bunx nuxt-feathers-zod middlewares add request-id --target nitro
133
133
  ```
134
134
 
135
- ## CLI command surface in 6.3.9
135
+ ## CLI command surface in 6.4.40
136
136
 
137
137
  | Area | Commands |
138
138
  |---|---|
139
139
  | Init | `init templates`, `init embedded`, `init remote` |
140
140
  | Remote auth | `remote auth keycloak` |
141
141
  | Services | `add service`, `add remote-service`, `auth service`, `schema` |
142
- | Runtime scaffolding | `add middleware`, `add server-module`, `add mongodb-compose`, `mongo management` |
143
- | OSS assets | `templates list`, `plugins list/add`, `modules list/add`, `middlewares list/add` |
142
+ | Runtime scaffolding | `add middleware`, `schema`, `add server-module`, `add mongodb-compose`, `mongo management` |
143
+ | Secondary OSS helpers | `templates list`, `plugins list/add`, `modules list/add`, `middlewares list/add` |
144
144
  | Diagnostics | `doctor` |
145
145
 
146
+
147
+ ## Public CLI focus
148
+
149
+ The public OSS docs now foreground these commands as the **stable core**:
150
+
151
+ - `init embedded`
152
+ - `init remote`
153
+ - `remote auth keycloak`
154
+ - `add service <name>`
155
+ - `add remote-service <name>`
156
+ - `add middleware <name>`
157
+ - `schema <service>`
158
+ - `auth service <name>`
159
+ - `mongo management`
160
+ - `doctor`
161
+
162
+ The following commands remain supported, but are now treated as **secondary helpers or compatibility aliases** in the docs:
163
+
164
+ - `add custom-service <name>`
165
+ - `templates list` / `init templates`
166
+ - `plugins list|add`
167
+ - `modules list|add`
168
+ - `middlewares list|add`
169
+ - `add server-module <name>`
170
+ - `add mongodb-compose`
171
+
172
+ ## Schema maintenance quick reference
173
+
174
+ ```bash
175
+ bunx nuxt-feathers-zod schema users --show
176
+ bunx nuxt-feathers-zod schema users --validate
177
+ bunx nuxt-feathers-zod schema users --diff
178
+ bunx nuxt-feathers-zod schema users --repair-auth
179
+ ```
180
+
181
+ Key `schema` flags:
182
+
183
+ - `--show` / `--json` / `--export`
184
+ - `--set-mode none|zod|json`
185
+ - `--add-field`, `--set-field`, `--remove-field`, `--rename-field`
186
+ - `--validate`
187
+ - `--repair-auth` for auth-enabled `users` baselines
188
+ - `--dry` / `--force`
189
+
190
+ ## `add middleware` target matrix
191
+
192
+ | Target | Purpose | Status in docs |
193
+ |---|---|---|
194
+ | `nitro` | Nitro/H3 middleware | public |
195
+ | `route` | Nuxt route middleware in `app/middleware` | public |
196
+ | `feathers` | Feathers server plugin/middleware artifact | advanced |
197
+ | `server-module` | embedded Feathers server module | advanced |
198
+ | `module` | generic module artifact | advanced |
199
+ | `client-module` | client-side module artifact | advanced |
200
+ | `hook` | Feathers hook scaffold | advanced |
201
+ | `policy` | policy/guard scaffold | advanced |
202
+
146
203
  ## Auth-aware generation for `users`
147
204
 
148
205
  For the `users` service, `--auth` and `--authAware` are distinct concerns:
@@ -220,6 +277,99 @@ bunx nuxt-feathers-zod add mongodb-compose
220
277
  bunx nuxt-feathers-zod add mongodb-compose --out docker-compose-db.yaml --database app --rootPassword secret --force
221
278
  ```
222
279
 
280
+ ## Remote + Keycloak runtime contract
281
+
282
+ In **remote mode** with **Keycloak SSO**, NFZ applies a two-step client contract.
283
+
284
+ ### Step 1 — immediate local Feathers session hydration
285
+
286
+ As soon as Keycloak is authenticated in the browser, the client auth store is hydrated immediately:
287
+
288
+ ```ts
289
+ authStore.authenticated = true
290
+ authStore.accessToken = keycloak.token
291
+ authStore.user = ssoUser
292
+ ```
293
+
294
+ This guarantees that:
295
+ - Nuxt route middleware can consider the user authenticated
296
+ - client service calls receive `Authorization: Bearer <keycloak token>`
297
+ - the local runtime stays coherent even before the remote Feathers API confirms the token
298
+
299
+ ### Step 2 — remote authentication handshake
300
+
301
+ NFZ then attempts a remote Feathers authentication call using the configured remote strategy:
302
+
303
+ ```ts
304
+ await api.authenticate({
305
+ strategy: 'sso', // or another configured remote.auth.strategy
306
+ accessToken: keycloak.token,
307
+ access_token: keycloak.token,
308
+ token: keycloak.token,
309
+ user: ssoUser,
310
+ authenticated: true,
311
+ })
312
+ ```
313
+
314
+ The actual strategy name comes from `feathers.client.remote.auth.strategy`.
315
+
316
+ Example:
317
+
318
+ ```ts
319
+ export default defineNuxtConfig({
320
+ modules: ['nuxt-feathers-zod'],
321
+ feathers: {
322
+ client: {
323
+ mode: 'remote',
324
+ remote: {
325
+ url: 'https://api.example.com',
326
+ auth: {
327
+ enabled: true,
328
+ payloadMode: 'keycloak',
329
+ strategy: 'sso',
330
+ tokenField: 'access_token',
331
+ servicePath: 'authentication',
332
+ },
333
+ },
334
+ },
335
+ keycloak: {
336
+ serverUrl: 'https://sso.example.com',
337
+ realm: 'myrealm',
338
+ clientId: 'myapp',
339
+ },
340
+ },
341
+ })
342
+ ```
343
+
344
+ ### Resulting semantics in `useAuth()`
345
+
346
+ In provider `keycloak`, `useAuth()` now exposes distinct states:
347
+
348
+ - `isSsoAuthenticated`
349
+ - `isFeathersAuthenticated`
350
+ - `isAuthenticated`
351
+ - `ssoUser`
352
+ - `feathersUser`
353
+ - `user`
354
+ - `ssoToken`
355
+ - `feathersToken`
356
+ - `token`
357
+
358
+ Resolution rules:
359
+
360
+ - `user = feathersUser ?? ssoUser`
361
+ - `token = feathersToken ?? ssoToken`
362
+
363
+ ### Backend requirement
364
+
365
+ For protected remote services to be truly accepted by Feathers hooks such as `authenticate('jwt')`, the remote backend must accept the token and strategy you configure.
366
+
367
+ Typical options:
368
+ - validate the Keycloak token directly through the Feathers authentication strategy
369
+ - expose a dedicated remote strategy such as `strategy: 'sso'`
370
+
371
+ If the backend refuses the handshake, NFZ keeps the local client state coherent and preserves the error in the auth store for diagnostics.
372
+
223
373
  ## Notes
224
374
 
225
375
  - Recommended convention: `servicesDirs: ['services']`
@@ -249,3 +399,7 @@ This release hardens the CLI patcher for remote mode so chained commands keep `n
249
399
 
250
400
 
251
401
  - 6.4.1: Added automatic Keycloak `js-sha256` default-export shim alias for consumer Nuxt 4 apps to avoid browser crash from `keycloak-js` importing `js-sha256` as a default export.
402
+
403
+ ### Remote Keycloak `strategy: 'sso'` with option B
404
+
405
+ When the remote backend expects `api.authenticate({ strategy: 'sso', user: loginuser, authenticated: true })`, NFZ now keeps the local SSO object in the auth store (`authStore.user = ssoUser`) but sends only the derived login string as `user` in the remote authenticate payload.
@@ -2620,7 +2620,7 @@ Commands:
2620
2620
  remote auth keycloak Configure remote auth payload mode for Keycloak
2621
2621
  add service <name> Generate an embedded service (or a service with custom methods via --custom)
2622
2622
  add remote-service <name> Register a remote service (client-only)
2623
- add middleware <name> Generate middleware (target nitro|route|feathers)
2623
+ add middleware <name> Generate middleware or middleware-like artifacts
2624
2624
  add mongodb-compose Generate docker-compose-db.yaml for MongoDB
2625
2625
  mongo management Enable/update embedded MongoDB management routes
2626
2626
  schema <service> Inspect schema state or switch schema mode
@@ -2777,6 +2777,8 @@ Flags overview:
2777
2777
  --remove-field <name> remove field from manifest/schema
2778
2778
  --set-field <spec> create or replace field definition
2779
2779
  --rename-field <from:to> rename field preserving definition
2780
+ --validate validate manifest/schema coherence
2781
+ --repair-auth repair auth-compatible users schema baseline
2780
2782
  --servicesDir <dir> (default: services)
2781
2783
  --force
2782
2784
  --dry
@@ -5498,12 +5500,31 @@ export default defineNuxtRouteMiddleware(async (to) => {
5498
5500
  if (import.meta.server)
5499
5501
  return
5500
5502
 
5503
+ const hash = window.location.hash || ''
5504
+
5505
+ // After Keycloak redirects, the hash can contain state/session_state/code which
5506
+ // is not a valid CSS selector. Clean it eagerly so Vue Router does not try to
5507
+ // interpret it as an anchor selector during navigation.
5508
+ if (hash && /(state=|session_state=|code=)/.test(hash)) {
5509
+ const cleanUrl = window.location.pathname + window.location.search
5510
+ window.history.replaceState(window.history.state, '', cleanUrl)
5511
+ }
5512
+
5501
5513
  const auth = useAuth()
5502
5514
  await auth.init()
5503
5515
 
5504
- if (auth.provider.value === 'keycloak' && !auth.isAuthenticated.value) {
5505
- await auth.login({ redirectUri: window.location.origin + to.fullPath })
5516
+ if (auth.provider.value === 'keycloak' && !auth.isSsoAuthenticated.value) {
5517
+ await auth.login({ redirectUri: window.location.origin + window.location.pathname + window.location.search })
5518
+ return
5506
5519
  }
5520
+
5521
+ // Remote + Keycloak contract:
5522
+ // - isSsoAuthenticated means the local Feathers client store is hydrated immediately
5523
+ // with { authenticated:true, accessToken:keycloak.token, user:ssoUser }
5524
+ // - NFZ then attempts api.authenticate(...) using the configured remote strategy
5525
+ // (for example strategy:'sso') and sends { user: loginuser, authenticated: true }
5526
+ // - If the backend does not accept that strategy/token, the client store still remains
5527
+ // coherent for UI/middleware purposes and the error is preserved in the auth store.
5507
5528
  })
5508
5529
  `;
5509
5530
  }
@@ -6660,7 +6681,7 @@ function createCliCommand() {
6660
6681
  const addMiddlewareCommand = defineCommand({
6661
6682
  meta: {
6662
6683
  name: "middleware",
6663
- description: "Generate middleware"
6684
+ description: "Generate middleware or middleware-like artifacts"
6664
6685
  },
6665
6686
  args: {
6666
6687
  name: { type: "positional", required: true, description: "Middleware name" },
@@ -6675,7 +6696,7 @@ function createCliCommand() {
6675
6696
  const addServerModuleCommand = defineCommand({
6676
6697
  meta: {
6677
6698
  name: "server-module",
6678
- description: "Generate a reusable server module"
6699
+ description: "Generate an embedded server module (advanced)"
6679
6700
  },
6680
6701
  args: {
6681
6702
  name: { type: "positional", required: true, description: "Module name" },
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^4.0.0"
6
6
  },
7
- "version": "6.4.13",
7
+ "version": "6.4.40",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -2,7 +2,7 @@ import { createRequire } from 'node:module';
2
2
  import { addDevServerHandler, defineNuxtModule, createResolver, addImportsDir, addTemplate, addServerPlugin, addServerHandler, addImports, addPlugin, hasNuxtModule } from '@nuxt/kit';
3
3
  import { consola } from 'consola';
4
4
  import defu from 'defu';
5
- import { readFileSync } from 'node:fs';
5
+ import { existsSync, readFileSync } from 'node:fs';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { addCustomTab } from '@nuxt/devtools-kit';
8
8
  import { eventHandler, setHeader } from 'h3';
@@ -17,10 +17,35 @@ import { getServerTemplates } from '../dist/runtime/templates/server/index.js';
17
17
  const DEVTOOLS_ROUTE = "/__nfz-devtools";
18
18
  const DEVTOOLS_ROUTE_JSON = "/__nfz-devtools.json";
19
19
  const DEVTOOLS_ROUTE_CSS = "/__nfz-devtools.css";
20
- const VENDORED_STYLES = readFileSync(
21
- fileURLToPath(new URL("./runtime/devtools-ui-kit/assets/styles.css", import.meta.url)),
22
- "utf8"
23
- );
20
+ const DEVTOOLS_ROUTE_ICON = "/__nfz-devtools-icon.png";
21
+ function tryReadTextAsset(relativePath, fallback = "") {
22
+ try {
23
+ const filePath = fileURLToPath(new URL(relativePath, import.meta.url));
24
+ if (!existsSync(filePath))
25
+ return fallback;
26
+ return readFileSync(filePath, "utf8");
27
+ } catch {
28
+ return fallback;
29
+ }
30
+ }
31
+ function tryReadBinaryAsset(relativePath, fallback = new Uint8Array()) {
32
+ try {
33
+ const filePath = fileURLToPath(new URL(relativePath, import.meta.url));
34
+ if (!existsSync(filePath))
35
+ return fallback;
36
+ return readFileSync(filePath);
37
+ } catch {
38
+ return fallback;
39
+ }
40
+ }
41
+ function getVendoredStyles() {
42
+ return tryReadTextAsset("./runtime/devtools-ui-kit/assets/styles.css");
43
+ }
44
+ function getDevtoolsIconBuffers() {
45
+ const light = tryReadBinaryAsset("./public/plume-light.png");
46
+ const dark = tryReadBinaryAsset("./public/plume-dark.png", light);
47
+ return { light, dark };
48
+ }
24
49
  function safeJson(value) {
25
50
  return JSON.stringify(value, null, 2).replace(/</g, "<").replace(/>/g, ">");
26
51
  }
@@ -84,6 +109,52 @@ function renderHtml(payload) {
84
109
  html.dark { --nfz-bg: #0b1020; --nfz-bg-elevated: rgba(10,15,29,.9); --nfz-card: rgba(17,24,39,.72); --nfz-border: rgba(127,127,127,.22); --nfz-text: #e5e7eb; --nfz-muted: #a1a1aa; }
85
110
  html.parent-theme-synced .nfz-sub::after { content: ' \xB7 synced with parent theme'; }
86
111
  </style>
112
+
113
+ <script>
114
+ (() => {
115
+ const docEl = document.documentElement
116
+ function applyTheme(theme) {
117
+ docEl.classList.remove('dark', 'light')
118
+ docEl.classList.add(theme)
119
+ }
120
+ function readThemeFromElement(el) {
121
+ if (!el)
122
+ return null
123
+ const className = String(el.className || '')
124
+ const dataTheme = String(el.getAttribute('data-theme') || '')
125
+ const dataColorMode = String(el.getAttribute('data-color-mode') || '')
126
+ const computed = window.parent.getComputedStyle(el)
127
+ const colorScheme = String(computed.colorScheme || '')
128
+ const isDark = /(^|s)dark(s|$)/.test(className)
129
+ || dataTheme === 'dark'
130
+ || dataColorMode === 'dark'
131
+ || colorScheme.includes('dark')
132
+ const isLight = /(^|s)light(s|$)/.test(className)
133
+ || dataTheme === 'light'
134
+ || dataColorMode === 'light'
135
+ || colorScheme.includes('light')
136
+ if (!isDark && !isLight)
137
+ return null
138
+ return { isDark }
139
+ }
140
+ function getParentThemeState() {
141
+ try {
142
+ if (window.parent === window)
143
+ return null
144
+ const parentDoc = window.parent?.document
145
+ return readThemeFromElement(parentDoc?.documentElement) || readThemeFromElement(parentDoc?.body)
146
+ }
147
+ catch {
148
+ return null
149
+ }
150
+ }
151
+ const parentTheme = getParentThemeState()
152
+ if (parentTheme)
153
+ applyTheme(parentTheme.isDark ? 'dark' : 'light')
154
+ else
155
+ applyTheme(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
156
+ })()
157
+ <\/script>
87
158
  </head>
88
159
  <body>
89
160
  <div class="nfz-shell">
@@ -195,21 +266,35 @@ function renderHtml(payload) {
195
266
  })
196
267
 
197
268
  const docEl = document.documentElement
198
- const rootThemeAttrs = ['class', 'data-theme', 'style']
269
+ const rootThemeAttrs = ['class', 'data-theme', 'data-color-mode', 'style']
270
+
271
+ function readThemeFromElement(el) {
272
+ if (!el)
273
+ return null
274
+ const className = String(el.className || '')
275
+ const dataTheme = String(el.getAttribute('data-theme') || '')
276
+ const dataColorMode = String(el.getAttribute('data-color-mode') || '')
277
+ const computed = window.parent.getComputedStyle(el)
278
+ const colorScheme = String(computed.colorScheme || '')
279
+ const isDark = /(^|s)dark(s|$)/.test(className)
280
+ || dataTheme === 'dark'
281
+ || dataColorMode === 'dark'
282
+ || colorScheme.includes('dark')
283
+ const isLight = /(^|s)light(s|$)/.test(className)
284
+ || dataTheme === 'light'
285
+ || dataColorMode === 'light'
286
+ || colorScheme.includes('light')
287
+ if (!isDark && !isLight)
288
+ return null
289
+ return { isDark }
290
+ }
199
291
 
200
292
  function getParentThemeState() {
201
293
  try {
202
- const parentEl = window.parent?.document?.documentElement
203
- if (!parentEl)
294
+ if (window.parent === window)
204
295
  return null
205
- const className = String(parentEl.className || '')
206
- const dataTheme = String(parentEl.getAttribute('data-theme') || '')
207
- const computed = window.parent.getComputedStyle(parentEl)
208
- const colorScheme = String(computed.colorScheme || '')
209
- const isDark = /(^|s)dark(s|$)/.test(className)
210
- || dataTheme === 'dark'
211
- || colorScheme.includes('dark')
212
- return { isDark, className, dataTheme }
296
+ const parentDoc = window.parent?.document
297
+ return readThemeFromElement(parentDoc?.documentElement) || readThemeFromElement(parentDoc?.body)
213
298
  }
214
299
  catch {
215
300
  return null
@@ -234,24 +319,43 @@ function renderHtml(payload) {
234
319
  return false
235
320
  }
236
321
 
237
- syncThemeFromParent()
238
-
239
- let parentObserver = null
240
- try {
241
- const parentEl = window.parent?.document?.documentElement
242
- if (parentEl && window.parent !== window) {
243
- parentObserver = new window.MutationObserver(() => syncThemeFromParent())
244
- parentObserver.observe(parentEl, { attributes: true, attributeFilter: rootThemeAttrs })
322
+ let parentObservers = []
323
+ function attachParentObservers() {
324
+ try {
325
+ if (window.parent === window || parentObservers.length)
326
+ return
327
+ const parentDoc = window.parent?.document
328
+ const targets = [parentDoc?.documentElement, parentDoc?.body].filter(Boolean)
329
+ parentObservers = targets.map((target) => {
330
+ const observer = new window.MutationObserver(() => syncThemeFromParent())
331
+ observer.observe(target, { attributes: true, attributeFilter: rootThemeAttrs })
332
+ return observer
333
+ })
245
334
  }
335
+ catch {}
246
336
  }
247
- catch {}
337
+
338
+ syncThemeFromParent()
339
+ attachParentObservers()
340
+ ;[32, 120, 260, 600, 1200].forEach((delay) => {
341
+ window.setTimeout(() => {
342
+ syncThemeFromParent()
343
+ attachParentObservers()
344
+ }, delay)
345
+ })
248
346
 
249
347
  window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
250
348
  syncThemeFromParent()
251
349
  })
252
350
 
351
+ window.addEventListener('load', () => {
352
+ syncThemeFromParent()
353
+ attachParentObservers()
354
+ })
355
+
253
356
  themeBtn.addEventListener('click', () => {
254
357
  syncThemeFromParent()
358
+ attachParentObservers()
255
359
  })
256
360
  <\/script>
257
361
  </body>
@@ -323,7 +427,7 @@ function setupNfzDevtools(nuxt, options, extras) {
323
427
  route: DEVTOOLS_ROUTE_CSS,
324
428
  handler: eventHandler((event) => {
325
429
  setHeader(event, "content-type", "text/css; charset=utf-8");
326
- return VENDORED_STYLES;
430
+ return getVendoredStyles();
327
431
  })
328
432
  });
329
433
  addDevServerHandler({
@@ -333,6 +437,17 @@ function setupNfzDevtools(nuxt, options, extras) {
333
437
  return payload;
334
438
  })
335
439
  });
440
+ addDevServerHandler({
441
+ route: DEVTOOLS_ROUTE_ICON,
442
+ handler: eventHandler((event) => {
443
+ const prefersDark = String(event.node.req.headers["sec-ch-prefers-color-scheme"] || "").toLowerCase().includes("dark");
444
+ setHeader(event, "content-type", "image/png");
445
+ setHeader(event, "cache-control", "public, max-age=3600");
446
+ setHeader(event, "vary", "Sec-CH-Prefers-Color-Scheme");
447
+ const icons = getDevtoolsIconBuffers();
448
+ return prefersDark ? icons.dark : icons.light;
449
+ })
450
+ });
336
451
  addDevServerHandler({
337
452
  route: DEVTOOLS_ROUTE,
338
453
  handler: eventHandler((event) => {
@@ -343,12 +458,12 @@ function setupNfzDevtools(nuxt, options, extras) {
343
458
  addCustomTab({
344
459
  name: "nfz-oss",
345
460
  title: "NFZ",
346
- icon: "carbon:data-base",
461
+ icon: DEVTOOLS_ROUTE_ICON,
347
462
  view: {
348
463
  type: "iframe",
349
464
  src: DEVTOOLS_ROUTE
350
465
  }
351
- }, nuxt);
466
+ });
352
467
  }
353
468
 
354
469
  function dedupeStrings(values) {
@@ -414,7 +529,10 @@ function setTsIncludes(options, nuxt) {
414
529
  nuxt.hook("nitro:config", (nitroConfig) => {
415
530
  nitroConfig.typescript = nitroConfig.typescript || {};
416
531
  nitroConfig.typescript.tsConfig = nitroConfig.typescript.tsConfig || {};
417
- nitroConfig.typescript.tsConfig.include = dedupeStrings([...nitroConfig.typescript.tsConfig.include || [], ...includeGlobs]);
532
+ nitroConfig.typescript.tsConfig.include = dedupeStrings([
533
+ ...nitroConfig.typescript.tsConfig.include || [],
534
+ ...includeGlobs
535
+ ]);
418
536
  });
419
537
  }
420
538
  async function ensurePinia(client, nuxt) {
@@ -540,10 +658,10 @@ const module$1 = defineNuxtModule({
540
658
  );
541
659
  if (enableAuthBootstrap) {
542
660
  addImports({ from: resolver.resolve("./runtime/stores/auth"), name: "useAuthStore" });
543
- addPlugin({ order: 1, src: resolver.resolve("./runtime/plugins/feathers-auth") });
661
+ addPlugin({ order: 21, src: resolver.resolve("./runtime/plugins/feathers-auth") });
544
662
  }
545
663
  if (resolvedOptions.keycloak) {
546
- addPlugin({ order: 1, src: resolver.resolve("./runtime/plugins/keycloak-sso"), mode: "client" });
664
+ addPlugin({ order: 22, src: resolver.resolve("./runtime/plugins/keycloak-sso"), mode: "client" });
547
665
  }
548
666
  }
549
667
  let clientPluginDst;
@@ -554,7 +672,7 @@ const module$1 = defineNuxtModule({
554
672
  clientPluginDst = tpl.dst;
555
673
  }
556
674
  addPlugin({
557
- order: 0,
675
+ order: 20,
558
676
  src: clientPluginDst ?? resolver.resolve(resolvedOptions.templateDir, "client/plugin.ts"),
559
677
  ...mode === "remote" ? { mode: "client" } : {}
560
678
  });
@@ -3,9 +3,15 @@ export declare function useAuth(): {
3
3
  provider: import("vue").ComputedRef<AuthProvider>;
4
4
  ready: import("vue").Ref<boolean, boolean>;
5
5
  isAuthenticated: import("vue").ComputedRef<boolean>;
6
+ isSsoAuthenticated: import("vue").ComputedRef<boolean>;
7
+ isFeathersAuthenticated: import("vue").ComputedRef<boolean>;
6
8
  user: import("vue").ComputedRef<any>;
9
+ ssoUser: import("vue").ComputedRef<any>;
10
+ feathersUser: import("vue").ComputedRef<any>;
7
11
  permissions: import("vue").ComputedRef<any>;
8
12
  token: import("vue").ComputedRef<string | null>;
13
+ ssoToken: import("vue").ComputedRef<string | null>;
14
+ feathersToken: import("vue").ComputedRef<string | null>;
9
15
  init: () => Promise<void>;
10
16
  login: (options?: any) => Promise<any>;
11
17
  logout: (options?: any) => Promise<any>;