You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DateKeys-App/vite.config.ts

207 lines
9.1 KiB

// Vite configuration of the SvelteKit site. The library tests run with
// vitest.config.ts, which vitest prefers when both files exist.
import { sveltekit } from '@sveltejs/kit/vite';
Phase 2, step 8: the open action of /inspect After steps 1 to 8, a valid capsule whose date has passed on the device clock can be opened in the page: steps 9 to 18 of spec section 63 with open, loaded on demand with a dynamic import (opener.ts), so noble and age-encryption stay out of the first load of every page. - The release is supplied directly by the person (spec 63, step 10): drand's JSON answer or the bare signature, pasted after opening the drand URL the page links to, or the release in the record of an official fixture. The page never fetches it and reads only its round and signature (spec 11, 13). The CSP is unchanged. - time_and_key credentials: a .dkk (readAccessKey reads at most 12 bytes + 16 MiB + 1) or age identities, one per line. - The plaintext of the person's own file goes to a temporary OPFS file (tempfile.ts), committed only after step 18 (spec 56), offered for download and deleted on request, with another capsule, on pagehide and, if left over, on the next visit. One directory and one Web Lock per tab keep other tabs' clean-up away from files in use. Without OPFS, or when the browser refuses it, capsules up to 64 MiB open in memory. An opening in progress stops when another capsule is loaded. - opening.ts builds the page model of steps 9 to 18 as the reference records them; fixtures show their plaintext and compare its SHA-256 with their record. - licenses.txt: the notices of tlock-js (ibe.ts) and age (bech32.ts), the license of every package in the client bundle, Vite's and rolldown's runtime code, and the site's own license. check-build now fails if a notice is missing, or if a page loads noble, @scure/base or age-encryption with its first load. - The home page no longer says that the page never asks for keys. Checked in the browser on the production build: the time_only, time_and_key_portable (with its .dkk) and time_and_key_recipients (with a pasted identity) fixtures open with the SHA-256 of their records; a tampered signature fails at step 10 and a tampered STREAM chunk at step 17, with no download and no file left; an own file opens to OPFS, downloads without a CSP violation and is deleted with its lock; a left over directory goes on the next visit; no request leaves the origin. An adversarial review (four dimensions, each finding checked by a refuter) confirmed 15 findings, all fixed here. 2611 tests; coverage 100 % of the new modules, now a threshold. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
Phase 2, step 2: runtime dependencies and their guards - age-encryption 0.3.1, @noble/curves 2.4.0 (moved from dev) and @noble/hashes 2.4.0 become exact runtime dependencies (plan section 3, decision 5). The lockfile gains six packages: age-encryption, @noble/ciphers 2.4.0, @scure/base 2.4.0, @noble/post-quantum 0.5.4 and its own @noble/curves and @noble/hashes 2.0.1. No file of src/ imports them yet, so the site does not change. - src/lib/dependencies.test.ts guards them. package.json declares exactly these three, pinned. The lockfile has no tlock-js, drand-client or noble 1.x, and no noble 2.x copy other than 2.4.0 at the root and 2.0.1 under @noble/post-quantum. No file of src/ imports tlock-js or drand-client. Only ibe.ts, release.ts and the tests name @noble/, always subpaths of @noble/curves or @noble/hashes that resolve to the root 2.4.0 copy. Every check also runs on bad inputs. It replaces the "only tests import @noble/curves" test of bls12381.contrast.test.ts. - vite.config.ts records the modules of each client chunk in .svelte-kit/output/client-modules.json. check-build.mjs fails if the bundle holds tlock-js, drand-client or @babel/*, or a nested copy other than noble under @noble/post-quantum. It also reports the JavaScript each page loads: /inspect today loads 157 KB, 58.7 KB gzip. - Measured with a probe build (Vite 8, minified, gzip 9): the Decrypter is 48 KB gzip, with the Encrypter 56 KB, noble BLS12-381 plus SHA-256 28 KB, and all of them 73 KB. age-encryption imports its hybrid ML-KEM recipients statically, so post-quantum and its nested noble copy are about 99 KB of the Decrypter's 212 KB of rendered code. - npm audit --omit=dev: no vulnerabilities. The full audit finds two low ones in the tooling: cookie < 0.7.0 through @sveltejs/kit 2.70.3, which is the latest version and affects only SvelteKit's server. npm run verify is green: 2,384 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
import { join } from 'node:path';
import { defineConfig, type Plugin } from 'vite';
// Records what the client bundle is made of, for scripts/check-build.mjs:
// each chunk with the chunks it imports and the modules it contains, with
// their rendered sizes. The record goes to .svelte-kit/output, outside the
// site.
function clientModules(): Plugin {
let root = process.cwd();
return {
name: 'datekeys:client-modules',
apply: 'build',
configResolved(config) {
root = config.root;
},
generateBundle(_, bundle) {
if (this.environment.name !== 'client') return;
const chunks: Record<string, { imports: string[]; dynamicImports: string[]; modules: Record<string, number> }> = {};
for (const out of Object.values(bundle)) {
if (out.type !== 'chunk') continue;
chunks[out.fileName] = {
imports: out.imports,
dynamicImports: out.dynamicImports,
modules: Object.fromEntries(Object.entries(out.modules).map(([id, m]) => [id.replace(/\\/g, '/'), m.renderedLength])),
};
}
const dir = join(root, '.svelte-kit', 'output');
mkdirSync(dir, { recursive: true });
writeFileSync(join(dir, 'client-modules.json'), JSON.stringify({ chunks }, null, 1));
},
};
}
Phase 2, step 8: the open action of /inspect After steps 1 to 8, a valid capsule whose date has passed on the device clock can be opened in the page: steps 9 to 18 of spec section 63 with open, loaded on demand with a dynamic import (opener.ts), so noble and age-encryption stay out of the first load of every page. - The release is supplied directly by the person (spec 63, step 10): drand's JSON answer or the bare signature, pasted after opening the drand URL the page links to, or the release in the record of an official fixture. The page never fetches it and reads only its round and signature (spec 11, 13). The CSP is unchanged. - time_and_key credentials: a .dkk (readAccessKey reads at most 12 bytes + 16 MiB + 1) or age identities, one per line. - The plaintext of the person's own file goes to a temporary OPFS file (tempfile.ts), committed only after step 18 (spec 56), offered for download and deleted on request, with another capsule, on pagehide and, if left over, on the next visit. One directory and one Web Lock per tab keep other tabs' clean-up away from files in use. Without OPFS, or when the browser refuses it, capsules up to 64 MiB open in memory. An opening in progress stops when another capsule is loaded. - opening.ts builds the page model of steps 9 to 18 as the reference records them; fixtures show their plaintext and compare its SHA-256 with their record. - licenses.txt: the notices of tlock-js (ibe.ts) and age (bech32.ts), the license of every package in the client bundle, Vite's and rolldown's runtime code, and the site's own license. check-build now fails if a notice is missing, or if a page loads noble, @scure/base or age-encryption with its first load. - The home page no longer says that the page never asks for keys. Checked in the browser on the production build: the time_only, time_and_key_portable (with its .dkk) and time_and_key_recipients (with a pasted identity) fixtures open with the SHA-256 of their records; a tampered signature fails at step 10 and a tampered STREAM chunk at step 17, with no download and no file left; an own file opens to OPFS, downloads without a CSP violation and is deleted with its lock; a left over directory goes on the next visit; no request leaves the origin. An adversarial review (four dimensions, each finding checked by a refuter) confirmed 15 findings, all fixed here. 2611 tests; coverage 100 % of the new modules, now a threshold. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
// The package directory of a module id under node_modules, or undefined.
function packageDir(id: string): string | undefined {
const i = id.lastIndexOf('/node_modules/');
if (i < 0) return undefined;
const parts = id.slice(i + '/node_modules/'.length).split('/');
const name = parts[0]!.startsWith('@') ? `${parts[0]}/${parts[1]}` : parts[0]!;
return `${id.slice(0, i)}/node_modules/${name}`;
}
// The package of a virtual module of the bundler, such as \0vite/preload-helper.js
// or \0rolldown/runtime.js, or undefined for any other.
function virtualPackageDir(id: string, root: string): string | undefined {
const name = /^\0(vite|rolldown)\//.exec(id)?.[1];
return name === undefined ? undefined : `${root}/node_modules/${name}`;
}
/**
* The license notice kept in the leading comment of a module derived from
* another project, such as src/lib/dkc/ibe.ts: the block of lines indented
* three spaces that holds a copyright line, with the paragraph that
* introduces it. Undefined when the module has none.
*/
function derivedNotice(source: string): string | undefined {
const header: string[] = [];
for (const line of source.split(/\r?\n/)) {
if (!line.startsWith('//')) break;
header.push(line);
}
// The block runs from its first indented line to its last, over blank
// comment lines; list items of the comment are indented too, so it is
// found by its copyright line.
const copyright = header.findIndex((l) => /^\/\/ {3}.*copyright/i.test(l));
if (copyright < 0) return undefined;
let start = copyright;
while (start > 0 && header[start - 1]!.startsWith('// ')) start--;
let end = copyright;
while (end < header.length && (header[end]!.startsWith('// ') || header[end] === '//')) end++;
while (header[end - 1] === '//') end--;
const notice = header.slice(start, end).map((l) => (l === '//' ? '' : l.slice(5)));
// The paragraph before it, which names the project.
let to = start;
while (to > 0 && header[to - 1] === '//') to--;
let from = to;
while (from > 0 && header[from - 1] !== '//') from--;
const intro = header.slice(from, to).map((l) => l.replace(/^\/\/ ?/, ''));
return [...intro, '', ...notice].join('\n');
}
const RULE = '='.repeat(72);
const LINE = '-'.repeat(72);
// Writes licenses.txt at the root of the site: the notices of the third-party
// code in the client bundle, whose minified JavaScript keeps no comments. It
// holds the notice of every module of src/ derived from another project
// (derivedNotice), the license file of every npm package with a module in
// the bundle, and the license of the site itself. scripts/check-build.mjs
// checks that nothing is missing.
function thirdPartyNotices(): Plugin {
let root = process.cwd();
return {
name: 'datekeys:third-party-notices',
apply: 'build',
configResolved(config) {
root = config.root.replace(/\\/g, '/');
},
generateBundle(_, bundle) {
if (this.environment.name !== 'client') return;
const ids = new Set<string>();
const dirs = new Set<string>();
for (const out of Object.values(bundle)) {
if (out.type !== 'chunk') continue;
for (const [raw, m] of Object.entries(out.modules)) {
const id = raw.replace(/\\/g, '/');
ids.add(id);
// The bundler's own virtual modules (\0…) are code of its package.
const dir = id.startsWith('\0') ? virtualPackageDir(id, root) : packageDir(id);
if (dir !== undefined) dirs.add(dir);
else if (id.startsWith('\0') && m.renderedLength > 0) this.error(`no license known for the virtual module ${JSON.stringify(id)}`);
}
}
const derived: string[] = [];
for (const id of [...ids].sort()) {
if (!id.startsWith(`${root}/src/`) || !/\.(ts|js|svelte)$/.test(id)) continue;
const notice = derivedNotice(readFileSync(id, 'utf8'));
if (notice !== undefined) derived.push(`${id.slice(root.length + 1)}\n\n${notice}`);
}
const packages: string[] = [];
for (const dir of [...dirs].sort()) {
const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) as { name: string; version: string; license?: string };
const file = readdirSync(dir).find((f) => /^(licen[cs]e|copying)(\.md|\.txt)?$/i.test(f));
if (file === undefined) this.error(`${pkg.name} ${pkg.version} has no license file`);
const nested = dir.slice(0, dir.lastIndexOf('/node_modules/')).includes('/node_modules/');
const where = nested ? `, anidada bajo ${dir.slice(dir.indexOf('/node_modules/') + '/node_modules/'.length, dir.lastIndexOf('/node_modules/'))}` : '';
let license = readFileSync(join(dir, file), 'utf8').trim();
// Vite's file also carries the licenses of its Node-side dependencies,
// none of which reaches the client: only its own part applies.
const bundled = license.indexOf('\n# Licenses of bundled dependencies');
if (pkg.name === 'vite' && bundled > 0) license = license.slice(0, bundled).trim();
// The code rolldown writes into the bundle derives from Rollup and
// esbuild, whose notices it keeps apart.
const third = join(dir, 'THIRD-PARTY-LICENSE');
if (pkg.name === 'rolldown' && existsSync(third)) license += `\n\n${readFileSync(third, 'utf8').trim()}`;
packages.push(`${LINE}\n${pkg.name} ${pkg.version} (${pkg.license ?? 'sin campo license'}${where})\n${LINE}\n\n${license}`);
}
const text = [
'Avisos de licencia de este sitio',
'',
'Este sitio es datekeys-ts, con licencia Apache-2.0 (al final de este fichero). Su',
Phase 2, step 8: the open action of /inspect After steps 1 to 8, a valid capsule whose date has passed on the device clock can be opened in the page: steps 9 to 18 of spec section 63 with open, loaded on demand with a dynamic import (opener.ts), so noble and age-encryption stay out of the first load of every page. - The release is supplied directly by the person (spec 63, step 10): drand's JSON answer or the bare signature, pasted after opening the drand URL the page links to, or the release in the record of an official fixture. The page never fetches it and reads only its round and signature (spec 11, 13). The CSP is unchanged. - time_and_key credentials: a .dkk (readAccessKey reads at most 12 bytes + 16 MiB + 1) or age identities, one per line. - The plaintext of the person's own file goes to a temporary OPFS file (tempfile.ts), committed only after step 18 (spec 56), offered for download and deleted on request, with another capsule, on pagehide and, if left over, on the next visit. One directory and one Web Lock per tab keep other tabs' clean-up away from files in use. Without OPFS, or when the browser refuses it, capsules up to 64 MiB open in memory. An opening in progress stops when another capsule is loaded. - opening.ts builds the page model of steps 9 to 18 as the reference records them; fixtures show their plaintext and compare its SHA-256 with their record. - licenses.txt: the notices of tlock-js (ibe.ts) and age (bech32.ts), the license of every package in the client bundle, Vite's and rolldown's runtime code, and the site's own license. check-build now fails if a notice is missing, or if a page loads noble, @scure/base or age-encryption with its first load. - The home page no longer says that the page never asks for keys. Checked in the browser on the production build: the time_only, time_and_key_portable (with its .dkk) and time_and_key_recipients (with a pasted identity) fixtures open with the SHA-256 of their records; a tampered signature fails at step 10 and a tampered STREAM chunk at step 17, with no download and no file left; an own file opens to OPFS, downloads without a CSP violation and is deleted with its lock; a left over directory goes on the next visit; no request leaves the origin. An adversarial review (four dimensions, each finding checked by a refuter) confirmed 15 findings, all fixed here. 2611 tests; coverage 100 % of the new modules, now a threshold. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
'JavaScript lleva, minimizado, código de terceros: módulos derivados de otros',
'proyectos y paquetes npm, cada uno con su aviso de copyright y su licencia.',
'',
RULE,
'Módulos de datekeys-ts derivados de otros proyectos',
Phase 2, step 8: the open action of /inspect After steps 1 to 8, a valid capsule whose date has passed on the device clock can be opened in the page: steps 9 to 18 of spec section 63 with open, loaded on demand with a dynamic import (opener.ts), so noble and age-encryption stay out of the first load of every page. - The release is supplied directly by the person (spec 63, step 10): drand's JSON answer or the bare signature, pasted after opening the drand URL the page links to, or the release in the record of an official fixture. The page never fetches it and reads only its round and signature (spec 11, 13). The CSP is unchanged. - time_and_key credentials: a .dkk (readAccessKey reads at most 12 bytes + 16 MiB + 1) or age identities, one per line. - The plaintext of the person's own file goes to a temporary OPFS file (tempfile.ts), committed only after step 18 (spec 56), offered for download and deleted on request, with another capsule, on pagehide and, if left over, on the next visit. One directory and one Web Lock per tab keep other tabs' clean-up away from files in use. Without OPFS, or when the browser refuses it, capsules up to 64 MiB open in memory. An opening in progress stops when another capsule is loaded. - opening.ts builds the page model of steps 9 to 18 as the reference records them; fixtures show their plaintext and compare its SHA-256 with their record. - licenses.txt: the notices of tlock-js (ibe.ts) and age (bech32.ts), the license of every package in the client bundle, Vite's and rolldown's runtime code, and the site's own license. check-build now fails if a notice is missing, or if a page loads noble, @scure/base or age-encryption with its first load. - The home page no longer says that the page never asks for keys. Checked in the browser on the production build: the time_only, time_and_key_portable (with its .dkk) and time_and_key_recipients (with a pasted identity) fixtures open with the SHA-256 of their records; a tampered signature fails at step 10 and a tampered STREAM chunk at step 17, with no download and no file left; an own file opens to OPFS, downloads without a CSP violation and is deleted with its lock; a left over directory goes on the next visit; no request leaves the origin. An adversarial review (four dimensions, each finding checked by a refuter) confirmed 15 findings, all fixed here. 2611 tests; coverage 100 % of the new modules, now a threshold. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
RULE,
'',
derived.join(`\n\n${LINE}\n\n`),
'',
RULE,
'Paquetes npm',
RULE,
'',
packages.join('\n\n'),
'',
RULE,
'datekeys-ts (Apache-2.0)',
Phase 2, step 8: the open action of /inspect After steps 1 to 8, a valid capsule whose date has passed on the device clock can be opened in the page: steps 9 to 18 of spec section 63 with open, loaded on demand with a dynamic import (opener.ts), so noble and age-encryption stay out of the first load of every page. - The release is supplied directly by the person (spec 63, step 10): drand's JSON answer or the bare signature, pasted after opening the drand URL the page links to, or the release in the record of an official fixture. The page never fetches it and reads only its round and signature (spec 11, 13). The CSP is unchanged. - time_and_key credentials: a .dkk (readAccessKey reads at most 12 bytes + 16 MiB + 1) or age identities, one per line. - The plaintext of the person's own file goes to a temporary OPFS file (tempfile.ts), committed only after step 18 (spec 56), offered for download and deleted on request, with another capsule, on pagehide and, if left over, on the next visit. One directory and one Web Lock per tab keep other tabs' clean-up away from files in use. Without OPFS, or when the browser refuses it, capsules up to 64 MiB open in memory. An opening in progress stops when another capsule is loaded. - opening.ts builds the page model of steps 9 to 18 as the reference records them; fixtures show their plaintext and compare its SHA-256 with their record. - licenses.txt: the notices of tlock-js (ibe.ts) and age (bech32.ts), the license of every package in the client bundle, Vite's and rolldown's runtime code, and the site's own license. check-build now fails if a notice is missing, or if a page loads noble, @scure/base or age-encryption with its first load. - The home page no longer says that the page never asks for keys. Checked in the browser on the production build: the time_only, time_and_key_portable (with its .dkk) and time_and_key_recipients (with a pasted identity) fixtures open with the SHA-256 of their records; a tampered signature fails at step 10 and a tampered STREAM chunk at step 17, with no download and no file left; an own file opens to OPFS, downloads without a CSP violation and is deleted with its lock; a left over directory goes on the next visit; no request leaves the origin. An adversarial review (four dimensions, each finding checked by a refuter) confirmed 15 findings, all fixed here. 2611 tests; coverage 100 % of the new modules, now a threshold. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
RULE,
'',
readFileSync(join(root, 'LICENSE'), 'utf8').trim(),
'',
].join('\n');
this.emitFile({ type: 'asset', fileName: 'licenses.txt', source: text });
},
};
}
// `vite preview` sends the pages without Cache-Control, so a browser may show
// an old page after a new build, whose chunks are gone from build/: the local
// preview has the pages revalidated on every load. The hashed files of
// _app/immutable keep the year-long cache that SvelteKit gives them. It goes
// before the SvelteKit plugin, which serves the prerendered pages itself and
// would answer first. A real host should serve the pages the same way.
function revalidatePages(): Plugin {
return {
name: 'datekeys:revalidate-pages',
configurePreviewServer(server) {
server.middlewares.use((req, res, next) => {
if (!req.url?.startsWith('/_app/immutable/')) res.setHeader('Cache-Control', 'no-cache');
next();
});
},
};
}
export default defineConfig({
plugins: [revalidatePages(), sveltekit(), clientModules(), thirdPartyNotices()],
build: {
// Never inline an asset as a data: URL. The CSP allows only the page's
// own origin, so the official fixtures (src/lib/inspector/fixtures.ts)
// must be separate same-origin files, whatever their size.
assetsInlineLimit: 0,
},
server: {
fs: {
// The dev server serves the official fixtures straight from testdata/,
// the single source of truth; the build copies them as hashed assets.
allow: ['testdata/fixtures'],
},
},
});

Powered by TurnKey Linux.