Official URL migration preserves sessions, shortcuts prefs, and last clock-in client; auth screens match web with legal links; time clock and invoice editor/send flows are updated for the new domain and UI patterns. Co-authored-by: Cursor <cursoragent@cursor.com>
110 lines
3.2 KiB
TypeScript
110 lines
3.2 KiB
TypeScript
import {
|
|
authStoragePrefix,
|
|
buildAccountId,
|
|
loadAccounts,
|
|
loadActiveAccountId,
|
|
loadDraftInstanceUrl,
|
|
saveAccounts,
|
|
saveActiveAccountId,
|
|
saveDraftInstanceUrl,
|
|
type SavedAccount,
|
|
} from "@/lib/accounts";
|
|
import { migrateAuthStorage } from "@/lib/auth-storage";
|
|
import { DEFAULT_API_URL } from "@/lib/config";
|
|
import { loadStoredInstanceUrl, saveStoredInstanceUrl } from "@/lib/instance-url";
|
|
import {
|
|
clearTimeClockPrefsForAccount,
|
|
getLastTimeClockClientId,
|
|
setLastTimeClockClientId,
|
|
} from "@/lib/time-clock-prefs";
|
|
|
|
const LEGACY_OFFICIAL_HOSTS = ["beenvoice.soconnor.dev"];
|
|
|
|
export function isLegacyOfficialUrl(url: string): boolean {
|
|
const trimmed = url.trim();
|
|
if (!trimmed) return false;
|
|
|
|
try {
|
|
const withProtocol = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
return LEGACY_OFFICIAL_HOSTS.includes(new URL(withProtocol).host);
|
|
} catch {
|
|
const host = trimmed.replace(/^https?:\/\//, "").replace(/\/$/, "").split("/")[0] ?? "";
|
|
return LEGACY_OFFICIAL_HOSTS.includes(host);
|
|
}
|
|
}
|
|
|
|
export function migrateOfficialUrl(url: string): string {
|
|
return isLegacyOfficialUrl(url) ? DEFAULT_API_URL : url;
|
|
}
|
|
|
|
export async function migrateStoredOfficialUrls(): Promise<{
|
|
accounts: SavedAccount[];
|
|
activeAccountId: string | null;
|
|
draftUrl: string | null;
|
|
}> {
|
|
const [accounts, activeId, draftUrl, storedInstanceUrl] = await Promise.all([
|
|
loadAccounts(),
|
|
loadActiveAccountId(),
|
|
loadDraftInstanceUrl(),
|
|
loadStoredInstanceUrl(),
|
|
]);
|
|
|
|
let nextActiveId = activeId;
|
|
const migratedAccounts: SavedAccount[] = [];
|
|
|
|
for (const account of accounts) {
|
|
if (!isLegacyOfficialUrl(account.instanceUrl)) {
|
|
migratedAccounts.push(account);
|
|
continue;
|
|
}
|
|
|
|
const newUrl = DEFAULT_API_URL;
|
|
const newId = buildAccountId(newUrl, account.userId);
|
|
const oldPrefix = authStoragePrefix(account.id);
|
|
const newPrefix = authStoragePrefix(newId);
|
|
|
|
if (oldPrefix !== newPrefix) {
|
|
await migrateAuthStorage(oldPrefix, newPrefix);
|
|
}
|
|
|
|
const lastClientId = await getLastTimeClockClientId(account.id);
|
|
if (lastClientId && account.id !== newId) {
|
|
await setLastTimeClockClientId(newId, lastClientId);
|
|
await clearTimeClockPrefsForAccount(account.id);
|
|
}
|
|
|
|
migratedAccounts.push({
|
|
...account,
|
|
id: newId,
|
|
instanceUrl: newUrl,
|
|
});
|
|
|
|
if (nextActiveId === account.id) {
|
|
nextActiveId = newId;
|
|
}
|
|
}
|
|
|
|
const newDraft = draftUrl && isLegacyOfficialUrl(draftUrl) ? DEFAULT_API_URL : draftUrl;
|
|
const accountsChanged = migratedAccounts.some(
|
|
(account, index) =>
|
|
account.id !== accounts[index]?.id || account.instanceUrl !== accounts[index]?.instanceUrl,
|
|
);
|
|
const activeChanged = nextActiveId !== activeId;
|
|
const draftChanged = newDraft !== draftUrl;
|
|
const instanceChanged =
|
|
storedInstanceUrl != null &&
|
|
isLegacyOfficialUrl(storedInstanceUrl) &&
|
|
storedInstanceUrl !== DEFAULT_API_URL;
|
|
|
|
if (accountsChanged) await saveAccounts(migratedAccounts);
|
|
if (activeChanged) await saveActiveAccountId(nextActiveId);
|
|
if (draftChanged) await saveDraftInstanceUrl(newDraft);
|
|
if (instanceChanged) await saveStoredInstanceUrl(DEFAULT_API_URL);
|
|
|
|
return {
|
|
accounts: migratedAccounts,
|
|
activeAccountId: nextActiveId,
|
|
draftUrl: newDraft,
|
|
};
|
|
}
|