refactor(web): DRY platform-sync.ts — remove 88 lines of hand-rolled auth, delegate to auth-client
This commit is contained in:
parent
8a5a40676a
commit
f80602d2b1
@ -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<string, unknown>).__PLATFORM_URL__) {
|
||||
return (window as unknown as Record<string, unknown>).__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<boolean> | 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<boolean> {
|
||||
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<T>(
|
||||
method: string,
|
||||
body?: unknown
|
||||
): Promise<T> {
|
||||
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<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
'x-request-id': crypto.randomUUID(),
|
||||
@ -153,7 +85,7 @@ async function apiRequest<T>(
|
||||
};
|
||||
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<T>(
|
||||
|
||||
// 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<T>(path, method, body);
|
||||
}
|
||||
@ -180,26 +112,19 @@ async function apiRequest<T>(
|
||||
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 {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user