158 lines
5.2 KiB
TypeScript
158 lines
5.2 KiB
TypeScript
import { createSignal } from "solid-js"
|
|
import { isElectronHost } from "./runtime-env"
|
|
|
|
// Storage key for active user
|
|
const ACTIVE_USER_KEY = "codenomad_active_user_id"
|
|
|
|
const [isLoggedIn, setLoggedIn] = createSignal(false)
|
|
const [isInitialized, setInitialized] = createSignal(false)
|
|
|
|
export { isLoggedIn, setLoggedIn, isInitialized }
|
|
|
|
/**
|
|
* Set the active user ID
|
|
*/
|
|
export function setActiveUserId(userId: string | null): void {
|
|
if (userId) {
|
|
localStorage.setItem(ACTIVE_USER_KEY, userId)
|
|
setLoggedIn(true)
|
|
console.log(`[UserContext] Active user set to: ${userId}`)
|
|
} else {
|
|
localStorage.removeItem(ACTIVE_USER_KEY)
|
|
setLoggedIn(false)
|
|
console.log(`[UserContext] Active user cleared`)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the active user ID
|
|
*/
|
|
export function getActiveUserId(): string | null {
|
|
return localStorage.getItem(ACTIVE_USER_KEY)
|
|
}
|
|
|
|
/**
|
|
* Get headers with user ID for API requests
|
|
*/
|
|
export function getUserHeaders(): Record<string, string> {
|
|
const userId = getActiveUserId()
|
|
if (userId) {
|
|
return { "X-User-Id": userId }
|
|
}
|
|
return {}
|
|
}
|
|
|
|
/**
|
|
* Create fetch options with user headers
|
|
*/
|
|
export function withUserHeaders(options: RequestInit = {}): RequestInit {
|
|
const userHeaders = getUserHeaders()
|
|
if (Object.keys(userHeaders).length === 0) return options
|
|
|
|
const headers = new Headers(options.headers || {})
|
|
for (const [key, value] of Object.entries(userHeaders)) {
|
|
headers.set(key, value)
|
|
}
|
|
|
|
return {
|
|
...options,
|
|
headers,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch wrapper that automatically includes user headers
|
|
*/
|
|
export async function userFetch(url: string | URL | Request, options: RequestInit = {}): Promise<Response> {
|
|
return fetch(url, withUserHeaders(options))
|
|
}
|
|
|
|
/**
|
|
* Globally patch fetch to include user headers for all internal /api/* requests
|
|
* This ensures compatibility with legacy code and 3rd party libraries.
|
|
*/
|
|
export function patchFetch(): void {
|
|
if ((window as any)._codenomad_fetch_patched) return
|
|
(window as any)._codenomad_fetch_patched = true
|
|
|
|
const originalFetch = window.fetch
|
|
window.fetch = async function (input: RequestInfo | URL, init?: RequestInit) {
|
|
let url = ""
|
|
if (typeof input === "string") {
|
|
url = input
|
|
} else if (input instanceof URL) {
|
|
url = input.toString()
|
|
} else if (input instanceof Request) {
|
|
url = input.url
|
|
}
|
|
|
|
// Only inject headers for internal API calls
|
|
if (url.startsWith("/api/") || url.includes(window.location.origin + "/api/")) {
|
|
return originalFetch(input, withUserHeaders(init))
|
|
}
|
|
|
|
return originalFetch(input, init)
|
|
}
|
|
console.log("[UserContext] Global fetch patched for /api/* requests")
|
|
}
|
|
|
|
/**
|
|
* Initialize user context from Host (Electron/Tauri) or API
|
|
* Call this on app startup
|
|
*/
|
|
export async function initializeUserContext(): Promise<void> {
|
|
console.log(`[UserContext] Initializing... host=${isElectronHost()}`)
|
|
try {
|
|
if (isElectronHost()) {
|
|
const api = (window as any).electronAPI
|
|
if (api && api.getActiveUser) {
|
|
console.log(`[UserContext] Requesting active user via api.getActiveUser()...`)
|
|
const activeUser = await api.getActiveUser()
|
|
console.log(`[UserContext] getActiveUser result:`, activeUser)
|
|
|
|
if (activeUser?.id) {
|
|
console.log(`[UserContext] Host has active session: ${activeUser.id}`)
|
|
setActiveUserId(activeUser.id)
|
|
} else {
|
|
console.log(`[UserContext] Host has no active session. Enforcing login.`)
|
|
setActiveUserId(null)
|
|
}
|
|
} else {
|
|
console.warn(`[UserContext] electronAPI.getActiveUser not found. Falling back to web mode.`)
|
|
await handleWebInit()
|
|
}
|
|
} else {
|
|
await handleWebInit()
|
|
}
|
|
} catch (error) {
|
|
console.error(`[UserContext] Critical initialization error:`, error)
|
|
setActiveUserId(null)
|
|
} finally {
|
|
setInitialized(true)
|
|
}
|
|
}
|
|
|
|
async function handleWebInit() {
|
|
console.log(`[UserContext] Web init - checking local cache...`)
|
|
const existingId = getActiveUserId()
|
|
|
|
// In "Mandatory Login" mode, we might want to clear this on every fresh load
|
|
// but for now let's see if the server validates it.
|
|
if (existingId) {
|
|
// We could verify this ID with the server here if we had a /api/users/me endpoint
|
|
// For now, let's keep it but mark it as "unverified" or just let the first API fail
|
|
console.log(`[UserContext] Found cached ID: ${existingId}. Validating session...`)
|
|
|
|
// Strategy: We want mandatory login. If this is a fresh launch, we should probably clear it.
|
|
// For Electron it's already cleared in main.ts. For Web it's tricky.
|
|
// Let's lean towards SECURITY: if no one explicitly logged in THIS RUN, show login.
|
|
|
|
// Actually, if we are in Electron and we hit this, it's because IPC failed.
|
|
// If we are in Web, we trust it for now but we'll see.
|
|
setLoggedIn(true)
|
|
} else {
|
|
console.log(`[UserContext] No cached ID found.`)
|
|
setLoggedIn(false)
|
|
}
|
|
}
|