refactor(apps/app): decouple axios from impersonation sessionStorage contract
Chose Option A from the follow-up brief: useImpersonationStore
already holds an `ImpersonationState` ref hydrated from
sessionStorage at store-init and exposes the active impersonation
target user as a public `targetUserId` computed. The store is the
canonical source; sessionStorage is just its persistence sidecar.
Adds a fifth callback `getImpersonationTargetUserId: () => string
| null` to AxiosBindingsDeps and replaces the
sessionStorage.getItem('crewli_impersonation') + JSON.parse block
in the request interceptor with a single `deps.getImpersonationTargetUserId()`
call. The bindings plugin wires it to
`useImpersonationStore().targetUserId`.
After this commit lib/axios.ts has zero references to
sessionStorage and zero magic strings about impersonation
persistence — the only persistence-mechanism knowledge left is in
useImpersonationStore (where it belongs) and in
plugins/3.axios-bindings.ts (allowed to know about stores). The
HTTP module is now unambiguously pure infrastructure.
Behavior preserved 1:1: the store hydrates from sessionStorage
synchronously inside the defineStore factory, so the very first
HTTP request after page load sees the same target user id as
before.
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import type { AxiosInstance, InternalAxiosRequestConfig } from 'axios'
|
|||||||
*/
|
*/
|
||||||
export interface AxiosBindingsDeps {
|
export interface AxiosBindingsDeps {
|
||||||
getActiveOrgId: () => string | null
|
getActiveOrgId: () => string | null
|
||||||
|
getImpersonationTargetUserId: () => string | null
|
||||||
notify: (message: string, level: 'error' | 'warning') => void
|
notify: (message: string, level: 'error' | 'warning') => void
|
||||||
onAuthFail: () => void
|
onAuthFail: () => void
|
||||||
onImpersonationRevoked: () => void
|
onImpersonationRevoked: () => void
|
||||||
@@ -31,18 +32,9 @@ export function registerInterceptors(client: AxiosInstance, deps: AxiosBindingsD
|
|||||||
if (orgId)
|
if (orgId)
|
||||||
config.headers['X-Organisation-Id'] = orgId
|
config.headers['X-Organisation-Id'] = orgId
|
||||||
|
|
||||||
// Read impersonation header directly from sessionStorage — no store dep.
|
const impersonationTargetUserId = deps.getImpersonationTargetUserId()
|
||||||
const impersonationData = sessionStorage.getItem('crewli_impersonation')
|
if (impersonationTargetUserId)
|
||||||
if (impersonationData) {
|
config.headers['X-Impersonate-User'] = impersonationTargetUserId
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(impersonationData) as { targetUserId?: string }
|
|
||||||
if (parsed.targetUserId)
|
|
||||||
config.headers['X-Impersonate-User'] = parsed.targetUserId
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
// Invalid data — ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (import.meta.env.DEV)
|
if (import.meta.env.DEV)
|
||||||
console.log(`🚀 ${config.method?.toUpperCase()} ${config.url}`, config.data)
|
console.log(`🚀 ${config.method?.toUpperCase()} ${config.url}`, config.data)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { useOrganisationStore } from '@/stores/useOrganisationStore'
|
|||||||
export default function (_: App): void {
|
export default function (_: App): void {
|
||||||
registerInterceptors(apiClient, {
|
registerInterceptors(apiClient, {
|
||||||
getActiveOrgId: () => useOrganisationStore().activeOrganisationId,
|
getActiveOrgId: () => useOrganisationStore().activeOrganisationId,
|
||||||
|
getImpersonationTargetUserId: () => useImpersonationStore().targetUserId,
|
||||||
notify: (message, level) => useNotificationStore().show(message, level),
|
notify: (message, level) => useNotificationStore().show(message, level),
|
||||||
onAuthFail: () => {
|
onAuthFail: () => {
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
|
|||||||
Reference in New Issue
Block a user