@hraness/slopcamera 3.10.0 → 3.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PRIVACY.md +8 -0
- package/apps/desktop/application/operation.ts +1 -1
- package/apps/desktop/cli/commands.ts +1 -1
- package/apps/desktop/cli/html-overlay-renderer.ts +42 -28
- package/apps/desktop/cli/html-overlay-watcher-baseline.ts +65 -0
- package/apps/desktop/dist/cli/main.js +240 -240
- package/dist/cli.js +1 -1
- package/dist/{index-0cby5jc7.js → index-33nxxj9a.js} +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/version.ts +1 -1
package/PRIVACY.md
CHANGED
|
@@ -53,3 +53,11 @@ from vulnerability reports.
|
|
|
53
53
|
|
|
54
54
|
Report suspected vulnerabilities through GitHub private vulnerability
|
|
55
55
|
reporting at https://github.com/hraness/slopcamera/security/advisories/new.
|
|
56
|
+
|
|
57
|
+
The public slopcamera.com website sends cookieless page views, page exits,
|
|
58
|
+
performance measurements, explicit link and install-copy actions, missing-page
|
|
59
|
+
reports, and rate-limited redacted client errors to PostHog. It keeps no person
|
|
60
|
+
profiles, analytics cookies, or session recordings and respects Do Not Track.
|
|
61
|
+
Public campaign parameters are retained; other query values and credentials
|
|
62
|
+
are removed. Sensitive paths collapse to `/private` and their campaign
|
|
63
|
+
attribution is removed. Preview builds and the inert preview page send nothing.
|
|
@@ -4,7 +4,7 @@ import type { z } from "zod";
|
|
|
4
4
|
|
|
5
5
|
import type { ApplicationContext } from "./context";
|
|
6
6
|
|
|
7
|
-
export const SLOPCAMERA_APPLICATION_TOOL_VERSION = "slopcamera-3.10.
|
|
7
|
+
export const SLOPCAMERA_APPLICATION_TOOL_VERSION = "slopcamera-3.10.1" as const;
|
|
8
8
|
|
|
9
9
|
export const OPERATION_KINDS = [
|
|
10
10
|
"scene.audit",
|
|
@@ -365,7 +365,7 @@ import {
|
|
|
365
365
|
workflowRunStore,
|
|
366
366
|
} from "./workflow-runs";
|
|
367
367
|
|
|
368
|
-
export const SLOPCAMERA_VERSION = "3.10.
|
|
368
|
+
export const SLOPCAMERA_VERSION = "3.10.1";
|
|
369
369
|
|
|
370
370
|
// Legacy direct renders predate per-target output contracts. Keep them
|
|
371
371
|
// bounded generously enough for long-form production while preventing one
|
|
@@ -23,6 +23,8 @@ import { basename, dirname, join } from "node:path";
|
|
|
23
23
|
|
|
24
24
|
import { z } from "zod";
|
|
25
25
|
|
|
26
|
+
import { verifyStableBrowserWatcherBaseline } from "./html-overlay-watcher-baseline";
|
|
27
|
+
|
|
26
28
|
import {
|
|
27
29
|
chromium,
|
|
28
30
|
type Browser,
|
|
@@ -2020,6 +2022,8 @@ export class PlaywrightHtmlOverlayRenderer implements HtmlOverlayRenderer {
|
|
|
2020
2022
|
signal,
|
|
2021
2023
|
);
|
|
2022
2024
|
browserRuntimeSnapshot = preparedBrowserRuntime;
|
|
2025
|
+
let watcherVersion = 0;
|
|
2026
|
+
let watcherFailure: Error | undefined;
|
|
2023
2027
|
const runtimeName = basename(preparedBrowserRuntime.runtimeRoot);
|
|
2024
2028
|
const snapshotName = basename(preparedBrowserRuntime.directory);
|
|
2025
2029
|
const anchorWatcher = watch(
|
|
@@ -2028,11 +2032,13 @@ export class PlaywrightHtmlOverlayRenderer implements HtmlOverlayRenderer {
|
|
|
2028
2032
|
(eventType, filename) => {
|
|
2029
2033
|
const path = filename?.toString() ?? "<snapshot-anchor>";
|
|
2030
2034
|
if (path === snapshotName) {
|
|
2035
|
+
watcherVersion += 1;
|
|
2031
2036
|
browserRuntimeMutations.add(`anchor-${eventType}:${path}`);
|
|
2032
2037
|
}
|
|
2033
2038
|
},
|
|
2034
2039
|
);
|
|
2035
2040
|
anchorWatcher.on("error", error => {
|
|
2041
|
+
watcherFailure = error;
|
|
2036
2042
|
browserRuntimeMutations.add(`anchor-watch-error:${error.message}`);
|
|
2037
2043
|
});
|
|
2038
2044
|
browserRuntimeWatchers.push(anchorWatcher);
|
|
@@ -2065,6 +2071,7 @@ export class PlaywrightHtmlOverlayRenderer implements HtmlOverlayRenderer {
|
|
|
2065
2071
|
// its exact path identity still detects fixture substitution.
|
|
2066
2072
|
return;
|
|
2067
2073
|
}
|
|
2074
|
+
watcherVersion += 1;
|
|
2068
2075
|
if (path === runtimeName || path.startsWith(`${runtimeName}/`)) {
|
|
2069
2076
|
browserRuntimeMutations.add(`${eventType}:${path}`);
|
|
2070
2077
|
return;
|
|
@@ -2073,41 +2080,48 @@ export class PlaywrightHtmlOverlayRenderer implements HtmlOverlayRenderer {
|
|
|
2073
2080
|
},
|
|
2074
2081
|
);
|
|
2075
2082
|
containerWatcher.on("error", error => {
|
|
2083
|
+
watcherFailure = error;
|
|
2076
2084
|
browserRuntimeMutations.add(`watch-error:${error.message}`);
|
|
2077
2085
|
});
|
|
2078
2086
|
browserRuntimeWatchers.push(containerWatcher);
|
|
2079
|
-
await new Promise<void>(resolve => setTimeout(resolve, 25));
|
|
2080
|
-
if (signal.aborted) throw cancellationReason(signal);
|
|
2081
|
-
// FSEvents can deliver the just-created direct-child notification after
|
|
2082
|
-
// both watches attach. Clear that bounded baseline before re-verifying
|
|
2083
|
-
// every manifest and path identity. A swap during this window changes
|
|
2084
|
-
// the already-captured container ctime and therefore fails below.
|
|
2085
|
-
browserRuntimeMutations.clear();
|
|
2086
2087
|
let browser: Browser | undefined;
|
|
2087
2088
|
let browserFailure: Readonly<{ error: unknown }> | undefined;
|
|
2088
2089
|
try {
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
signal,
|
|
2094
|
-
);
|
|
2095
|
-
await assertBrowserRuntimeSnapshotIdentity(
|
|
2096
|
-
preparedBrowserRuntime,
|
|
2097
|
-
request.browserRuntime.manifest.entries,
|
|
2098
|
-
"immediately before launch",
|
|
2099
|
-
signal,
|
|
2100
|
-
);
|
|
2101
|
-
await assertBrowserRuntimeSnapshotContainerIdentity(
|
|
2102
|
-
preparedBrowserRuntime,
|
|
2103
|
-
"immediately before launch",
|
|
2090
|
+
// Delayed preparation notifications can arrive during full verification.
|
|
2091
|
+
// Recheck the SAME pre-wait identities until one pass is stable. Nothing
|
|
2092
|
+
// after the synchronous baseline commit is discarded.
|
|
2093
|
+
await verifyStableBrowserWatcherBaseline({
|
|
2104
2094
|
signal,
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2095
|
+
version: () => watcherVersion,
|
|
2096
|
+
failure: () => watcherFailure,
|
|
2097
|
+
quietMs: process.platform === "darwin" ? 2_000 : 25,
|
|
2098
|
+
deadlineMs: 10_000,
|
|
2099
|
+
verify: async () => {
|
|
2100
|
+
await verifyBrowserRuntimeSnapshot(
|
|
2101
|
+
preparedBrowserRuntime,
|
|
2102
|
+
request.browserRuntime,
|
|
2103
|
+
"immediately before launch",
|
|
2104
|
+
signal,
|
|
2105
|
+
);
|
|
2106
|
+
await assertBrowserRuntimeSnapshotIdentity(
|
|
2107
|
+
preparedBrowserRuntime,
|
|
2108
|
+
request.browserRuntime.manifest.entries,
|
|
2109
|
+
"immediately before launch",
|
|
2110
|
+
signal,
|
|
2111
|
+
);
|
|
2112
|
+
await assertBrowserRuntimeSnapshotContainerIdentity(
|
|
2113
|
+
preparedBrowserRuntime,
|
|
2114
|
+
"immediately before launch",
|
|
2115
|
+
signal,
|
|
2116
|
+
);
|
|
2117
|
+
await assertBrowserRuntimeSnapshotAnchorIdentity(
|
|
2118
|
+
preparedBrowserRuntime,
|
|
2119
|
+
"immediately before launch",
|
|
2120
|
+
signal,
|
|
2121
|
+
);
|
|
2122
|
+
},
|
|
2123
|
+
commit: () => { browserRuntimeMutations.clear(); },
|
|
2124
|
+
});
|
|
2111
2125
|
discardProvenLaunchManagedRootMetadataEvent(
|
|
2112
2126
|
browserRuntimeMutations,
|
|
2113
2127
|
runtimeName,
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
2
|
+
|
|
3
|
+
interface BaselineClock {
|
|
4
|
+
readonly now: () => number;
|
|
5
|
+
readonly wait: (milliseconds: number, signal: AbortSignal) => Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const clock: BaselineClock = {
|
|
9
|
+
now: () => performance.now(),
|
|
10
|
+
wait: async (milliseconds, signal) => { await delay(milliseconds, undefined, { signal }); },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/** Drain asynchronous preparation notifications before revalidating the saved identities. */
|
|
14
|
+
interface BrowserWatcherBaselineOptions {
|
|
15
|
+
readonly signal: AbortSignal;
|
|
16
|
+
readonly version: () => number;
|
|
17
|
+
readonly failure: () => Error | undefined;
|
|
18
|
+
readonly quietMs: number;
|
|
19
|
+
readonly deadlineMs: number;
|
|
20
|
+
readonly clock?: BaselineClock;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function settleBrowserWatcherBaseline(options: BrowserWatcherBaselineOptions): Promise<void> {
|
|
24
|
+
const timing = options.clock ?? clock;
|
|
25
|
+
const started = timing.now();
|
|
26
|
+
let lastActivity = started;
|
|
27
|
+
let observedVersion = options.version();
|
|
28
|
+
for (;;) {
|
|
29
|
+
options.signal.throwIfAborted();
|
|
30
|
+
const failure = options.failure();
|
|
31
|
+
if (failure !== undefined) throw failure;
|
|
32
|
+
const now = timing.now();
|
|
33
|
+
const version = options.version();
|
|
34
|
+
if (version !== observedVersion) {
|
|
35
|
+
observedVersion = version;
|
|
36
|
+
lastActivity = now;
|
|
37
|
+
}
|
|
38
|
+
if (now - started >= options.deadlineMs) {
|
|
39
|
+
throw new Error("Browser runtime filesystem notifications did not settle before launch.");
|
|
40
|
+
}
|
|
41
|
+
if (now - lastActivity >= options.quietMs) return;
|
|
42
|
+
await timing.wait(Math.min(50, options.quietMs - (now - lastActivity), options.deadlineMs - (now - started)), options.signal);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Every discarded notification batch must precede a stable, complete identity check. */
|
|
47
|
+
export async function verifyStableBrowserWatcherBaseline(options: BrowserWatcherBaselineOptions & {
|
|
48
|
+
readonly verify: () => Promise<void>;
|
|
49
|
+
readonly commit: () => void;
|
|
50
|
+
}): Promise<void> {
|
|
51
|
+
for (let pass = 0; pass < 3; pass += 1) {
|
|
52
|
+
await settleBrowserWatcherBaseline(options);
|
|
53
|
+
const version = options.version();
|
|
54
|
+
await options.verify();
|
|
55
|
+
options.signal.throwIfAborted();
|
|
56
|
+
const failure = options.failure();
|
|
57
|
+
if (failure !== undefined) throw failure;
|
|
58
|
+
if (options.version() === version) {
|
|
59
|
+
// No await may separate the stable-generation check from arming the guard.
|
|
60
|
+
options.commit();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
throw new Error("Browser runtime filesystem notifications changed during all three verification passes.");
|
|
65
|
+
}
|