Repository navigation
feat: username-customized deep links (/username -> auto-generated donation button) - #10
Conversation
…ton) Support linking straight to a pre-generated donation button via a username-customized URL, e.g. https://donation-button.blink.sv/pretyflaco. This lets external surfaces (notably the Blink mobile app's "Ways to get paid" section) send a user directly to their own button. GitHub Pages has no server-side routing, so: - 404.html captures the requested path, validates it as a plausible single-segment username, stashes it in sessionStorage, and redirects to "/" (history-replacing, so the 404 URL doesn't linger). - js/generator.js gains two pure, unit-testable helpers: * parseUsernameFromPath(pathname) -- path -> candidate username, or null for root/nested/file-like/reserved/invalid segments. * resolveDeepLinkUsername(win) -- prefers the 404 sessionStorage handoff (consumed once), falls back to location.pathname. On DOMContentLoaded, if a username is resolved, it prefills the input and calls generateCode(), so existence is still verified against Blink (with the Spark LNURL fallback) and unknown usernames show the normal message. The DOM wiring is now guarded behind "typeof document", and the helpers are exported UMD-style so they can be required without a DOM. Adds tests/generator-deeplink.spec.js (18 cases) covering parsing edge cases and the sessionStorage/path precedence + failure modes. Public API, container id, embed path, and analytics are unchanged. npm test green (104 passing). Verified end-to-end in a browser: a stored username prefills + auto-generates and the sessionStorage key is consumed.
blink-dev-bot
left a comment
There was a problem hiding this comment.
Review: feat: username-customized deep links
Verdict: APPROVE
Clean, well-structured PR. The 404.html → sessionStorage → generator handoff is the right pattern for GitHub Pages' lack of server-side routing. Good test coverage (18 cases), proper edge case handling, and the username is still verified against the Blink API before rendering.
Observations
MEDIUM — Duplicated parseUsernameFromPath logic
The username parser is duplicated between 404.html (ES5 inline) and js/generator.js (modern JS). The comment says "keep the two in sync" but code duplication is a maintenance risk. Not blocking since the 404.html version is intentionally dependency-free and inline for load speed, but worth noting — if the validation rules ever change, both need updating.
LOW — Incomplete fallback comment in 404.html
Line 64: "fall back to a query param the generator can also read is not implemented; just proceed to home." — reads like a TODO that was left as-is. Consider rewording to clarify this is a deliberate choice, not a pending feature.
LOW — Reserved list maintenance
Both files maintain identical reserved word lists. If either grows (e.g. adding fonts, api, docs), the other must match. A comment cross-referencing the two locations would help.
What's good
- Pure, unit-testable helpers exported UMD-style — clean separation
sessionStorageconsumed once to prevent re-trigger on reload- Graceful degradation when sessionStorage is unavailable
<meta name="robots" content="noindex">on the 404 page prevents search engine indexing of redirect pages- Existence verification still runs through the Blink API — the deep link is a prefill shortcut, not a trust boundary
No blocking issues. Ship it.
🤖 Review by claude-opus-4-6 via Blink-Claw-Bot
…est) Addresses the three non-blocking observations from the PR #10 review: - LOW: reworded the 404.html sessionStorage-catch comment. It read like a leftover TODO ("fall back to a query param ... is not implemented"); it now states the deliberate choice — no query-param fallback, just land on the generator home. - LOW: added cross-reference comments to both reserved lists / parser copies (404.html inline ES5 and js/generator.js), noting the duplication is intentional (404.html must be dependency-free and run before render on GitHub Pages) and pointing at the new parity test. - MEDIUM (duplicated parseUsernameFromPath): kept the intentional duplication but added tests/generator-deeplink-parity.spec.js, which extracts the inline parser from 404.html and asserts it agrees with js/generator.js's exported parser across a shared fixture table. Drift now fails CI (mirrors the inline-LNURL parity approach in AGENTS.md). No runtime/behavior change. npm test green (133 passing, was 104).
|
Thanks for the review! Addressed all three observations in
No runtime/behavior change. |
…#11) * fix: deep-link username swallowed when DOMContentLoaded already fired Following the deep-link feature (#10), hitting a username URL like https://donation-button.blink.sv/pretyflaco intermittently landed on the plain generator with the username silently dropped, instead of auto-generating. Root cause: js/generator.js is loaded via a plain <script> at the end of <body> (no `defer`) and is also reached through the 404.html -> location.replace('/') deep-link redirect. In that redirect/bfcache path the DOM is often already fully parsed by the time generator.js runs, so `DOMContentLoaded` has ALREADY fired. The bootstrap was registered only via addEventListener('DOMContentLoaded', initGenerator), which then never fires — initGenerator() never runs, so no prefill and no auto-generate. It was intermittent because it depended on cache/redirect timing. Fix: guard on document.readyState — attach the DOMContentLoaded listener while still 'loading', otherwise call initGenerator() immediately since the DOM is already available. The bootstrap is additionally gated behind `typeof module === 'undefined'` so requiring the file in the (DOM-less) test runner to exercise the pure helpers stays a no-op. The 404.html half was already correct (verified live: it is served for /<username>, parses + stashes the username, and redirects to /). Tests: adds tests/generator-bootstrap.spec.js, which executes the real browser path against jsdom and asserts the deep-link username is prefilled when readyState is 'complete'/'interactive' (the regressing case) and still deferred correctly when 'loading'. Verified the new spec FAILS on the pre-fix code and passes after. Full suite green (137 passing, was 133). No public API / widget behavior change. * fix(lint): resolve ESLint error in bootstrap test + tidy catch bindings CI (ESLint) failed on tests/generator-bootstrap.spec.js: an unused catch binding (`catch (e)`) is an error under the recommended config for tests/** (which, unlike js/**, has no no-unused-vars override). - Use optional catch binding (`catch {}`) in the new bootstrap test and in the two deep-link helper catches in js/generator.js (were warnings). - Drop three now-unnecessary `eslint-disable no-new-func` directives that ESLint flagged as unused (no-new-func isn't enabled), in the bootstrap and parity specs. eslint . -> 0 errors (remaining warnings are pre-existing, unrelated). npm test green (137 passing).
What
Adds support for username-customized deep links so external surfaces can send a user straight to their own pre-generated donation button:
The primary motivator is linking from the Blink mobile app's "Ways to get paid" section directly to a user's donation button.
How (GitHub Pages has no server-side routing)
404.html(new): GitHub Pages serves this for any unmatched path. It validates the path as a plausible single-segment username, stashes it insessionStorage, andlocation.replace('/')to the generator (so the 404 URL doesn't linger in history). Dependency-free, inline, runs before render.js/generator.js: two new pure, unit-testable helpers:parseUsernameFromPath(pathname)— path → candidate username, ornullfor root / nested / file-like / reserved / invalid segments.resolveDeepLinkUsername(win)— prefers the 404sessionStoragehandoff (consumed once so a reload doesn't re-trigger), falls back tolocation.pathname.DOMContentLoaded, if a username resolves, it prefills the input and callsgenerateCode()— so existence is still verified against Blink (incl. the Spark LNURL fallback) and unknown usernames show the normal "does not exist yet" message.typeof documentand the helpers are exported UMD-style, so the file can be required without a DOM for tests (mirrors theblink-lnurl.jspattern).Behavior on landing
/pretyflaco→ prefill + auto-generate (verified existence check runs). Root/is unchanged. Reserved paths (/js,/img,/css,/tests,/404,/CNAME, ...), files (/favicon.ico,*.html/js/css), nested paths, and invalid segments do not trigger a username and just load the generator home.Testing
tests/generator-deeplink.spec.js(new, 18 cases): parsing edge cases + thesessionStorage/path precedence, one-time consumption, and failure modes (storage throwing / no window).npm testgreen — 104 passing (was 86).sessionStorage.blinkRedirectUsername = 'pretyflaco', load/). Result: input prefilled topretyflaco, existence check passed (✓ Blink username found!), result container shown, embed code generated, and the sessionStorage key consumed.Not changed
Public API (
BlinkPayButton.init),blink-pay-button-containerid, the single-file embed path, and analytics are all untouched.Part 2 of 2. Part 1 (copy consistency) is #9.