Add CrawlProof ad units, without spending the CSP on them - #1
Merged
Conversation
The generated embed did not belong to this codebase. It imported `next/script` into a Bun/Hono app that has no Next in it, which is what CI was failing on, and it stacked all four ad sizes at the bottom of `<body>` on every page, where three of them would have been laid out at a fixed pixel width in a column that is not that width. The deeper problem was quieter. `ad.js` injects each creative as a `srcdoc` iframe, and a srcdoc document inherits the embedder's Content-Security-Policy — so the creative's own `<style>` block and its remote images would only have rendered if this site added `'unsafe-inline'` to `style-src` and opened `img-src` to the web, on every page, for an ad. The policy is a large part of what this site is. `/api/ads/frame` serves the same creative as a real cross-origin document, which carries its own policy. So the whole of advertising here is one iframe and one `frame-src` entry: no third-party script runs, no cookie is set, and nothing is written to local storage — the ad tag would have minted a permanent `crawlproof.visitor` id there, which is exactly what the privacy page promises does not happen. Two placements, both `text_link`, the one format built to fill its container: below the instrument and its FAQ on a test page, and in the seam after the rack on the home page. Never above a test — someone who came to find out whether their microphone works should reach it without passing an ad. An unsold slot answers with a blank document, and nothing on this side can see that across an origin, so the unit is a strip of whitespace with no border and no ground of its own: empty, it reads as the gap between two sections, which is where it was put. The static export switches advertising off before it renders anything. The desktop app ships that output and makes no network request unless you run the network test, and an ad frame is a network request. The privacy page now says all of this, and while it was open: it claimed no scripts or fonts were loaded from anyone else's server, which has not been true since the page started loading two typefaces from Google.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the CrawlProof ad units to d3vices, rewritten for this codebase.
Slot
f54a21ad-3db6-4a62-a9f4-93d505c221c9, which is already active and serving.Why the generated version was replaced
It imported
next/scriptinto a Bun/Hono app with no Next in it — that is what CI was red on — and stacked all four sizes at the bottom of<body>on every page.The quieter problem:
ad.jsinjects each creative as asrcdociframe, and a srcdoc document inherits the embedder's CSP. The creative's own<style>block and its remote images would only have rendered if this site added'unsafe-inline'tostyle-srcand openedimg-srcto the web — on every page, permanently, for an ad.app.jsallows a single inline script by hash on purpose; that is not a good trade./api/ads/framereturns the same creative as a real cross-origin document, which carries its own policy. Advertising therefore costs exactly one CSP directive:No third-party script runs on the page, no cookie is set, and nothing is written to local storage — the script tag mints a permanent
crawlproof.visitorid in the publisher's origin, which would have contradicted the privacy page.Placements
Two, both
text_link— the only format built to fill its container. The banner creatives are laid out at a fixed pixel width inside the frame, so a narrowed frame crops one rather than reflowing it, and without a script nothing here can measure the viewport.Never above a test: someone who came to find out whether their microphone works reaches it without passing an ad.
An unsold slot answers with a blank document, and nothing on this side can see that across an origin — so the unit is a strip of whitespace with no border and no ground of its own. Empty, it reads as the gap between two sections, which is where it was put.
The rest
ADS_SLOTturns it off when empty. The slot id is a public identifier that ships in the markup, so it is the default rather than a variable somebody has to remember to set — merging this is not a silent no-op. Nothing to configure on Railway.export.jssetsADS_SLOT=''before rendering: the packaged app ships that output and makes no network request unless you run the network test, and an ad frame is a network request. Verified — zerocrawlproofreferences indist/site.loading="lazy", so the impression is only spent once the unit is near the viewport.allow-scriptsnorallow-same-origin; the popup permissions are only what lets the click-through open a tab.Verified
biome checkclean, 55 tests pass, static export clean, all 24 pages pass the browser smoke test. In a real browser, on both pages: the frame fills the column (984px desktop, 320px on a 390px phone, no horizontal overflow), the click-through resolves, and there are no CSP violations and no page errors.One known rough edge
/api/ads/framedoes not accept athemeparameter the way/api/ads/servedoes, and without a script this end cannot detect the viewer's theme anyway. So the ad always renders in the slot's theme. On this site's default dark it looks native; on the light theme it is a dark strip — legible and clearly an ad, but louder than it should be. Fixing it properly means forwardingthemefrom the frame route in crawlproof.com, which is a separate change.