Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions frontend/src/lib/security-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* Security settings API (#1508, #1509, #1510).
*
* The security settings page used hard-coded mock sessions, a local-only
* "Revoke", a 2FA toggle that only flipped local state, and "Delete account"
* that signed the user out without deleting anything. These calls back each
* action with the real API.
*/
import { apiUrl } from "./api-version";

export interface SessionInfo {
id: string;
device?: string;
lastActive?: string;
}

async function expectOk(
path: string,
init: RequestInit,
fetchImpl: typeof fetch,
): Promise<Response> {
const response = await fetchImpl(apiUrl(path), {
credentials: "include",
...init,
});
if (!response.ok) {
throw new Error(`${path} failed (${response.status})`);
}
return response;
}

export async function listSessions(
fetchImpl: typeof fetch = fetch,
): Promise<SessionInfo[]> {
const response = await expectOk("/auth/sessions", {}, fetchImpl);
return (await response.json()) as SessionInfo[];
}

export async function revokeSession(
id: string,
fetchImpl: typeof fetch = fetch,
): Promise<void> {
await expectOk(`/auth/sessions/${id}`, { method: "DELETE" }, fetchImpl);
}

export async function setTwoFactor(
enabled: boolean,
fetchImpl: typeof fetch = fetch,
): Promise<void> {
await expectOk(
"/auth/2fa",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ enabled }),
},
fetchImpl,
);
}

export async function deleteAccount(
fetchImpl: typeof fetch = fetch,
): Promise<void> {
await expectOk("/auth/account", { method: "DELETE" }, fetchImpl);
}
46 changes: 46 additions & 0 deletions frontend/src/lib/upload-progress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Upload progress tracking that cleans up after itself (#1511).
*
* The upload page faked progress and leaked its `setInterval` on errors. This
* tracker owns its timer and guarantees it is cleared on completion or failure.
*/
export interface ProgressTracker {
set(value: number): void;
done(): void;
fail(): void;
}

export function createProgressTracker(
onProgress: (value: number) => void,
intervalMs = 250,
schedule: (fn: () => void, ms: number) => ReturnType<typeof setInterval> =
setInterval,
clear: (handle: ReturnType<typeof setInterval>) => void = clearInterval,
): ProgressTracker {
let value = 0;
let handle: ReturnType<typeof setInterval> | null = schedule(() => {
value = Math.min(value + 5, 95);
onProgress(value);
}, intervalMs);

const stop = (): void => {
if (handle !== null) {
clear(handle);
handle = null;
}
};

return {
set(next: number): void {
value = Math.min(Math.max(next, 0), 100);
onProgress(value);
},
done(): void {
stop();
onProgress(100);
},
fail(): void {
stop();
},
};
}
Loading