Appearance
Plugins ​
collage-admin's plugins live in app/plugins/. Nuxt auto-registers them based on filename: a .client.ts suffix means browser-only execution; .server.ts means SSR-only; no suffix means both environments. Plugins that call nuxtApp.provide('name', value) make the value available as $name in components, composables, and other plugins.
amplitude.client.ts ​
Initialises the Amplitude Analytics SDK and Session Replay on the client. The plugin registers a custom enrichment plugin that attaches workspace context (id and name), current route info, and environment to every event automatically. It tracks referrer across in-app client-side navigations (overriding the browser's frozen document.referrer), identifies users when auth state changes, and flushes the event queue on pagehide so events fired immediately before logout or redirect are not lost.
Provides: $amplitude — an AmplitudeTracker object with methods:
track(eventName, properties?, options?)— fire a named eventidentify(userId, properties?)— set the Amplitude user and hashed identity propertiesidentifyFromUser(source)— convenience wrapper that builds properties from aUserobjectsetUserProperties(properties)— update identity properties without changing user idreset()— log out of Amplitude and tear down Session Replayflush()— immediately drain the in-memory event buffergetSessionId()/getDeviceId()— read the current SDK session/device identifierssyncSessionReplaySessionId()— keep the Session Replay SDK aligned with the main SDK session
Runtime config:
config.public.amplitudeApiKey— Amplitude project API key (required for any tracking)config.public.amplitudeSessionReplaySampleRate— fraction of sessions to record (0–1)
auth-sync.client.ts ​
Cross-tab authentication synchronisation. Listens for localStorage storage events on the auth_token key so that when a second tab logs in or out the current tab's auth state follows without a page reload. On login it calls fetchUser(true) to hydrate the user store; on logout it clears auth cookies and redirects to /. On app:mounted (after hydration, to avoid SSR hydration mismatches) it checks the auth_token cookie; if the cookie is missing but localStorage still holds a token, it calls applySession(token, readStrategy().value ?? 'local') to restore the cookie.
Provides: nothing
Runtime config: none
branding.client.ts ​
Client-side branding bootstrap for routes that neither the SSR branding plugin nor other page-level data fetches cover — specifically the host page (/) and the login page. Skips execution when a workspace route param is present, when the path is a shared-assets page, or when the SSR plugin already resolved a white-label brand. Calls damStore.fetchCheckBranding() fire-and-forget; the store update causes useBrand() to recompute reactively.
Also persists the resolved brand icon to a BRAND_ICON_COOKIE (30-day, per-host, SameSite: Lax, Secure) so the next SSR render can paint the favicon before client-side branding resolves. The cookie is set reactively off damStore.brandingDetails; it is cleared when white_label_enabled is false and is excluded on share pages.
Provides: nothing
Runtime config: none
branding.server.ts ​
SSR-only branding resolver that runs before the initial render so white-label favicons and OG meta tags are baked into the HTML on first load. Resolution order:
- Share page (
/shared-assets/:type) — POSTshow-share-assets - Workspace route (
:workspace_id) — POSTget-branding(only when anauth_tokencookie is present) - Host-based fallback — GET
check-brandingusing theOrigin/X-Forwarded-Hostheaders
Each step is non-critical; failure falls through to the next. If all steps fail brandingDetails stays null and Collage defaults apply. Every call uses timeout: 5000 (TIMEOUT_MS).
Host cache (step 3 only, since 38752451). A module-level hostBrandingCache (Map<origin, { expiresAt, data }>) keeps the check-branding result for HOST_BRANDING_TTL_MS = 60_000 (60 s) per Node process:
| Case | Behaviour |
|---|---|
Entry for origin exists and expiresAt > Date.now() | setBrandingDetails(cached.data) and return; no API call |
| Entry missing or expired | GET check-branding; on a resolved response (isResolvedBranding) store it with expiresAt = now + 60 s |
| Response not resolved, or the call throws | nothing is cached, so the next request asks the API again |
origin empty (no host header and not local) | cache is skipped and no request is made |
The key is the origin that is sent to the API: ${proto}://${host} from the forwarded headers, or config.public.baseUrl when appEnv === 'local'. Steps 1 and 2 are never cached. Expired entries are only replaced, not evicted, so the map holds one entry per origin seen by the process. A branding change made in workspace settings can take up to 60 s to show on SSR for unauthenticated host-based routes (the login page, for example).
Provides: nothing (writes directly to damStore)
Runtime config:
config.apiBaseUrlServer— absolute API base for SSR fetches (falls back toconfig.public.apiBaseUrl); the plugin throws if neither is setconfig.public.appEnv/config.public.baseUrl— whenappEnv === 'local',baseUrlreplaces the request host as theOriginsent tocheck-branding
dayjs.client.ts ​
Exposes the dayjs library to the app as a Nuxt-provided helper.
Provides: $dayjs — the dayjs function, identical to a direct import
Runtime config: none
echo.client.ts ​
Sets up Laravel Echo with a Pusher broadcaster for real-time WebSocket channels. Initialisation is lazy: on auth pages (index, forgot-password, reset-password, social-login, generate-password) or when no auth token is present a safe stub is returned instead of a real connection. The real Echo instance is created once and reused; destroyEcho() (also exported) disconnects and nulls it. The plugin hooks app:unmounted to prevent listener stacking during HMR.
Provides: $echo — a Laravel Echo instance (or stub) with private(), channel(), and leave()
Runtime config:
config.public.pusherKey— Pusher application keyconfig.public.pusherCluster— Pusher cluster regionconfig.public.pusherAuthEndpoint— endpoint Pusher uses to authenticate private channels
fetch.ts ​
The central HTTP client for all API calls. Wraps $fetch.create() and provides the result as $api. Runs in both SSR and client environments.
Key behaviours in the onRequest interceptor:
- Base URL — uses
config.apiBaseUrlServeron SSR andconfig.public.apiBaseUrlon the client; throws if neither is set on SSR. - Workspace injection — automatically appends
url_workspace_idto every API request (except user-auth endpoints and public share endpoints) from the route param,currentWorkspacecookie, or explicit query. - Origin injection — adds
origin_urlto user-related API calls on the client (required by the backend for white-label resolution). - Auth header — reads the Bearer token from
useState('auth-token-mem')→ localStorage →auth_tokencookie and setsAuthorization. - CSRF — adds
X-XSRF-TOKENfor state-changing requests to Nuxt server routes. - Long-running overlay — for a defined list of action APIs, triggers
loadingStore.incrementApiLongRunning()after a 5-second delay if the request is still in flight, then decrements on completion. - Cancel tokens — every request gets an
AbortControllerregistered withcancelTokenManager; callingcancelAllRequests()aborts all in-flight requests (used on 401 and logout). A request made withskipCancel: truestill gets a controller (options._abortController) but is not registered, socancelAllRequests()leaves it running.useAuth().logout()is the only caller, so the/logoutPOST is not aborted by the sidebar'scancelAllRequests()right after it (0907e092). - 401 handling — clears the auth token from cookie, state, and localStorage, then calls
cancelAllRequests().
Error handling in onResponseError runs in this order; the first rule that matches throws:
- 428 SSO re-check — added for Single Sign-On. When the API answers 428 (HTTP status or
codein the body) withdata.redirect_url, the client navigates the whole window there so the identity provider can re-verify the user. The URL must passisSafeRedirectUrl(url, window.location.origin)(app/utils/redirect.ts: onlyhttp:andhttps:after resolving against the origin), so ajavascript:ordata:URL from the API is ignored and the error falls through to the normal rules. The thrown error carriesisSsoRecheck: trueso callers can ignore it. - 401 session expired — the session-expired page is now shown for every authenticated API call except URLs containing
get-external-user. Before 5f6902b0 the user endpoints (isUserApiUrl) were also excluded, so a refresh after a user was deactivated cleared the session but left the page without an error. The 401 page then redirects to/after a 5 second countdown (seeuseCollageErrorLayoutin Layouts). - The handler never calls
router.push(): it runs inside the auth middleware's navigation, and a push there re-runs the middleware chain and loops.
Provides: $api — a callable with convenience methods .get(), .post(), .put(), .patch(), .delete()
Runtime config:
config.apiBaseUrlServer— absolute API base for SSRconfig.public.apiBaseUrl— API base for client-side requestsconfig.public.originUrl— origin override for local devconfig.public.https— if'true', addsx-forwarded-proto: httpsto every request
mitt.client.ts ​
Creates a global event bus using the mitt library and makes it available app-wide.
Provides: $mittBus — a mitt emitter instance; use $mittBus.emit(event, payload) and $mittBus.on(event, handler) for cross-component communication
Runtime config: none
pwa-links.client.ts ​
Intercepts anchor-tag clicks when the app is running as an installed PWA (standalone display mode). Same-origin links are routed through Vue Router to stay inside the PWA window; external links get rel="noopener". Also adds a pwa-standalone class to <html> when running in standalone mode, usable as a CSS hook.
Provides: nothing
Runtime config: none
auth-reconnect.client.ts ​
Client-only. Listens for the browser online event. If useAuth().isBackendUnreachable is true (the last /user call failed with no status, status 0 or 5xx), it calls retryAuth(), which resets the retry back-off and runs fetchUser(true). The listener is removed on app:unmounted.
Provides: nothing. Runtime config: none
snackbar.client.ts ​
DOM-injected toast notification system. Creates and animates fixed-position notification elements directly in the document body, bypassing Vue rendering for low-overhead global toasts.
Provides: $snackbar — a SnackbarMethods object with:
show(message, icon?, color?, timeout?)— generic showsuccess(message, timeout?)— green success toasterror(message, timeout?)— red error toastwarning(message, timeout?)— amber warning toastinfo(message, timeout?)— teal info toast
error() returns without showing anything while isSessionTearingDown() (app/utils/cancelTokenManager.ts) is true. cancelAllRequests() sets that flag on logout and on a 401, and useAuth clears it (clearSessionTeardown()) once a /user fetch succeeds. Requests aborted by the teardown reject like failures, so without this guard every caller's catch would show an error toast for the app's own cleanup.
Runtime config: none
stream-saver.client.ts ​
Wraps the streamsaver library and guards createWriteStream against calls on auth pages. The guard reads the route reactively at call time (not at plugin setup time) so navigating from the login page to a page that downloads files works correctly after the initial load.
Provides: $streamSaver — a proxied StreamSaver instance; call $streamSaver.createWriteStream(filename) to start a streaming browser download
Runtime config: none
v-click-outside.client.ts ​
Registers a global Vue directive v-on-click-outside backed by VueUse's onClickOutside. Attach the directive to any element and pass a handler function; the handler fires when a click occurs outside that element.
Provides: nothing (registers v-on-click-outside directive globally)
Runtime config: none
vue-audio-visual.client.ts ​
Globally registers the vue-audio-visual v3 plugin, making components such as <av-waveform> and <av-bars> available throughout the app. Also installs a custom Vue warn handler that silently drops two known-harmless warnings: the expose in mixins warning from vue-audio-visual itself, and a Vuetify VBtn/VDefaultsProvider SSR slot warning.
Provides: nothing (global Vue plugin registration via nuxtApp.vueApp.use(AVPlugin))
Runtime config: none
vue-query.ts ​
Installs TanStack Vue Query (@tanstack/vue-query) with a shared QueryClient. Runs in both SSR and client environments.
Default query settings:
staleTime— 30 000 ms (overridable via runtime config)gcTime— 5 minutesrefetchOnWindowFocus— enabledretry—shouldRetry: no retry for 4xx responses (status read withgetApiErrorStatusfrom~/utils/error) except 408 and 429; otherwise 1 retry (RETRY_COUNT)
Provides: nothing (global Vue plugin registration via nuxtApp.vueApp.use(VueQueryPlugin, { queryClient }))
Runtime config:
config.public.queryStaleTime— override the default stale time in milliseconds (env var:NUXT_PUBLIC_QUERY_STALE_TIME)
vuetify.ts ​
Creates and mounts the Vuetify instance. Runs with enforce: 'pre' so it loads before any plugin that consumes Vuetify components. SSR is configured with an assumed desktop viewport (1280×800) to prevent the navigation drawer from collapsing to mobile width on the server before hydration.
Configured theme tokens (light theme):
| Token | Value |
|---|---|
primary | #6473FF |
primary-darken-1 | #3A47BB |
secondary | #40E0D0 |
error | #FF5572 |
warning | #FF7F50 |
success | #00A13B |
info | #6473FF |
Icons default to the MDI set.
Provides: $vuetify — the Vuetify instance
Runtime config: none
cancel-throttled-requests.client.ts ​
Cancels in-flight throttled API requests on every client-side route change. Uses a router.beforeEach guard: whenever to.path !== from.path, calls cancelThrottleRequests() from ~/utils/cancelTokenManager. This prevents stale responses from a previous page from overwriting state on the newly loaded page.
Provides: nothing
Runtime config: none
pwa.client.ts ​
Manages PWA service-worker lifecycle on the client. Reads mode and scope from usePwaSettings(). If mode is 'off', unregisters all existing service workers and purges caches. Otherwise registers /sw.js scoped to scope once the page load event fires (or immediately if the document is already complete). Only runs when serviceWorker is supported in the browser.
Provides: nothing
Runtime config: none
auth-migration.ts ​
One-time session adoption for users migrating from the old Admin-Frontend (which used @nuxtjs/auth-next). That app stored tokens with cookie names prefixed by auth. (e.g. auth._token.local, auth.strategy, auth.currentWorkspace) and included the "Bearer " prefix inside the cookie value. This plugin detects those legacy cookies, converts them to the new cookie names and format, then nulls out the originals.
Runs in both SSR and client environments (no .server.ts / .client.ts suffix). It must execute before fetch.ts and branding.server.ts so the adopted session is visible to middleware on the same SSR request. On SSR it additionally rewrites the incoming Cookie request header in-process so downstream middleware sees the migrated cookies without waiting for a second request.
Provides: nothing
Runtime config: none
Note: Safe to delete once no users with legacy auth._token.* cookies remain in the wild.
reticle.client.ts ​
Connects the Reticle browser SDK for in-app verification and test orchestration in development. Only active when import.meta.dev is true — no-ops in production builds. Connects to the local Reticle daemon via WebSocket at ws://127.0.0.1:4400/reticle using the collage-admin-b872f794 project ID. A pairing token can be passed via runtimeConfig.public.reticleToken or the build-time constant __RETICLE_TOKEN__.
Provides: nothing
Runtime config:
config.public.reticleToken— optional pairing token for the Reticle daemon
Plugin Reference ​
| File | Environment | Provides | Key config |
|---|---|---|---|
amplitude.client.ts | client | $amplitude | amplitudeApiKey, amplitudeSessionReplaySampleRate |
auth-migration.ts | both | — | — |
auth-reconnect.client.ts | client | — | — |
auth-sync.client.ts | client | — | — |
branding.client.ts | client | — | — |
branding.server.ts | server | — | apiBaseUrlServer, apiBaseUrl, appEnv, baseUrl |
cancel-throttled-requests.client.ts | client | — | — |
dayjs.client.ts | client | $dayjs | — |
echo.client.ts | client | $echo | pusherKey, pusherCluster, pusherAuthEndpoint |
fetch.ts | both | $api | apiBaseUrlServer, apiBaseUrl, originUrl, https |
mitt.client.ts | client | $mittBus | — |
pwa.client.ts | client | — | — |
pwa-links.client.ts | client | — | — |
reticle.client.ts | client (dev only) | — | reticleToken |
snackbar.client.ts | client | $snackbar | — |
stream-saver.client.ts | client | $streamSaver | — |
v-click-outside.client.ts | client | — (directive) | — |
vue-audio-visual.client.ts | client | — (global plugin) | — |
vue-query.ts | both | — (global plugin) | queryStaleTime |
vuetify.ts | both | $vuetify | — |