const fullCommitShaPattern = /^[0-9a-f]{40,64}$/iu; type VersionStorage = Pick; export type DeploymentVersionCheckerOptions = { currentCommitSha: string; versionUrl: string; fetchVersion?: typeof fetch; storage?: VersionStorage; onVersionChanged: (availableCommitSha: string) => void; }; export const deploymentVersionAssetSource = (buildCommitSha: string): string => `${JSON.stringify({ commitSha: buildCommitSha })}\n`; export const parseDeploymentCommitSha = (payload: unknown): string | null => { if (!payload || typeof payload !== 'object' || !('commitSha' in payload)) return null; const commitSha = String(payload.commitSha).trim().toLowerCase(); return fullCommitShaPattern.test(commitSha) ? commitSha : null; }; const notificationStorageKey = (versionUrl: string, availableCommitSha: string): string => `sammo:deployment-version-notice:${versionUrl}:${availableCommitSha}`; export const createDeploymentVersionChecker = (options: DeploymentVersionCheckerOptions) => { const currentCommitSha = options.currentCommitSha.trim().toLowerCase(); const fetchVersion = options.fetchVersion ?? fetch; let inFlight: Promise | null = null; let lastNotifiedCommitSha: string | null = null; const wasNotified = (key: string): boolean => { try { return options.storage?.getItem(key) === '1'; } catch { return false; } }; const rememberNotification = (key: string): void => { try { options.storage?.setItem(key, '1'); } catch { // Session storage can be unavailable under restrictive browser policies. } }; const run = async (): Promise => { if (!fullCommitShaPattern.test(currentCommitSha)) return; const response = await fetchVersion(options.versionUrl, { cache: 'no-cache', }); if (!response.ok) return; const availableCommitSha = parseDeploymentCommitSha(await response.json()); if (!availableCommitSha || availableCommitSha === currentCommitSha) return; const storageKey = notificationStorageKey(options.versionUrl, availableCommitSha); if (lastNotifiedCommitSha === availableCommitSha || wasNotified(storageKey)) return; lastNotifiedCommitSha = availableCommitSha; rememberNotification(storageKey); options.onVersionChanged(availableCommitSha); }; return { check: (): Promise => { if (inFlight) return inFlight; inFlight = run() .catch(() => undefined) .finally(() => { inFlight = null; }); return inFlight; }, }; };