mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 13:36:48 +00:00
8f6ed02cff
After an update the service worker keeps serving the previous bundle until the new one is installed. That shell announces the release layout too, so the layout alone let it draw the notice with its own older texts and use it up for the version now running with one click on X. The bundle now sends the version it was built as (client/package.json, baked in by define), and the server compares it with its own before it hands a notice with a release block over. A shell of another version is told nothing and gets the notice after the reload, from the bundle that can draw it. This holds for every update, not only the one that introduced the parameter.
106 lines
4.3 KiB
TypeScript
106 lines
4.3 KiB
TypeScript
import semver from 'semver';
|
|
import { readEnv } from '../app-config';
|
|
import { db } from '../db/database.js';
|
|
import { SYSTEM_NOTICES } from './registry.js';
|
|
import { evaluate } from './conditions.js';
|
|
import type { SystemNotice, SystemNoticeDTO } from './types.js';
|
|
|
|
export function getCurrentAppVersion(): string {
|
|
const fromEnv = semver.valid(readEnv().app.appVersion ?? '');
|
|
if (fromEnv) return fromEnv;
|
|
try {
|
|
const pkg = require('../../package.json') as { version?: string };
|
|
return semver.valid(pkg.version ?? '') ?? '0.0.0';
|
|
} catch {
|
|
return '0.0.0';
|
|
}
|
|
}
|
|
|
|
export function isNoticeVersionActive(n: SystemNotice, currentAppVersion: string): boolean {
|
|
const appVersion = semver.coerce(currentAppVersion)?.version ?? '0.0.0';
|
|
if (n.minVersion !== undefined) {
|
|
const min = semver.valid(n.minVersion);
|
|
if (!min) { console.warn(`[systemNotices] "${n.id}" invalid minVersion "${n.minVersion}" — skipping`); return false; }
|
|
if (semver.lt(appVersion, min)) return false;
|
|
}
|
|
if (n.maxVersion !== undefined) {
|
|
const max = semver.valid(n.maxVersion);
|
|
if (!max) { console.warn(`[systemNotices] "${n.id}" invalid maxVersion "${n.maxVersion}" — skipping`); return false; }
|
|
if (semver.gte(appVersion, max)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function severityWeight(s: string): number {
|
|
return s === 'critical' ? 2 : s === 'warn' ? 1 : 0;
|
|
}
|
|
|
|
export function getActiveNoticesFor(
|
|
userId: number,
|
|
addonEnabled: (addonId: string) => boolean,
|
|
managed = false
|
|
): SystemNoticeDTO[] {
|
|
const user = db.prepare(
|
|
'SELECT login_count, first_seen_version, role FROM users WHERE id = ?'
|
|
).get(userId) as { login_count: number; first_seen_version: string; role: string } | undefined;
|
|
|
|
if (!user) return [];
|
|
|
|
const { count: tripCount } = db.prepare(
|
|
'SELECT COUNT(*) AS count FROM trips WHERE user_id = ?'
|
|
).get(userId) as { count: number };
|
|
|
|
// Dismissals mapped to the app version they were dismissed at (used by per-version notices).
|
|
const dismissals = new Map<string, string | null>(
|
|
(db.prepare('SELECT notice_id, dismissed_app_version FROM user_notice_dismissals WHERE user_id = ?')
|
|
.all(userId) as Array<{ notice_id: string; dismissed_app_version: string | null }>)
|
|
.map(r => [r.notice_id, r.dismissed_app_version])
|
|
);
|
|
|
|
const now = new Date();
|
|
const currentAppVersion = getCurrentAppVersion();
|
|
const ctx = { user: { ...user, noTrips: tripCount }, currentAppVersion, now, addonEnabled, managed };
|
|
const appVer = semver.coerce(currentAppVersion)?.version ?? '0.0.0';
|
|
|
|
const isStillDismissed = (n: SystemNotice): boolean => {
|
|
if (!dismissals.has(n.id)) return false;
|
|
if (n.recurring === 'per-version') {
|
|
// Re-show once the running app version moves past the version it was last dismissed at,
|
|
// so a per-version notice surfaces again on each install/upgrade.
|
|
const dismissedVer = semver.coerce(dismissals.get(n.id) ?? '0.0.0')?.version ?? '0.0.0';
|
|
return semver.gte(dismissedVer, appVer);
|
|
}
|
|
return true; // default: permanent one-time dismissal
|
|
};
|
|
|
|
return SYSTEM_NOTICES
|
|
.filter(n => {
|
|
if (isStillDismissed(n)) return false;
|
|
if (!isNoticeVersionActive(n, currentAppVersion)) return false;
|
|
return evaluate(n, ctx);
|
|
})
|
|
.sort((a, b) => {
|
|
const pw = (b.priority ?? 0) - (a.priority ?? 0);
|
|
if (pw !== 0) return pw;
|
|
const sw = severityWeight(b.severity) - severityWeight(a.severity);
|
|
if (sw !== 0) return sw;
|
|
return new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime();
|
|
})
|
|
.map(({ conditions: _c, publishedAt: _p, minVersion: _mn, maxVersion: _mx, priority: _pr, recurring: _rc, ...dto }) => dto);
|
|
}
|
|
|
|
export function dismissNotice(userId: number, noticeId: string): boolean {
|
|
const exists = SYSTEM_NOTICES.some(n => n.id === noticeId);
|
|
if (!exists) return false;
|
|
// Record the app version at dismissal so per-version notices can re-appear on the next
|
|
// upgrade. Upsert (not INSERT OR IGNORE) so re-dismissing after a bump refreshes the version.
|
|
db.prepare(`
|
|
INSERT INTO user_notice_dismissals (user_id, notice_id, dismissed_at, dismissed_app_version)
|
|
VALUES (?, ?, ?, ?)
|
|
ON CONFLICT(user_id, notice_id) DO UPDATE SET
|
|
dismissed_at = excluded.dismissed_at,
|
|
dismissed_app_version = excluded.dismissed_app_version
|
|
`).run(userId, noticeId, Date.now(), getCurrentAppVersion());
|
|
return true;
|
|
}
|