Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 10x 10x 6x 6x 6x 6x 6x 6x 6x 6x 5x 5x 6x 6x 5x 5x 6x 3x 3x 6x 3x 6x 6x 6x 6x 6x 6x 10x 10x 10x 10x | import { CACHE } from '@/constants/common';
import type { ReleaseSection } from '@/helpers/updates';
import { parseReleaseNotes } from '@/helpers/updates';
import { cstorage } from '@/utils/storage';
import { useCallback, useEffect, useState } from 'react';
import semverValid from 'semver/functions/valid';
import semverGt from 'semver/functions/gt';
import { Logger } from '@/utils/Logger';
import { getErrorMessage } from '@/helpers/exceptions';
const logger = new Logger('useJustUpdated');
/**
* A release the user has just moved onto, with the notes that shipped in it.
* @group Types
* @category Hooks
*/
export interface JustUpdatedNotice {
/** The version now running, without a leading `v`. */
version: string;
/** The version this profile was on before, for context. */
previousVersion: string;
/** Highlights from this build's own changelog section; never empty. */
notes: ReleaseSection[];
}
/**
* Return shape of {@link useJustUpdated}.
*/
interface UseJustUpdated {
/** The just-installed release, or `undefined` when there's nothing to announce. */
notice: JustUpdatedNotice | undefined;
/** Marks the notes as seen so they don't reappear on the next open. */
acknowledge: () => void;
}
/**
* Announces what changed after the extension updates itself.
*
* Works by comparing the running `__APP_VERSION__` against the last version this
* profile opened. The notes come from `__CHANGELOG_CURRENT__` — this build's own
* changelog section, baked in at build time — so the prompt needs no network and
* shows exactly the entries published for the release.
*
* Two cases deliberately stay silent: a **fresh install** (no previous version
* recorded — a changelog means nothing to a first-time user) and a **downgrade
* or re-install** of the same version. The version is recorded on first sight
* either way, so each release announces itself at most once.
* @returns The pending {@link JustUpdatedNotice} plus an `acknowledge` action.
* @category Hooks
* @example
* ```tsx
* const { notice, acknowledge } = useJustUpdated();
* // after updating 1.2.0 -> 1.3.0:
* // notice → { version: "1.3.0", previousVersion: "1.2.0", notes: [...] }
* ```
* @source
*/
export function useJustUpdated(): UseJustUpdated {
const [notice, setNotice] = useState<JustUpdatedNotice | undefined>(undefined);
useEffect(() => {
let cancelled = false;
const check = async () => {
try {
const stored = await cstorage.local.get([CACHE.LAST_SEEN_VERSION]);
const previousVersion = stored[CACHE.LAST_SEEN_VERSION];
// Record the current version regardless, so this only ever fires once
// per release even if the user closes the popup without acknowledging.
if (previousVersion !== __APP_VERSION__) {
await cstorage.local.set({ [CACHE.LAST_SEEN_VERSION]: __APP_VERSION__ });
}
// Fresh install: nothing to compare against, and no history to summarize.
if (typeof previousVersion !== 'string') return;
const from = semverValid(previousVersion);
const to = semverValid(__APP_VERSION__);
if (!from || !to || !semverGt(to, from)) return;
const notes = parseReleaseNotes(__CHANGELOG_CURRENT__);
// With no changelog section there is nothing worth interrupting for.
if (notes.length === 0 || cancelled) return;
setNotice({ version: to, previousVersion: from, notes });
} catch (error) {
logger.error(`Failed to check for a just-installed update: ${getErrorMessage(error)}`, {
error,
});
}
};
void check();
return () => {
cancelled = true;
};
}, []);
const acknowledge = useCallback(() => setNotice(undefined), []);
return { notice, acknowledge };
}
|