UNPKG

signalk-server

Version:

An implementation of a [Signal K](http://signalk.org) server for boats.

165 lines 6.22 kB
"use strict"; /* * Copyright 2026 Signal K contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DETAIL_TTL = exports.LIST_TTL = void 0; exports.createCache = createCache; const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const debug_1 = require("../debug"); const safe_name_1 = require("./safe-name"); const debug = (0, debug_1.createDebug)('signalk-server:appstore:cache'); const LIST_TTL_MS = 60 * 60 * 1000; // 1 hour const DETAIL_TTL_MS = 6 * 60 * 60 * 1000; // 6 hours function createCache(configPath) { const root = path_1.default.join(configPath, 'appstore-cache'); const listFile = path_1.default.join(root, 'list.json'); const pluginsDir = path_1.default.join(root, 'plugins'); function ensureRoot() { try { fs_1.default.mkdirSync(pluginsDir, { recursive: true }); } catch (err) { debug.enabled && debug('failed to create cache dir %s: %O', pluginsDir, err); } } function pluginDir(name) { return path_1.default.join(pluginsDir, (0, safe_name_1.safeName)(name)); } function isFresh(writtenAt, ttl, installed = false) { // Cached detail for an installed plugin is treated as fresh // indefinitely. The on-disk plugin tarball is the source of truth // for installed plugins (we render its README/CHANGELOG locally), // so the cache only ages out when the plugin is uninstalled or // the user clicks Refresh. This also keeps the detail tab usable // offline — npm being unreachable doesn't blank the cached // README/Changelog/Indicators for plugins the user already runs. if (installed) return true; return Date.now() - writtenAt < ttl; } function readListRaw() { try { if (!fs_1.default.existsSync(listFile)) return undefined; const raw = fs_1.default.readFileSync(listFile, 'utf8'); return JSON.parse(raw); } catch (err) { debug.enabled && debug('readList failed: %O', err); return undefined; } } return { readList() { const parsed = readListRaw(); if (!parsed) return undefined; if (!isFresh(parsed.writtenAt, LIST_TTL_MS)) return undefined; return parsed; }, // Bypass the TTL — offline.ts uses this when the live store is // unreachable so we can still serve a stale snapshot rather than // empty results. readListIgnoringTtl() { return readListRaw(); }, writeList(payload) { ensureRoot(); const record = { writtenAt: Date.now(), payload }; try { fs_1.default.writeFileSync(listFile, JSON.stringify(record), 'utf8'); } catch (err) { debug.enabled && debug('writeList failed: %O', err); } }, readPluginDetail(name) { try { const file = path_1.default.join(pluginDir(name), 'detail.json'); if (!fs_1.default.existsSync(file)) return undefined; const raw = fs_1.default.readFileSync(file, 'utf8'); const parsed = JSON.parse(raw); if (!isFresh(parsed.writtenAt, DETAIL_TTL_MS, parsed.installed)) { return undefined; } return parsed; } catch (err) { debug.enabled && debug('readPluginDetail failed for %s: %O', name, err); return undefined; } }, writePluginDetail(detail, installed) { ensureRoot(); const dir = pluginDir(detail.name); try { fs_1.default.mkdirSync(dir, { recursive: true }); } catch (err) { debug.enabled && debug('mkdir failed for %s: %O', dir, err); } const record = { writtenAt: Date.now(), installed, payload: { ...detail, fromCache: true } }; try { fs_1.default.writeFileSync(path_1.default.join(dir, 'detail.json'), JSON.stringify(record), 'utf8'); } catch (err) { debug.enabled && debug('writePluginDetail failed for %s: %O', detail.name, err); } }, invalidateList() { try { if (fs_1.default.existsSync(listFile)) fs_1.default.unlinkSync(listFile); } catch (err) { debug.enabled && debug('invalidateList failed: %O', err); } }, invalidatePluginDetail(name) { try { const file = path_1.default.join(pluginDir(name), 'detail.json'); if (fs_1.default.existsSync(file)) fs_1.default.unlinkSync(file); } catch (err) { debug.enabled && debug('invalidatePluginDetail failed for %s: %O', name, err); } }, invalidateAllPluginDetail() { try { if (fs_1.default.existsSync(pluginsDir)) { fs_1.default.rmSync(pluginsDir, { recursive: true, force: true }); } } catch (err) { debug.enabled && debug('invalidateAllPluginDetail failed: %O', err); } }, cacheRoot() { return root; } }; } exports.LIST_TTL = LIST_TTL_MS; exports.DETAIL_TTL = DETAIL_TTL_MS; //# sourceMappingURL=cache.js.map