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>
27 lines
929 B
TypeScript
27 lines
929 B
TypeScript
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;
|
|
}
|
|
}
|