123declare const __PEACH_VERSION__: string;45export type ReleaseImportance = "breaking" | "critical";6export interface ReleaseNote {7 heading: string;8 body: string;9}1011export const PEACH_VERSION: string = typeof __PEACH_VERSION__ !== "undefined"12 ? __PEACH_VERSION__13 : "0.0.0";1415export interface PeachRelease {16 version: string;17 releasedAt: string;18 notes: ReleaseNote[];19 importance?: ReleaseImportance;20}21export const RELEASES: PeachRelease[] = [22 {23 version: "5.2.51",24 releasedAt: "2026-09-11",25 notes: [{26 heading: "Keep Sync security alerts until dismissal",27 body: "Device-key and revocation alerts remain queued until you dismiss them, so closing the popup cannot hide a trust event.",28 }],29 },3031 {32 version: "5.2.49",33 releasedAt: "2026-09-11",34 notes: [{35 heading: "Make Sync reset resilient",36 body: "Sync reset no longer fails when the browser cannot refresh its session-only identity cache; the durable identity remains authoritative.",37 }],38 },39 {40 version: "5.2.48",41 releasedAt: "2026-09-11",42 notes: [{43 heading: "Complete locked-vault Sync resets",44 body: "Sync reset now unlocks the real vault before rotating its local identity when Settings is opened after the vault session expired.",45 }],46 },47 {48 version: "5.2.46",49 releasedAt: "2026-09-11",50 importance: "breaking",51 notes: [{52 heading: "A major Sync security update",53 body: "We're sorry for the extra step. This major security update makes Sync and device Revocation meaningfully more secure, but it requires a deliberate repair: 1) update Peach on every device; 2) open Sync settings on the device showing the reset notice; 3) enter your master password and complete Reset Sync; 4) pair or re-pair every device individually. Old device trust will not continue automatically. Your vault and recovery words are unchanged.",54 }],55 },56 { version: "5.2.17", releasedAt: "2026-09-07", importance: "breaking", notes: [{ heading: "Sync may need device repair", body: "If sync starts failing after this security update, revoke the affected devices and pair them again. A recent security change may require repairing device trust before sync can resume." }] },57 { version: "5.2.6", releasedAt: "2026-09-06", notes: [{ heading: "Release history", body: "Settings now has an accurate, versioned changelog. Important notices combine every critical or breaking change since your previous installed version." }] },58 { version: "5.2.5", releasedAt: "2026-09-06", notes: [{ heading: "Quieter update notices", body: "Routine updates no longer replay unchanged What's New information." }] },59 { version: "5.2.4", releasedAt: "2026-09-06", notes: [{ heading: "Chrome passkey sign-in", body: "Older Peach passkeys work again on GitHub and other strict sites." }] },60 { version: "5.2.3", releasedAt: "2026-09-06", notes: [{ heading: "Inline entry creation", body: "The + button keeps new-entry creation inside the unlocked page helper." }] },61 { version: "5.2.2", releasedAt: "2026-09-06", notes: [{ heading: "Inline editing", body: "Entry editing stays in the page helper, and password-limit guidance is readable." }] },62 {63 version: "5.1.3", releasedAt: "2026-09-05", importance: "breaking",64 notes: [65 { heading: "Passphrases & long passwords", body: "Peach supports passphrases and passwords up to 256 characters." },66 { heading: "Update Android before using Vault V4", body: "Vault V4 requires Peach Android v2.15.3 or newer. Update Android and export a backup there before moving to Vault V4." },67 ],68 },69];7071export function hasReleaseNotes(): boolean { return RELEASES.length > 0; }7273const UPGRADE_FROM_KEY = "peach_release_notes_from";74const LEGACY_SEEN_KEY = "peach_whats_new_seen";7576function versionParts(version: string): [number, number, number] | null {77 const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version);78 return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;79}8081export function compareVersions(left: string, right: string): number {82 const a = versionParts(left); const b = versionParts(right);83 if (!a || !b) return 0;84 for (let index = 0; index < 3; index += 1) if (a[index] !== b[index]) return a[index]! - b[index]!;85 return 0;86}8788export function importantReleasesSince(version: string): PeachRelease[] {89 const currentVersion = PEACH_VERSION === "0.0.0" ? RELEASES[0]!.version : PEACH_VERSION;90 return selectImportantReleases(RELEASES, version, currentVersion);91}9293export function selectImportantReleases(94 releases: PeachRelease[],95 fromVersion: string,96 throughVersion: string,97): PeachRelease[] {98 return releases.filter((release) =>99 release.importance !== undefined &&100 compareVersions(release.version, fromVersion) > 0 &&101 compareVersions(release.version, throughVersion) <= 0102 );103}104105export async function recordInstalledVersion(details: chrome.runtime.InstalledDetails): Promise<void> {106 if (details.reason !== "update" || !details.previousVersion) return;107 const stored = await chrome.storage.local.get(UPGRADE_FROM_KEY);108 if (stored[UPGRADE_FROM_KEY] === undefined) await chrome.storage.local.set({ [UPGRADE_FROM_KEY]: details.previousVersion });109}110111export async function pendingImportantReleases(): Promise<PeachRelease[]> {112 try {113 const stored = await chrome.storage.local.get([UPGRADE_FROM_KEY, LEGACY_SEEN_KEY]);114 const from = stored[UPGRADE_FROM_KEY];115 return typeof from === "string" && versionParts(from) ? importantReleasesSince(from) : [];116 } catch { return []; }117}118119export async function shouldShowWhatsNew(): Promise<boolean> {120 return (await pendingImportantReleases()).length > 0;121}122123export async function markWhatsNewSeen(): Promise<void> {124 try {125 await chrome.storage.local.remove([UPGRADE_FROM_KEY, LEGACY_SEEN_KEY]);126 } catch {127 128 }129}