fix(offline): address PR #120 review (cross-user leak, Yjs title dup, i18n, docs, guards)
Security: - Clear the offline IndexedDB cache on sign-in (not only logout) so a previous user's persisted query cache and Yjs page bodies cannot leak to the next user on a shared device when the prior session ended without an explicit logout. Regressions: - Remove the double Yjs title write from the AI title-generation path: the title editor is bound to the Yjs `title` fragment and the server REST update reseeds it, so the local setContent raced that reseed and doubled/garbled the title. Conventions / i18n / docs: - Remove the unused showAiMenuAtom. - Register the 3 offline-fallback strings in en-US and ru-RU. - Fix the 5 broken links to the nonexistent docs/offline-sync-plan.md. Stability / simplification: - warmInfiniteAll now reports truncation (returns false) when it hits maxPages with a cursor still pending instead of silently succeeding. - space-tree make-offline catch logs the raw error and surfaces the real cause. - Move the Offline/Mobile/CORS CHANGELOG entries from the released 0.93.0 section into [Unreleased] (CORS is a documented breaking change). - Drop the pass-through sync-flag forwarders in use-page-collab-providers; set the atoms directly. - Collapse the three isSwaggerEnabled true-cases into it.each. Tests / architecture: - Extract collabTokenNeedsRefresh (pure) and cover all four token states. - Extract shouldPropagateTitleChange and cover the collab-origin skip; add a TitleEditor render test for the static-h1 vs collaborative-editor switch. - Add a use-auth test asserting the sign-in cache purge runs before login. - Add an OFFLINE_PERSIST_ROOTS guard test asserting every persisted root maps to an exported query-key factory; route make-offline's currentUser warm through a new userKeys factory. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
26
apps/client/src/features/editor/hooks/collab-token.ts
Normal file
26
apps/client/src/features/editor/hooks/collab-token.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { jwtDecode } from "jwt-decode";
|
||||
|
||||
/**
|
||||
* Decide whether a collab token must be refreshed before reconnecting after an
|
||||
* onAuthenticationFailed event. Pure and side-effect free so the four token
|
||||
* states can be unit-tested directly:
|
||||
* - no token -> true (fetch a fresh one and reconnect)
|
||||
* - undecodable/malformed -> true (jwtDecode throws -> refresh)
|
||||
* - valid, not expired -> false (token is still good; do NOT reconnect)
|
||||
* - valid, expired -> true (refresh + reconnect)
|
||||
*
|
||||
* `nowMs` is injectable for deterministic tests; it defaults to `Date.now()`.
|
||||
*/
|
||||
export function collabTokenNeedsRefresh(
|
||||
token: string | undefined,
|
||||
nowMs: number = Date.now(),
|
||||
): boolean {
|
||||
if (!token) return true;
|
||||
try {
|
||||
const payload = jwtDecode<{ exp: number }>(token);
|
||||
return nowMs / 1000 >= payload.exp;
|
||||
} catch {
|
||||
// malformed/undecodable token -> refresh
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user