v3: src/ is documented like api/ and shared/, and the reference covers the whole repository — the frontend had no module docs, JSDoc on some components and hooks and not on others, props typed inline in half the components and in private interfaces in the rest, // comments on declarations, and no return types on 147 exported functions, so deno doc --lint over src/ found 637 errors. The rule api/ follows is applied to every non-test module of src/: a /** … @module */ block, JSDoc on every export and every field of an exported interface, /** */ for declarations and // for notes, verbs for functions ("Renders…" for a component), parameters named in the prose, {@link} only for symbols in scope, and a page's JSDoc names its route and who may see it (App.tsx's module doc lists the route table). For deno doc to show them, every exported function states its return type: components React.JSX.Element (or | null, or a portal), memo and forwardRef components their exotic types, contexts React.Context<…>, and a hook returning several things a documented Use<Name>Result interface (useAuth, useRequiredAuth, useFading, useEmojiTrigger, useMentionAutocomplete, useDumpCardLink, usePlayRichContent; FeedCacheResult, YouTubePlaylistControls and UseApiFormReturn are renamed to match). Every component that takes props takes an exported, documented <Name>Props interface — the 64 inline prop types are lifted into one, DumpFeed gets DumpFeedProps extending MainFeedProps. React types go through the global React namespace: the four default imports of react are gone, since deno doc can't follow them. Two duplicates go too: useMentionAutocomplete's UserResult was shared/'s UserSuggestion, and JournalCard re-exported JournalShape for no one; i18n.ts no longer re-exports Lingui's i18n, which its three users now import from @lingui/core. deno task doc and doc:lint now take src/ (but its tests and the generated locales), the index page gains a Frontend section linking every module by layer and domain, mod.test.ts walks src/ as well, and the docs workflow runs on changes to src/. deno doc doesn't document npm packages, so it takes a type imported from one — react-hook-form's FieldValues in the form fields, frimousse's SkinTone — for a private one; doc:lint now goes through doc-lint.ts, which fails on every diagnostic but those, 25 of them. The README states the frontend's part of the rule. No behaviour changes; 205 backend and 628 frontend tests pass, and tsc, deno check and deno lint are clean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,14 @@
|
||||
/**
|
||||
* The session, and requests made with it.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { useCallback, useContext } from "react";
|
||||
|
||||
import { AuthContext } from "../../contexts/auth/AuthContext.ts";
|
||||
|
||||
import type { AuthResponse } from "../../../shared/user.ts";
|
||||
import type { AuthResponse, SelfUser } from "../../../shared/user.ts";
|
||||
import type { Session } from "../../utils/auth/storage.ts";
|
||||
import {
|
||||
forgetStoredSession,
|
||||
@@ -10,7 +16,56 @@ import {
|
||||
storeSession,
|
||||
} from "../../utils/auth/storage.ts";
|
||||
|
||||
export const useAuth = () => {
|
||||
/** What {@link useAuth} returns. */
|
||||
export interface UseAuthResult {
|
||||
/** The signed-in user, null when signed out. */
|
||||
user: SelfUser | null;
|
||||
/** The session token. */
|
||||
token: string | null;
|
||||
/**
|
||||
* The token for file and thumbnail URLs, where it is visible in the query
|
||||
* string. Sessions from before media tokens fall back to the session token
|
||||
* until AuthProvider fetches one.
|
||||
*/
|
||||
mediaToken: string | null;
|
||||
/** Starts a session from a sign-in or sign-up response, in every tab. */
|
||||
login: (response: AuthResponse) => void;
|
||||
/**
|
||||
* Ends the session this render holds. A 401 can land after the session has
|
||||
* moved on to fresh tokens (a password change, here or in another tab), and
|
||||
* must not sign those out.
|
||||
*/
|
||||
logout: () => void;
|
||||
/** Merges `patch` into the current session, in memory and in storage. */
|
||||
updateSession: (patch: Partial<Session>) => void;
|
||||
/**
|
||||
* `fetch` with the session's token, and a JSON content type unless the body
|
||||
* is FormData. An expired token, or a 401, ends the session; the former
|
||||
* answers a synthetic 401 without calling the server.
|
||||
*/
|
||||
authFetch: (input: RequestInfo, init?: RequestInit) => Promise<Response>;
|
||||
/**
|
||||
* `authFetch` for multipart uploads that need a progress bar.
|
||||
*
|
||||
* `fetch` reports nothing until the whole body is on the wire, which for a 50
|
||||
* MB dump on a phone is a long silence — so this goes through XHR, whose
|
||||
* `upload.progress` events fire as bytes leave. The result is wrapped back
|
||||
* into a real `Response` so callers keep using `expectOk` unchanged.
|
||||
*
|
||||
* `onProgress` receives a 0–1 fraction, or `null` once the body is fully sent
|
||||
* and we are waiting on the server (the length is unknown for chunked bodies,
|
||||
* and "100%, still waiting" reads as a stall).
|
||||
*/
|
||||
authUpload: (
|
||||
url: string,
|
||||
body: FormData,
|
||||
onProgress: (fraction: number | null) => void,
|
||||
method?: string,
|
||||
) => Promise<Response>;
|
||||
}
|
||||
|
||||
/** Returns the session and what acts on it. */
|
||||
export function useAuth(): UseAuthResult {
|
||||
const { authResponse, setAuthResponse } = useContext(AuthContext);
|
||||
|
||||
const login = useCallback((response: AuthResponse) => {
|
||||
@@ -27,7 +82,6 @@ export const useAuth = () => {
|
||||
forgetStoredSession(sessionToken);
|
||||
}, [setAuthResponse, sessionToken]);
|
||||
|
||||
/** Merge into the current session, in memory and in storage. */
|
||||
const updateSession = useCallback((patch: Partial<Session>) => {
|
||||
setAuthResponse((prev) => {
|
||||
if (!prev) return prev;
|
||||
@@ -70,18 +124,6 @@ export const useAuth = () => {
|
||||
return res;
|
||||
}, [authResponse?.token, logout]);
|
||||
|
||||
/**
|
||||
* `authFetch` for multipart uploads that need a progress bar.
|
||||
*
|
||||
* `fetch` reports nothing until the whole body is on the wire, which for a
|
||||
* 50 MB dump on a phone is a long silence — so this goes through XHR, whose
|
||||
* `upload.progress` events fire as bytes leave. The result is wrapped back
|
||||
* into a real `Response` so callers keep using `expectOk` unchanged.
|
||||
*
|
||||
* `onProgress` receives a 0–1 fraction, or `null` once the body is fully sent
|
||||
* and we are waiting on the server (the length is unknown for chunked bodies,
|
||||
* and "100%, still waiting" reads as a stall).
|
||||
*/
|
||||
const authUpload = useCallback((
|
||||
url: string,
|
||||
body: FormData,
|
||||
@@ -133,9 +175,6 @@ export const useAuth = () => {
|
||||
return {
|
||||
user: authResponse?.user ?? null,
|
||||
token: authResponse?.token ?? null,
|
||||
// For file and thumbnail URLs, where the token is visible in the query
|
||||
// string. Sessions from before media tokens fall back to the session token
|
||||
// until AuthProvider fetches one.
|
||||
mediaToken: authResponse?.mediaToken ?? authResponse?.token ?? null,
|
||||
login,
|
||||
logout,
|
||||
@@ -143,9 +182,20 @@ export const useAuth = () => {
|
||||
authFetch,
|
||||
authUpload,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export const useRequiredAuth = () => {
|
||||
/** What {@link useRequiredAuth} returns: {@link UseAuthResult} with a user. */
|
||||
export interface UseRequiredAuthResult extends UseAuthResult {
|
||||
/** The signed-in user. */
|
||||
user: SelfUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the session, for a page behind a sign-in.
|
||||
*
|
||||
* @throws {Error} When no one is signed in: the route should have guarded it.
|
||||
*/
|
||||
export function useRequiredAuth(): UseRequiredAuthResult {
|
||||
const {
|
||||
user,
|
||||
token,
|
||||
@@ -172,4 +222,4 @@ export const useRequiredAuth = () => {
|
||||
authFetch,
|
||||
authUpload,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user