Merges Renovate's kit 3 (PR #149) and adapter-static 4 (PR #148) bumps, plus the migration they need. The mechanical part is `sv migrate sveltekit-3`, run one task at a time and reviewed: - svelte.config.js is gone. Its options move into sveltekit() in vite.config.ts, exported as kitOptions so vitest.config.ts runs the same kit setup, including the $test-utils alias the tests import. - $lib becomes #lib through package.json "imports". There is no src/lib/index, so only the "#lib/*" entry is kept. - tsconfig extends $app/tsconfig. - Peer floors raised to kit 3's requirements: svelte ^5.57.1, vite ^8.0.12, svelte-check ^4.7.5. By hand, from the codemod's list of non-automated tasks: - goto's replaceState option is now replace; keepFocus becomes reset: false. For the search typeahead, reset: false also stops the scroll-to-top, which is wanted while typing. - The test setup mocks drop pushState/replaceState and $app/paths base/assets, which kit 3 removed, and mock refreshAll in place of invalidateAll. - The other flagged files only read page.url or goto internal routes, so they needed no change. TypeScript goes to ^6, not the ^7 Renovate offers: kit 3 declares typescript ^6 as a peer and svelte-check 4.7 accepts ^5 || ^6. Move to 7 once both accept it. With Tailwind 4 and kit 3 in, `npm audit` on the whole tree reports 0, so the web lane now audits every dependency rather than only what ships to browsers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
99 lines
3.3 KiB
TypeScript
99 lines
3.3 KiB
TypeScript
import { api, type User, type LoginResponse } from '#lib/api/client.js';
|
|
import { putMyTimezone } from '#lib/api/me.js';
|
|
import { queryClient } from '#lib/query/client.js';
|
|
import { signalSessionEnd } from './sessionEnd.svelte';
|
|
import { user, setUser } from './user.svelte';
|
|
|
|
// Re-export so existing `import { user } from '#lib/auth/store.svelte'`
|
|
// callers keep working. New code can import directly from auth/user.svelte.
|
|
export { user };
|
|
|
|
// Weekly client-driven cadence for sending the browser's current IANA
|
|
// timezone to PUT /api/me/timezone (#392 Half B). Tracked in
|
|
// localStorage so the cadence survives tab restarts; bumped after each
|
|
// successful PUT. Failures are swallowed — server keeps its previous
|
|
// value (or 'UTC' default).
|
|
const TZ_LAST_SENT_KEY = 'minstrel.tz_last_sent_at';
|
|
const WEEKLY_MS = 7 * 24 * 60 * 60 * 1000;
|
|
|
|
async function sendTimezoneIfStale(): Promise<void> {
|
|
if (typeof window === 'undefined') return; // SSR-safe no-op
|
|
try {
|
|
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
if (!tz) return;
|
|
const lastStr = window.localStorage.getItem(TZ_LAST_SENT_KEY);
|
|
const last = lastStr ? Number(lastStr) : 0;
|
|
if (Number.isFinite(last) && Date.now() - last < WEEKLY_MS) return;
|
|
await putMyTimezone(tz);
|
|
window.localStorage.setItem(TZ_LAST_SENT_KEY, String(Date.now()));
|
|
} catch (err) {
|
|
console.warn('tz send failed:', err);
|
|
}
|
|
}
|
|
|
|
export async function bootstrap(): Promise<void> {
|
|
try {
|
|
setUser(await api.get<User>('/api/me'));
|
|
// ignore: best-effort, runs in background
|
|
void sendTimezoneIfStale();
|
|
} catch {
|
|
setUser(null);
|
|
}
|
|
}
|
|
|
|
export async function login(username: string, password: string): Promise<void> {
|
|
const res = await api.post<LoginResponse>('/api/auth/login', { username, password });
|
|
setUser(res.user);
|
|
void sendTimezoneIfStale();
|
|
}
|
|
|
|
/**
|
|
* Whether the server has no accounts yet, in which case the first
|
|
* registration must carry the setup token printed in the server log.
|
|
*/
|
|
export async function getSetupStatus(): Promise<{ setup_required: boolean }> {
|
|
return api.get<{ setup_required: boolean }>('/api/auth/setup-status');
|
|
}
|
|
|
|
export async function register(opts: {
|
|
username: string;
|
|
password: string;
|
|
inviteToken?: string;
|
|
setupToken?: string;
|
|
displayName?: string;
|
|
}): Promise<void> {
|
|
const body: Record<string, string> = {
|
|
username: opts.username,
|
|
password: opts.password,
|
|
};
|
|
if (opts.inviteToken) body.invite_token = opts.inviteToken;
|
|
if (opts.setupToken) body.setup_token = opts.setupToken;
|
|
if (opts.displayName) body.display_name = opts.displayName;
|
|
const res = await api.post<LoginResponse>('/api/auth/register', body);
|
|
setUser(res.user);
|
|
await bootstrap();
|
|
void sendTimezoneIfStale();
|
|
}
|
|
|
|
export async function forgotPassword(email: string): Promise<void> {
|
|
await api.post('/api/auth/forgot-password', { email });
|
|
}
|
|
|
|
export async function resetPassword(token: string, newPassword: string): Promise<void> {
|
|
await api.post('/api/auth/reset-password', { token, new_password: newPassword });
|
|
}
|
|
|
|
export async function logout(opts: { silent?: boolean } = {}): Promise<void> {
|
|
const userId = user.value?.id;
|
|
if (!opts.silent) {
|
|
try {
|
|
await api.post('/api/auth/logout', {});
|
|
} catch {
|
|
// best effort — server-side session may already be gone
|
|
}
|
|
}
|
|
signalSessionEnd(userId ?? null);
|
|
setUser(null);
|
|
queryClient.clear();
|
|
}
|