Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,12 @@ corepack npm run diffsplain -- doctor
corepack npm run doctor
```

Run the checks:
Install Chromium once, then run the checks:

```sh
corepack npm run test:browser:install
corepack npm run check
corepack npm run test:browser
```

Run the Blume docs:
Expand Down
22 changes: 17 additions & 5 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -237,11 +237,17 @@ export default function Home() {
const latestVersion = useRef<string | null>(null);
const touchStart = useRef<number | null>(null);
const searchRef = useRef<HTMLInputElement | null>(null);
const session = useMemo(
() => new URLSearchParams(window.location.hash.slice(1)),
[],
);
const [access, setAccess] = useState(() => session.get("access"));

const refresh = useCallback(async () => {
try {
const liveUrl = new URL("diff-data.json", document.baseURI);
liveUrl.searchParams.set("t", String(Date.now()));
if (access) liveUrl.searchParams.set("access", access);
const liveResponse = await fetch(liveUrl, {
cache: "no-store",
});
Expand Down Expand Up @@ -269,7 +275,7 @@ export default function Home() {
error instanceof Error ? error.message : "Could not read the snapshot",
);
}
}, []);
}, [access]);

useEffect(() => {
const initial = window.setTimeout(() => void refresh(), 0);
Expand All @@ -288,10 +294,9 @@ export default function Home() {
};
if ("EventSource" in window) {
const eventsUrl = new URL("events", document.baseURI);
const project = new URLSearchParams(window.location.hash.slice(1)).get(
"project",
);
const project = session.get("project");
if (project) eventsUrl.searchParams.set("project", project);
if (access) eventsUrl.searchParams.set("access", access);
events = new EventSource(eventsUrl);
events.addEventListener("ready", () => {
stopPolling();
Expand All @@ -300,6 +305,13 @@ export default function Home() {
void refresh();
});
events.addEventListener("update", () => void refresh());
events.addEventListener("access", (event) => {
const nextAccess = (event as MessageEvent<string>).data;
if (!/^[A-Za-z0-9_-]{32,}$/.test(nextAccess)) return;
session.set("access", nextAccess);
window.history.replaceState(null, "", `#${session}`);
setAccess(nextAccess);
});
events.addEventListener("error", startPolling);
} else {
startPolling();
Expand All @@ -311,7 +323,7 @@ export default function Home() {
events?.close();
window.clearInterval(ticker);
};
}, [refresh]);
}, [access, refresh, session]);

const files = useMemo(() => snapshot?.files ?? [], [snapshot]);
const currentIndex = Math.max(
Expand Down
16 changes: 14 additions & 2 deletions benchmarks/live-update-speed.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ function stop(child) {
});
}

function protectedUrl(reviewUrl, path) {
const route = new URL(path, reviewUrl);
const access = new URLSearchParams(new URL(reviewUrl).hash.slice(1)).get(
"access",
);
if (access) route.searchParams.set("access", access);
return route;
}

const temporary = mkdtempSync(join(tmpdir(), "diffsplain-updates-"));
const output = join(temporary, "diff-data.json");
writeFileSync(output, JSON.stringify({ version: "0" }));
Expand All @@ -82,7 +91,10 @@ try {
const url = await waitForUrl(child);
const samples = [];
if (mode === "events") {
const response = await fetch(`${url}/events`);
const response = await fetch(protectedUrl(url, "events"));
if (!response.ok) {
throw new Error(`Event stream returned ${response.status}`);
}
reader = response.body.getReader();
await reader.read();
for (let version = 1; version <= 9; version += 1) {
Expand All @@ -96,7 +108,7 @@ try {
let seenVersion = "0";
const waiters = new Map();
poll = setInterval(async () => {
const response = await fetch(`${url}/diff-data.json`, {
const response = await fetch(protectedUrl(url, "diff-data.json"), {
cache: "no-store",
});
const value = await response.json();
Expand Down
64 changes: 64 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
},
"files": [
"dist",
"scripts/access-token.mjs",
"scripts/build-diff-data.mjs",
"scripts/cli-args.mjs",
"scripts/cache.mjs",
Expand Down Expand Up @@ -67,12 +68,15 @@
"prepack": "npm run build",
"setup": "npm ci",
"setup:smoke": "node scripts/setup-smoke.mjs",
"test": "npm run build && node --test tests/*.test.mjs",
"test": "npm run build && node --test tests/*.test.mjs && npm run test:browser",
Comment thread
itsjling marked this conversation as resolved.
"test:browser": "node --test tests/browser/*.test.mjs",
"test:browser:install": "playwright install chromium",
"test:cloud": "node --test tests/generate-summaries.test.mjs tests/present-agent.test.mjs",
"test:run": "node --test tests/*.test.mjs",
"lint": "tsc --noEmit && eslint ."
},
"devDependencies": {
"@playwright/test": "1.62.0",
"@tailwindcss/postcss": "4.2.1",
"@types/node": "22.19.19",
"@types/react": "19.2.14",
Expand Down
21 changes: 21 additions & 0 deletions scripts/access-token.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { createHash } from 'node:crypto';
import { tmpdir, userInfo } from 'node:os';
import { join } from 'node:path';

export function accessTokenDirectory({
temporaryDirectory = tmpdir(),
identity = userInfo(),
} = {}) {
const userKey = createHash('sha256')
.update(
JSON.stringify([
identity.uid,
identity.gid,
identity.username,
identity.homedir,
]),
)
.digest('hex')
.slice(0, 16);
return join(temporaryDirectory, `diffsplain-access-${userKey}`);
}
3 changes: 2 additions & 1 deletion scripts/check.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const requiredPackageFiles = [
'README.md',
'package.json',
'dist/index.html',
'scripts/access-token.mjs',
'scripts/build-diff-data.mjs',
'scripts/cache.mjs',
'scripts/cli-args.mjs',
Expand All @@ -74,7 +75,7 @@ const requiredPackageFiles = [
'scripts/summary-path.mjs',
'scripts/support-record.mjs',
];
const allowedPackageFile = /^(README(?:\.md)?|LICENSE(?:\.md)?|NOTICE(?:\.md)?|package\.json|dist\/.+|scripts\/(?:build-diff-data|cache|cli-args|coding-agents|doctor|generate-summaries|present|presenter-runtime|serve-built|summary-path|support-record)\.mjs)$/;
const allowedPackageFile = /^(README(?:\.md)?|LICENSE(?:\.md)?|NOTICE(?:\.md)?|package\.json|dist\/.+|scripts\/(?:access-token|build-diff-data|cache|cli-args|coding-agents|doctor|generate-summaries|present|presenter-runtime|serve-built|summary-path|support-record)\.mjs)$/;
const privatePackageFile = /(^|\/)(?:\.env|\.npmrc|\.git|\.github|\.agents|\.codex)(?:\/|$)|\.(?:pem|key)$/i;

export function validatePackageManifest(pack) {
Expand Down
6 changes: 5 additions & 1 deletion scripts/cli-args.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,10 @@ export function parseCliArgs(
`--port must be a number from ${portOption.min} to ${portOption.max}`,
);
}
const host = (options.get('--host') || hostOption.default).replace(
/^\[|\]$/g,
'',
);

return {
help: false,
Expand All @@ -465,7 +469,7 @@ export function parseCliArgs(
: undefined,
port: Number(portValue),
portWasPassed: options.has('--port'),
host: options.get('--host') || hostOption.default,
host,
browserEnabled: !options.has('--no-browser'),
forceSummaryRegeneration: options.has('--force'),
};
Expand Down
23 changes: 22 additions & 1 deletion scripts/present.mjs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
#!/usr/bin/env node

import { spawn } from 'node:child_process';
import { createHash } from 'node:crypto';
import { createHash, randomBytes } from 'node:crypto';
import {
chmodSync,
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
statSync,
writeFileSync,
} from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join, resolve } from 'node:path';
Expand All @@ -20,6 +23,7 @@ import {
selectCodingAgent,
} from './coding-agents.mjs';
import { doctorReport } from './doctor.mjs';
import { accessTokenDirectory } from './access-token.mjs';
import { cacheStatus, clearCache, formatCacheStatus, pruneCache } from './cache.mjs';
import {
agentFallbackRecordNeeded,
Expand Down Expand Up @@ -242,6 +246,20 @@ const projectKey = createHash('sha256')
)
.digest('hex')
.slice(0, 12);
const accessDirectory = accessTokenDirectory();
const accessPath = join(accessDirectory, `${projectKey}.token`);
mkdirSync(accessDirectory, { recursive: true, mode: 0o700 });
chmodSync(accessDirectory, 0o700);
let previousAccess;
try {
const savedAccess = readFileSync(accessPath, 'utf8').trim();
if (/^[A-Za-z0-9_-]{43}$/.test(savedAccess)) previousAccess = savedAccess;
} catch {
// The first run for a project has no prior tab access value.
}
const access = randomBytes(32).toString('base64url');
writeFileSync(accessPath, access, { mode: 0o600 });
chmodSync(accessPath, 0o600);
if (agentEnabled) {
feedArgs.push('--ignore-summary-watch');
agentArgs.push('--snapshot', outputPath);
Expand Down Expand Up @@ -331,6 +349,9 @@ function startSite() {
host,
'--project',
projectKey,
'--access',
access,
...(previousAccess ? ['--previous-access', previousAccess] : []),
...(!cli.portWasPassed ? ['--increment-port'] : []),
],
{ cwd: root, stdio: ['ignore', 'pipe', 'inherit'] },
Expand Down
Loading
Loading