~james/core

core/localinfra/pds-patch.mjs -rw-r--r-- 4.0 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 16 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import { createHash } from "node:crypto";
import { copyFileSync, existsSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
import { createRequire } from "node:module";
import { basename, dirname, join } from "node:path";

const [appDir, themePath, fontPath] = process.argv.slice(2);

function packageDir(from, name) {
	for (const dir of createRequire(join(from, "/")).resolve.paths(name)) {
		const candidate = join(dir, name);
		if (existsSync(candidate)) return realpathSync(candidate);
	}
	throw new Error(`${name} not resolvable from ${from}`);
}

const pdsDir = packageDir(appDir, "@atproto/pds");
const providerDir = join(packageDir(pdsDir, "@atproto/oauth-provider"), "dist");
const uiManifestPath = createRequire(join(providerDir, "router/assets/assets.js")).resolve(
	"@atproto/oauth-provider-ui/bundle-manifest.json",
);

const sha256 = (data) => createHash("sha256").update(data).digest("base64");

function patch(path, replacements, { all = false, append = "" } = {}) {
	let text = readFileSync(path, "utf8");
	for (const [from, to] of replacements) {
		const count = text.split(from).length - 1;
		if (all ? count === 0 : count !== 1) throw new Error(`${path}: expected ${all ? "some" : "one"} ${from}, found ${count}`);
		text = text.replaceAll(from, () => to);
	}
	text += append;
	writeFileSync(path, text);
	return text;
}

patch(join(pdsDir, "dist/mailer/index.js"), [
	["logoUrl: branding.logo ?? DEFAULT_LOGO_URL,", "logoUrl: process.env.PDS_EMAIL_LOGO_URL ?? branding.logo ?? DEFAULT_LOGO_URL,"],
	["markUrl: branding.logo ?? DEFAULT_MARK_URL,", "markUrl: process.env.PDS_EMAIL_LOGO_URL ?? branding.logo ?? DEFAULT_MARK_URL,"],
]);

patch(join(providerDir, "oauth-provider.js"), [["ttl: 600e3", "ttl: 1e3"]], { all: true });

patch(join(providerDir, "lib/csp/index.js"), [["    'default-src',\n", "    'default-src',\n    'font-src',\n"]]);
patch(join(providerDir, "router/assets/assets.js"), [
	[`'connect-src': ["'self'"],`, `'connect-src': ["'self'"],\n    'font-src': ["'self'"],`],
]);
patch(join(providerDir, "router/assets/assets.js"), [["'img-src': ['https:'],", "'img-src': ['https:', 'data:'],"]]);
patch(join(providerDir, "router/assets/assets-manifest.js"), [
	["[null, 'document', 'style', 'script']", "[null, 'document', 'style', 'script', 'font']"],
	["'public, max-age=31536000, immutable'", "'no-cache'"],
]);

const uiDir = dirname(uiManifestPath);
const manifest = JSON.parse(readFileSync(uiManifestPath, "utf8"));
const styles = Object.entries(manifest).filter(([, asset]) => asset.mime === "text/css");
if (styles.length === 0) throw new Error(`${uiManifestPath}: no stylesheet in manifest`);

const theme = readFileSync(themePath, "utf8");
for (const [filename, asset] of styles) {
	if (asset.data) throw new Error(`${filename}: inlined in manifest`);
	const css = patch(
		join(uiDir, filename),
		[
			["--radius:.625rem", "--radius:.5rem"],
			["calc(var(--radius) + 4px)", "var(--radius)"],
			["calc(var(--radius) - 2px)", "var(--radius)"],
			["calc(var(--radius) - 4px)", "var(--radius)"],
		],
		{ all: true, append: theme },
	);
	asset.sha256 = sha256(css);
}

const scripts = [
	[/flag:`[\u{1F1E6}-\u{1F1FF}]{2}`/gu, "flag:``"],
	[
		/(side:\w+=`bottom`,sideOffset:\w+=)4(,align:\w+=)`center`(,alignOffset:\w+=0,alignItemWithTrigger:\w+=)!0/g,
		"$18$2`start`$3!1",
	],
];
const hits = scripts.map(() => 0);
for (const [filename, asset] of Object.entries(manifest)) {
	if (asset.type !== "chunk") continue;
	const path = join(uiDir, filename);
	let js = readFileSync(path, "utf8");
	scripts.forEach(([pattern, replacement], i) => {
		js = js.replace(pattern, (...match) => {
			hits[i]++;
			return replacement.replace(/\$(\d)/g, (_, n) => match[n]);
		});
	});
	writeFileSync(path, js);
	asset.sha256 = sha256(js);
}
scripts.forEach(([pattern], i) => {
	if (hits[i] === 0) throw new Error(`${uiDir}: ${pattern} not found`);
});

const font = basename(fontPath);
copyFileSync(fontPath, join(uiDir, font));
manifest[font] = { type: "asset", mime: "font/woff2", sha256: sha256(readFileSync(fontPath)) };

writeFileSync(uiManifestPath, JSON.stringify(manifest));