learning_ai_invt_trdg/mobile/lib/platformAuth.ts

190 lines
5.3 KiB
TypeScript

import { secureSessionStorage, clearMobileSessionStorage, MOBILE_SESSION_STORAGE_KEY } from '@/lib/secureSessionStorage';
import { createMobilePlatformSdk, mobileRuntime } from '@/lib/runtime';
import { createRequestId } from '../../shared/request-id.js';
export interface PlatformAuthUser {
id: string;
email?: string;
role?: string;
plan?: string;
displayName?: string;
}
export interface StoredPlatformSession {
accessToken: string;
refreshToken: string;
user: PlatformAuthUser;
}
class PlatformAuthError extends Error {
status?: number;
constructor(message: string, status?: number) {
super(message);
this.name = 'PlatformAuthError';
this.status = status;
}
}
function createPlatformAuthSdk(accessToken: string | null = null) {
return createMobilePlatformSdk(() => accessToken);
}
function normalizeUser(value: any): PlatformAuthUser {
return {
id: String(value?.id || value?.sub || '').trim(),
email: typeof value?.email === 'string' ? value.email : undefined,
role: typeof value?.role === 'string' ? value.role : undefined,
plan: typeof value?.plan === 'string' ? value.plan : undefined,
displayName: typeof value?.displayName === 'string' ? value.displayName : undefined,
};
}
async function platformRequest<T>(
path: string,
options?: {
method?: string;
accessToken?: string;
body?: Record<string, unknown>;
}
): Promise<T> {
const sdk = createPlatformAuthSdk(options?.accessToken ?? null);
const response = await sdk.fetch(path, {
method: options?.method || 'GET',
headers: {
'x-request-id': createRequestId('mobile-auth')
},
body: options?.body ? JSON.stringify(options.body) : undefined,
});
const payload = await response.json().catch(() => ({}));
if (!response.ok) {
throw new PlatformAuthError(
String((payload as { message?: string; error?: string }).message || (payload as { error?: string }).error || `HTTP ${response.status}`),
response.status
);
}
return payload as T;
}
function isStoredSession(value: unknown): value is StoredPlatformSession {
const candidate = value as StoredPlatformSession | null;
return Boolean(
candidate
&& typeof candidate.accessToken === 'string'
&& typeof candidate.refreshToken === 'string'
&& candidate.user
&& typeof candidate.user.id === 'string'
);
}
async function writeSession(session: StoredPlatformSession): Promise<void> {
await secureSessionStorage.setItem(MOBILE_SESSION_STORAGE_KEY, JSON.stringify(session));
}
export async function persistPlatformSession(session: StoredPlatformSession): Promise<void> {
await writeSession(session);
}
export async function readPlatformSession(): Promise<StoredPlatformSession | null> {
const raw = await secureSessionStorage.getItem(MOBILE_SESSION_STORAGE_KEY);
if (!raw) {
return null;
}
try {
const parsed = JSON.parse(raw) as unknown;
return isStoredSession(parsed) ? parsed : null;
} catch {
return null;
}
}
export async function clearPlatformSession(): Promise<void> {
await clearMobileSessionStorage();
}
export async function getCurrentPlatformUser(accessToken: string): Promise<PlatformAuthUser> {
const me = await platformRequest<any>('/auth/me', {
accessToken,
});
return normalizeUser(me);
}
export async function refreshPlatformSession(refreshToken: string): Promise<StoredPlatformSession> {
const refreshed = await platformRequest<{ accessToken: string; refreshToken: string }>('/auth/refresh', {
method: 'POST',
body: { refreshToken },
});
const user = await getCurrentPlatformUser(refreshed.accessToken);
const session: StoredPlatformSession = {
accessToken: refreshed.accessToken,
refreshToken: refreshed.refreshToken,
user,
};
await writeSession(session);
return session;
}
export async function syncPlatformSessionFromTokens(
accessToken: string,
refreshToken: string
): Promise<StoredPlatformSession> {
const user = await getCurrentPlatformUser(accessToken);
const session: StoredPlatformSession = {
accessToken,
refreshToken,
user,
};
await writeSession(session);
return session;
}
export async function restorePlatformSession(): Promise<StoredPlatformSession | null> {
const stored = await readPlatformSession();
if (!stored) {
return null;
}
try {
const user = await getCurrentPlatformUser(stored.accessToken);
const session = { ...stored, user };
await writeSession(session);
return session;
} catch (error) {
if ((error as PlatformAuthError)?.status === 401 || (error as PlatformAuthError)?.status === 403) {
try {
return await refreshPlatformSession(stored.refreshToken);
} catch {
await clearPlatformSession();
return null;
}
}
throw error;
}
}
export async function loginPlatformSession(email: string, password: string): Promise<StoredPlatformSession> {
const response = await platformRequest<{
accessToken: string;
refreshToken: string;
user: PlatformAuthUser;
}>('/auth/login', {
method: 'POST',
body: {
email,
password,
productId: mobileRuntime.productId,
},
});
const session: StoredPlatformSession = {
accessToken: response.accessToken,
refreshToken: response.refreshToken,
user: normalizeUser(response.user),
};
await writeSession(session);
return session;
}