From f80602d2b14f550ea78f570ff6a00f6c865afe2c Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Sat, 28 Feb 2026 11:49:06 -0800 Subject: [PATCH] =?UTF-8?q?refactor(web):=20DRY=20platform-sync.ts=20?= =?UTF-8?q?=E2=80=94=20remove=2088=20lines=20of=20hand-rolled=20auth,=20de?= =?UTF-8?q?legate=20to=20auth-client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/lib/platform-sync.ts | 101 +++++------------------------------ 1 file changed, 13 insertions(+), 88 deletions(-) diff --git a/web/src/lib/platform-sync.ts b/web/src/lib/platform-sync.ts index 226d786..7af9687 100644 --- a/web/src/lib/platform-sync.ts +++ b/web/src/lib/platform-sync.ts @@ -54,11 +54,9 @@ export interface OfflineQueueItem { enqueuedAt: number; } -// ── API Client ──────────────────────────────────────────────── +// ── API Client (delegated to @bytelyst/auth-client) ────────── const STORAGE_KEYS = { - authToken: 'chronomind-auth-token', - refreshToken: 'chronomind-refresh-token', lastSync: 'chronomind-platform-last-sync', offlineQueue: 'chronomind-offline-queue', syncEnabled: 'chronomind-platform-sync-enabled', @@ -66,77 +64,9 @@ const STORAGE_KEYS = { export const PRODUCT_ID = _PRODUCT_ID; -function getBaseUrl(): string { - if (typeof window !== 'undefined' && (window as unknown as Record).__PLATFORM_URL__) { - return (window as unknown as Record).__PLATFORM_URL__ as string; - } - return process.env.NEXT_PUBLIC_PLATFORM_SERVICE_URL ?? 'https://api.chronomind.app'; -} - -function getAuthToken(): string | null { - if (typeof window === 'undefined') return null; - return localStorage.getItem(STORAGE_KEYS.authToken); -} - -function getRefreshToken(): string | null { - if (typeof window === 'undefined') return null; - return localStorage.getItem(STORAGE_KEYS.refreshToken); -} - -export function setRefreshToken(token: string | null): void { - if (typeof window === 'undefined') return; - if (token) { - localStorage.setItem(STORAGE_KEYS.refreshToken, token); - } else { - localStorage.removeItem(STORAGE_KEYS.refreshToken); - } -} - -let _refreshPromise: Promise | null = null; - -/** - * Attempt to refresh the access token using the stored refresh token. - * Returns true if refresh succeeded, false otherwise. - * Deduplicates concurrent refresh attempts. - */ -export async function refreshAccessToken(): Promise { - if (_refreshPromise) return _refreshPromise; - - _refreshPromise = (async () => { - const rt = getRefreshToken(); - if (!rt) return false; - - try { - const res = await fetch(`${getBaseUrl()}/auth/refresh`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'x-product-id': PRODUCT_ID, - 'x-request-id': crypto.randomUUID(), - }, - body: JSON.stringify({ refreshToken: rt }), - }); - - if (!res.ok) { - // Refresh token is invalid/expired — clear both tokens - setAuthToken(null); - setRefreshToken(null); - return false; - } - - const data = await res.json() as { accessToken: string; refreshToken: string }; - setAuthToken(data.accessToken); - setRefreshToken(data.refreshToken); - return true; - } catch { - return false; - } - })(); - - try { - return await _refreshPromise; - } finally { - _refreshPromise = null; +export class SyncConflictError extends Error { + constructor(public serverData: unknown) { + super('Sync conflict — server has newer version'); } } @@ -145,7 +75,9 @@ async function apiRequest( method: string, body?: unknown ): Promise { - const token = getAuthToken(); + const client = getAuthClient(); + const token = client.getAccessToken(); + const baseUrl = process.env.NEXT_PUBLIC_PLATFORM_SERVICE_URL ?? 'https://api.chronomind.app'; const headers: Record = { 'Content-Type': 'application/json', 'x-request-id': crypto.randomUUID(), @@ -153,7 +85,7 @@ async function apiRequest( }; if (token) headers['Authorization'] = `Bearer ${token}`; - const res = await fetch(`${getBaseUrl()}${path}`, { + const res = await fetch(`${baseUrl}${path}`, { method, headers, body: body ? JSON.stringify(body) : undefined, @@ -166,7 +98,7 @@ async function apiRequest( // On 401, attempt a silent token refresh and retry once if (res.status === 401 && !path.startsWith('/auth/')) { - const refreshed = await refreshAccessToken(); + const refreshed = await client.refreshAccessToken(); if (refreshed) { return apiRequest(path, method, body); } @@ -180,26 +112,19 @@ async function apiRequest( return res.json(); } -export class SyncConflictError extends Error { - constructor(public serverData: unknown) { - super('Sync conflict — server has newer version'); - } -} - // ── Public API ──────────────────────────────────────────────── export function setAuthToken(token: string | null): void { - if (typeof window === 'undefined') return; + const client = getAuthClient(); if (token) { - localStorage.setItem(STORAGE_KEYS.authToken, token); + client.setTokens(token, client.getRefreshToken() ?? ''); } else { - localStorage.removeItem(STORAGE_KEYS.authToken); - localStorage.removeItem(STORAGE_KEYS.refreshToken); + client.clearTokens(); } } export function isAuthenticated(): boolean { - return getAuthToken() !== null; + return getAuthClient().isAuthenticated(); } export function isSyncEnabled(): boolean {