openvibers/openvibe.shared app-icons.js

app-icons.js

main 3.3 KB · 60 lines · javascript

1'use strict';
2// ═══════════════════════════════════════════════════════════════
3// openvibe-shared/app-icons — every OpenVibe site's installable app icon set.
4//
5// The icons are pre-rendered PNGs in icons/<variant>[-maskable]-<size>.png (192 and 512), one set per app-icon
6// tint variant (most sites share the network's), drawn by scripts/build-icons.js from app-icon.svg({ site: variant,
7// maskable }); icons/sites.json names each service's variant (social.iconSite). A site serves its
8// pinned copy at /shared/icons/<name> through openvibe-shared/serve, and the web app manifest served at
9// /shared/manifest/<service>.webmanifest points at the same hashed URLs. (The `icons` subpath is already the
10// ring-icon module `ov-icons.js`, so the per-site icon set lives here.)
11//
12// appIcons.has(service) is there an icon set for this service id
13// appIcons.url(service, size, { maskable }) '/shared/icons/<variant>[-maskable]-<size>.png?v=<hash>', or null
14// appIcons.site(service) { name, description, icon } from icons/sites.json, or null
15// appIcons.isIcon(name) 'icons/<variant>[-maskable]-<size>.png' names serve.js may answer
16// appIcons.path(name) the file on disk (throws for anything that is not an icon)
17//
18// shell.page() uses appIcons.url(service, 192) for the apple-touch-icon when the page names its service.
19// ═══════════════════════════════════════════════════════════════
20const fs = require('fs');
21const path = require('path');
22const { hashAsset } = require('./assets');
23
24const DIR = path.join(__dirname, 'icons');
25const NAME_RE = /^icons\/([a-z0-9][a-z0-9-]{0,40})-(maskable-)?(192|512)\.png$/;
26const ID_RE = /^[a-z0-9][a-z0-9-]{0,40}$/;
27const hashes = new Map();
28let sites = null;
29/** icons/sites.json: { <service>: { name, description, icon: <variant> } }, read once. */
30function site(service) {
31 if (!sites) { try { sites = JSON.parse(fs.readFileSync(path.join(DIR, 'sites.json'), 'utf8')); } catch { sites = {}; } }
32 const id = String(service || '');
33 return ID_RE.test(id) && Object.prototype.hasOwnProperty.call(sites, id) ? sites[id] : null;
34}
35
36function isIcon(name) {
37 const m = NAME_RE.exec(String(name || ''));
38 return Boolean(m) && fs.existsSync(path.join(DIR, `${m[1]}-${m[2] || ''}${m[3]}.png`));
39}
40
41function iconPath(name) {
42 if (!isIcon(name)) throw new Error(`openvibe-shared/app-icons: ${name} is not an app icon`);
43 return path.join(__dirname, name);
44}
45
46const has = (service) => { const s = site(service); return Boolean(s && s.icon && isIcon(`icons/${s.icon}-192.png`)); };
47
48function url(service, size, { maskable = false } = {}) {
49 const meta = site(service);
50 if (!meta || !meta.icon) return null;
51 const s = Number(size);
52 if (s !== 192 && s !== 512) return null;
53 const name = `icons/${meta.icon}-${maskable ? 'maskable-' : ''}${s}.png`;
54 if (!isIcon(name)) return null;
55 if (!hashes.has(name)) hashes.set(name, hashAsset(fs.readFileSync(iconPath(name))));
56 return `/shared/${name}?v=${hashes.get(name)}`;
57}
58
59module.exports = { has, url, site, isIcon, path: iconPath, DIR };
60