[pull] main from jason5ng32:main#106
Merged
pull[bot] merged 59 commits intoCosr-Backup:mainfrom Apr 24, 2026
Merged
Conversation
Toast had been wedged into the bottom-*left* corner because the two
floating action buttons (InfoMask + QueryIP) occupied the conventional
bottom-right real estate. That was a downstream cost of the FAB choice
and felt off — every app in the sonner/react-hot-toast generation puts
toasts bottom-right by default.
Keep the FABs where they are and move the toasts around them instead.
The new rule lives in Toast.vue as an unscoped block (sonner renders its
root to document.body, so scoped styles wouldn't reach it):
right: calc(max(0px, (100vw - 1600px) / 2) + 66px) !important;
The `(viewport - 1600) / 2` half-overflow term tracks the same content-
edge math InfoMask / QueryIP use in their `positionStyle`, so the toast
stays glued to the left side of the FAB column on every screen size:
narrow (≤1600px): flat 66px from viewport right (FAB width 36 +
gap 10 + FAB's own 20 right-margin = 66)
wide (>1600px): 66px + half the 1600-clamped overflow, matching
where the FAB column drifted inward
Vertically the toast keeps sonner's default 32px offset — no longer
climbing above the FABs, just side-stepping them.
FAB vertical positions were nudged by 4px each (bottom-5 → bottom-6,
bottom-[66px] → bottom-[70px]) for a touch more breathing room between
them and the screen edge. Horizontal positioning unchanged, so the 66
in the CSS above still matches.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The Network Connectivity section now ships with 7 built-in targets plus a user-editable slot. Users can add up to 9 custom tests (name + URL), each tested with the same favicon-load method as the built-ins and persisted to `userPreferences.customConnectivityTargets` in localStorage. Highlights: - The existing `connectivityTests` reactive array is the single source of truth; custom entries carry a `custom: true` flag and everything else (J/K navigation, `checkAllConnectivity`, status rendering) treats them identically to built-ins. - A watch on the stored preference does a diff-based reconciliation (drop removed ids, push new ids, leave survivors in place) so that adding or removing one target does NOT reset the status/time/mintime of the others — a cosmetic regression I hit in an earlier draft. - Custom cards render a first-letter colored tile instead of a lucide icon. Color is a stable hash of the name, so "Weibo" always shows the same shade across sessions / devices. - The "+" add tile sits at the end of the grid and hides itself once the 9-target cap is reached. - Inline × on hover removes a custom card. Built-in cards are not removable — the `v-if="test.custom"` gates the button. - URL normalization (`weibo.com` / `www.weibo.com` / full URL with path+query) collapses everything to `origin/favicon.ico?` so the existing `checkConnectivityHandler` can append its cache-bust suffix unchanged. - Add dialog uses a new shadcn-vue `Label` primitive (drop-in copy matching the library convention) for the two form fields. Errors toggle `aria-invalid` on the offending input for the red ring, and the error paragraph has a 1-line min-height reservation so toggling errors doesn't shift the dialog height. - `DEFAULT_PREFERENCES.customConnectivityTargets = []` plus a test update so shape assertions still match. - Four locales get the new `connectivity.addCustom.*` keys (title, labels, placeholders, hints, error strings, add/remove actions). Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…o query inputs
Two accessibility papercuts resolved together.
1. Every Dialog in the app printed a reka-ui warning in the console on
open:
Warning: Missing `Description` or `aria-describedby="undefined"`
for DialogContent.
The shadcn-vue DialogContent primitive already had a VisuallyHidden
fallback for DialogTitle but none for DialogDescription, so screen
readers had no aria-describedby target and reka-ui refused to stay
quiet about it. Adding a symmetric `description` prop / slot plus
a fallback that reuses the title string satisfies the contract
without forcing every call site to pass redundant copy. Optional
callers can still override via `:description="..."` or the
`description` named slot when they want a distinct spoken label.
2. Propagate the aria-invalid-on-error pattern (introduced in the
Connectivity Add dialog) to the other query inputs that have
inline error messages: QueryIP, CensorshipCheck, DnsResolver,
MacChecker, Whois. With shadcn-vue's built-in `aria-invalid:*`
Tailwind utility, the offending field now gets the red ring
automatically whenever its errorMsg / modalQueryError is non-empty,
giving users a second, visual cue beyond the text message.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…ients
V6.0 reshaped several default preferences. Existing users have their old
overrides merged over new defaults (`{ ...defaults, ...stored }`), which
means they'd silently keep stale values for any option that used to exist
under a different shape or meaning. We want the release to force everyone
onto a clean baseline.
Rename the key from `userPreferences` → `userPreferences_v6`:
- Old key turns into orphaned data — the loader can't see it, falls back
to `createDefaultPreferences()`, fresh state wins.
- On that same fresh-install branch we `removeItem(legacy)` so browsers
don't carry dead bytes forever (guarded behind "new key was empty" to
avoid racing a concurrent tab that already migrated).
- `PREFS_STORAGE_KEY` + `LEGACY_PREFS_KEYS` constants sit at the top of
store.js so the next release can just bump the suffix if it needs the
same treatment. The comment above them documents when to do this.
- `frontend/locales/i18n.js` was also reading the raw key string in
`setLanguage()`; updated in lockstep (the comment there cross-refs the
store constant).
- Removed a now-redundant `localStorage.setItem(...)` in `loadPreferences`
that duplicated what `setPreferences()` already does on the next line.
- `tests/store.test.js` asserted against the literal old key in 4 places;
all updated to the new key.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Cuts the first minor release since v6.0's rewrite. Four changelog entries picked for user-facing visibility: - Network Connectivity accepts up to 9 user-added test targets (saved locally in the browser). - Toasts returned to the bottom-right corner, offset around the FAB column. - Auto theme mode now follows OS light/dark flips instantly (previously needed a reload / manual toggle). - First-paint visual flicker on page load removed. package.json bumped 6.0.0 → 6.1.0 in lockstep. Date stamped for Apr 20, 2026 to match the planned release day. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
On mobile (iOS Safari in particular, real device), tapping a category
in the Security Checklist sidebar made the whole vaul drawer visually
slide up — the header disappeared off the top, a matching blank patch
appeared at the bottom. PC didn't repro. Chrome DevTools mobile mode
didn't repro. Only manifested on real iOS.
Root cause: `changeList` called
`document.getElementById('checklist').scrollIntoView({ block: 'nearest' })`
on the details pane. On desktop's md 3-column grid the target was
already visible and the call no-op'd. On a 1-column mobile layout the
browser walked up DOM for the nearest scrolling ancestor to move, and
in settling the result it ended up touching the root scroller — but
vaul keeps the page scroll-locked via `body { position: fixed;
top: -Npx }` while the drawer is open, and that nudge shifted its
recorded offset. Visually the drawer itself looked like it slid.
Fix: walk up from the target element ourselves to find the first
ancestor with `overflow-y: auto | scroll`, check whether the target is
already inside that scroller's viewport (preserves the desktop no-op),
and if not, `scrollBy` directly on that element. The scroll never
bubbles past the drawer's inner container, so vaul's scroll-lock state
stays untouched.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Real iOS PWA reproducibly mis-rendered the Whois input caret a full line below the input on the Chinese locale — text landed inside the input but the `|` caret painted in the empty space below. Only happened in PWA (not Safari), only in Chinese (not English), and only on Whois (not Censorship / DNS Resolver / MAC Lookup, even though they shared the exact same markup pattern). The culprit turned out to be the raw `<label for="queryURLorIP">`. Swapping it for the shadcn Label primitive (reka-ui + Tailwind classes: `flex items-center text-sm leading-none font-medium select-none ...`) fixed the caret instantly. The likely reason is a combination of: - `select-none` removing the label from WKWebView's selection-range calculations, so it no longer contributes to the input's caret Y coordinate; - `leading-none` making the label's line-box tight around the font, which removes the CJK-specific overflow the default `text-sm` line-height caused; - reka-ui's Label primitive handling pointer events differently from a naked `<label>`. Why Whois was uniquely affected while the three siblings weren't is probably a threshold issue — Whois's top Note paragraph was the only one without `leading-relaxed`, so accumulated CJK line-box overflow upstream tipped it past the bug's trigger point. While touching these files: - Applied the same shadcn Label swap to the other three tools (Censorship / DNS Resolver / MAC Lookup) for consistency, so the same latent bug can't surface on those inputs in the future. - Added `leading-relaxed` to the top Note paragraphs that were missing it (Whois / MAC Lookup). - Added the `autocomplete / autocorrect / autocapitalize=off` + `spellcheck=false` attrs on Whois and MAC Lookup inputs — orthogonal hygiene for technical-string fields (domains, IPs, MAC addresses don't benefit from iOS text correction). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- QueryIP: skip programmatic focus on iOS. Inside a fixed-position Dialog, iOS Safari cannot scroll a programmatically-focused input into view above the on-screen keyboard. Letting the user tap the input delegates the whole keyboard + visualViewport dance to iOS natives. Desktop still auto-focuses on open. - QueryIP: replace deprecated `navigator.platform` with a Macintosh UA + `maxTouchPoints` check so iPadOS 13+ is still detected. - Apply a consistent attribute set (`autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" data-1p-ignore data-lpignore`) to every free-form Input (QueryIP, Whois, MacChecker, CensorshipCheck, DnsResolver, ConnectivityTest × 2). iOS QuickType was pushing iCloud-address and password AutoFill onto URL/IP/MAC inputs. - Rename ipcheck.Placeholder from \"Please enter an IP address\" style to \"e.g. 1.1.1.1\" in all four locales. iOS address-AutoFill heuristics fire on the word \"address / 地址 / adresse / adresi\" even with `autocomplete=\"off\"`. - Document the six-attribute convention in frontend/AGENTS.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Required for local testing against dev.ipcheck.ing / test.ipcheck.ing; Vite rejects Host headers that aren't on `server.allowedHosts`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the scattered length-threshold helpers (heroIpSizeClass in utils/, three copy-pasted fitOneLineClass functions across WebRtc / DnsLeak / RuleTest) with a single composable + wrapper component that measures real layout: - useFitText observes the element's parent via ResizeObserver and picks the largest Tailwind font-size tier whose rendered extent fits (scrollWidth for single line, scrollHeight for line-clamped multi-line). Recomputes on container resize and text change. - HERO_TIERS (xl → xs) for the prominent IPCard + QueryIP rows; INLINE_TIERS (base → xs) for the narrower test cards. - FitText wraps useFitText so v-for call sites get per-iteration observers without plumbing refs themselves. A #prefix slot lets an inline Monitor icon ride the first wrapped line instead of sitting at the visual center of a 2-line block. - IPCard / QueryIP opt into max-lines=2 so long IPv6 wraps rather than shrinking below text-sm on narrow mobile cards. Copy stays a flex sibling so the ellipsis clips only the IP, never the button. - DnsLeaksTest dropped a vestigial v-if/v-else pair and its unused isResolved() that only served the collapsed branch. - frontend/AGENTS.md gains a Fit-to-width section and lists the new module. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Before, `Promise.all` waited for the slowest DNS/DoH server; a single
unreachable provider could pin the response at 20+ seconds (Node's
default 5s timeout × 4 retries). Now:
- UDP DNS uses `new Resolver({ timeout: 3000, tries: 1 })`.
- DoH switches from bare `fetch` to `fetchUpstream` with `timeoutMs:
5000`, aligning with the project convention in api/AGENTS.md that
all upstream HTTP calls go through the timeout wrapper.
Slow / unreachable servers now resolve to `N/A` inside ~5s instead of
tens of seconds, capping total response time.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
whoiser's port-43 WHOIS path returns empty for newer gTLDs (.ing /
.app / .dev / …) that expose RDAP only. Keep whoiser as the primary
path — it still returns richer multi-provider text for legacy gTLDs
like .com / .us — and fall back to a minimal in-house RDAP client
(common/rdap.js) when whoiser produces no __raw for any provider.
The fallback:
- loads IANA's TLD → RDAP endpoint map (data.iana.org/rdap/dns.json)
with a 24h in-memory cache
- issues one RDAP GET via fetchUpstream (5s cap)
- formats the RDAP JSON into a WHOIS-like text block (status, events,
entities-by-role with vCard name / email / address, nameservers,
DNSSEC)
- returns the same { [host]: { __raw, ... } } shape as whoiser.domain()
so the frontend Accordion renders it with zero changes
IP queries stay on whoiser only — regional IP WHOIS servers are
widely reachable and RDAP adds no coverage there.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Swap ClipboardCheck / ClipboardPlus for CopyCheck / Copy (the pair used everywhere else in the app for copy affordances) and apply the same size-4 + text-success post-copy tint the IPCard copy button uses, so the UA copy button looks and behaves consistently. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The CircleCheck / CircleX on the Native IP row was always rendered in text-muted-foreground, which made the status glyph visually indistinguishable from the row label. Let it inherit the default text color so the check / cross carries its own weight. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AI assistants and human contributors alike should always base work on `dev` and commit back to `dev`; `main` is only reached via PRs merging `dev` → `main`. Also codifies the worktree-aware way to fast-forward `dev` from inside a secondary worktree: `git push . HEAD:dev` (with `receive.denyCurrentBranch=updateInstead` configured on the repo, so the main worktree's files auto-sync when clean) instead of `git update-ref`, which otherwise leaves the main worktree's HEAD and working tree out of sync. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
<img>-based probing has two failure modes that routinely produce
misleading results:
- Any non-2xx HTTP response (notably github.com/favicon.ico's 403 from
CDN hot-link rules) triggers onerror, flagging a reachable origin as
unavailable.
- The timing necessarily covers full-body download; favicons that ship
as 50-100 KB SVG/PNG blow up the measured latency well past the
actual RTT.
fetch(url, { mode: 'no-cors', method: 'HEAD', cache: 'no-store' })
fixes both: no-cors resolves the promise on any HTTP status (403
included), and HEAD returns only headers so payload size drops out of
the measurement. An AbortController replaces the bare setTimeout and
closes the old race where both onerror and the timeout could fire on
the same probe. Timing switches to performance.now() for float-ms
precision.
Bundled URL cleanups in the same change: strip the legacy trailing
"?" (cache-bust placeholder, superseded by cache: 'no-store') from
all built-in targets, and rename normalizeToOriginUrl →
normalizeTestUrl — it now preserves user-typed paths (so
"example.com/api/health" actually probes /api/health) and only
defaults to /favicon.ico when the user gave a bare domain, since "/"
routinely forces per-site backend logic that inflates HEAD latency
on major CDNs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 in-app sites still used bare fetch(), leaving them vulnerable to indefinite hangs on slow DNS / upstream flake: - DnsLeaksTest (edns.ip-api.com + surfsharkdns.com leak probes) - WebRtcTest (MaxMind country lookup for discovered STUN IPs) - SpeedTest (Cloudflare cdn-cgi/trace) - IpDetailPanel (/api/cfradar) - store.fetchConfigs (/api/configs) Swap each for fetchWithTimeout from @/utils/fetch-with-timeout.js (default 5s timeout, AbortController-backed). ConnectivityTest's HEAD + no-cors probe at line 272 stays as-is — its manual AbortController is part of the reachability-testing design, noted in the refactor plan. Co-authored-by: Claude <noreply@anthropic.com>
DnsResolver / Whois / CensorshipCheck each inlined the same hostname
regex (/^[a-z0-9-]+(\.[a-z0-9-]+)*\.[a-z]{2,}$/i) to decide whether a
parsed URL's hostname was domain-shaped. Promote it to a named
isValidDomain() export alongside isValidIP() — same re-export pattern
through frontend/utils/valid-ip.js — and update the three call sites.
Tests cover common + frontend entry points in parallel, matching how
isValidIP is already verified.
Co-authored-by: Claude <noreply@anthropic.com>
Three components had async work that could fire after the component was
gone, either writing refs on a dead instance or holding references the
engine side:
- ConnectivityTest: the autoRefresh setInterval was never cleared on
unmount. Also change intervalId's initial value from 3000 (a stale
leftover) to null so the cleanup guard reads correctly.
- Advanced.vue: the 1500ms setTimeout that unlocks the Invisibility
card never had its id tracked, so a fast route-change would leave
the callback in flight. Store the id and clearTimeout on unmount.
- SpeedTest: the existing onUnmounted nulled testEngine but the
SpeedTestEngine's own async work would still invoke
onRunningChange / onResultsChange / onFinish / onError on detached
refs. Null the callbacks first, matching what onFinish already does
at normal completion.
Co-authored-by: Claude <noreply@anthropic.com>
signInWithGoogle and signInWithGithub wrote this.alert = {...} directly,
bypassing the setAlert() action that every other call site uses. Route
them through setAlert so the alert mutation stays on one path.
Co-authored-by: Claude <noreply@anthropic.com>
…escription Two browser a11y warnings fired every time an Advanced Tools Drawer opened: 1. "Blocked aria-hidden on an element because its descendant retained focus" — clicking an Advanced Tools card kept focus on the card; when the Drawer opened, vaul tagged #mainpart (the card's ancestor) with aria-hidden="true", so the focused card sat inside an aria-hidden subtree. navigateAndToggleOffcanvas now blurs the click target before router.push, releasing focus so the descendant stays out of the hidden region. 2. "Missing Description or aria-describedby=\"undefined\" for DialogContent" — vaul-vue's DrawerContent renders reka-ui's Dialog primitive, which now requires a description. Add a VisuallyHidden DrawerDescription fallback that reuses the title when no description prop / slot is passed — same pattern the sibling DialogContent.vue already uses. Co-authored-by: Claude <noreply@anthropic.com>
The earlier commit silenced the "aria-hidden on focused descendant" warning at open time by blurring the click target, but Tab-focusing back into #mainpart while the Drawer was open brought the warning right back — aria-hidden alone doesn't remove descendants from the browser's focus ring. Bind :inert on #mainpart to store.openSheet !== null. inert is the spec-correct mechanism (explicitly recommended by the browser warning itself): it blocks focus, input, and pointer events on the subtree, so Tab skips past any main-content focusable while an overlay is up. Co-authored-by: Claude <noreply@anthropic.com>
The STUN test had two related correctness problems:
1. candidateReceived flipped to true on *any* candidate (including
host), so the 5s timeout's !candidateReceived guard almost never
fired. With Chrome / Firefox mDNS privacy on, host candidates are
abc.local hostnames that don't match the IP regex — stun.ip was
never updated, but the timeout backstop was already defeated, and
the row stayed on "Awaiting Test" forever. This is what prompted
the user report.
2. When mDNS privacy was off, the host candidate's local IP
(192.168.x.x) *did* match the regex — so the row proudly displayed
an internal IP as the "STUN server IP", implying STUN worked when
it hadn't.
Only server-reflexive / peer-reflexive candidates actually represent a
STUN answer. Filter on type ∈ {srflx, prflx} before accepting an IP.
Once we stop treating host candidates as success, the 5s backstop
becomes the sole non-happy path — worth distinguishing:
- StatusPrivacy: every candidate we saw was an mDNS .local host,
AND no srflx ever arrived → browser mDNS privacy is on AND STUN
didn't answer. More informative than generic "error".
- StatusTimeout: saw non-mDNS candidates (or none at all) but no
srflx → STUN server unreachable / UDP blocked / wrong URL.
- StatusError: unexpected throw (RTCPeerConnection construction etc).
Add locale keys webrtc.StatusTimeout / StatusPrivacy across en / zh /
fr / tr. Centralize the non-wait label set via an errorStatusLabels
computed so toneOf (via ipFieldTone) and isFieldPending stay in sync.
Also plug two small timer issues from the old shape:
- clearTimeout the 5s backstop on success (no stale closure).
- guard against double-settle via a single `settled` flag shared by
the success / failure paths.
Co-authored-by: Claude <noreply@anthropic.com>
…defined The old flow had fetchCountryCode return undefined on both the empty- response and caught-error branches. The caller then did countryInfo[0], got a TypeError, and relied on an outer try/catch to swallow it and stamp the "Error" country — a correct-by-accident loop through an exception that wasn't actually an exception. Switch to an explicit null return on every non-happy path (missing MaxMind source, empty upstream payload, network failure). succeedWith now checks once, drops its now-redundant try/catch, and surfaces the StatusError label directly. Co-authored-by: Claude <noreply@anthropic.com>
Matching the P1.2 sweep: if the component unmounts mid-test (user navigates away, route change, etc.) the RTCPeerConnection keeps ICE gathering for seconds and eventually tries to write to stun row refs that no longer belong to any live component. Track every pc created inside checkSTUNServer in an activeConnections Set, remove on close, and close the whole set in onBeforeUnmount. Co-authored-by: Claude <noreply@anthropic.com>
Revert 3a9ba2a and c448fae — user decided the Drawer a11y warnings are not worth fixing. The inert binding on #mainpart, the click- target blur in Advanced.vue, and the DrawerDescription fallback are all rolled back. This reverts the following commits: 3a9ba2a Fix(a11y): mark #mainpart inert while any overlay is open c448fae Fix(a11y): silence Drawer aria-hidden warning and add DialogContent description Co-authored-by: Claude <noreply@anthropic.com>
…rror a401f78 tried to distinguish "5s timeout" from "mDNS privacy" based on whether every observed candidate was a .local host. In practice the two conditions are independent: Chrome / Firefox default to mDNS privacy on, so a genuine STUN URL failure still gets reported as StatusPrivacy whenever all host candidates happen to be mDNS — which is almost always. The split produces false positives and misleads more than it informs. Collapse back to a single vague StatusError for every non-success path. Keep the srflx / prflx filter (host candidates still aren't a STUN success signal) and the settle / timer-clear plumbing from a401f78 — those are orthogonal correctness fixes. Drop the now-unused StatusTimeout / StatusPrivacy locale keys across en / zh / fr / tr, and simplify the tone / pending checks back to comparing against the two original labels. Co-authored-by: Claude <noreply@anthropic.com>
…EAVD Refactor(frontend): script-layer audit — composables, fetch timeouts, STUN fixes
New advanced tool at /enhanceddnsleaktest: generates a 32-hex session
token client-side, fires four <img> DNS probes in parallel, then pulls
the captured recursive-resolver rows (IP / ASN / geo / ECS / DNSSEC
DO+CD) from the main IPCheck.ing API. Firebase sign-in + configs.originalSite
gated, matching the InvisibilityTest pattern.
- api/dns-leak-test.js — thin proxy to
GET {IPCHECKING_API_ENDPOINT}/dnsleaktest/session/:token, forwarding
Authorization and attaching the apikey query param; upstream status
and body passed through verbatim so the frontend can surface
"Sign in required" / "Invalid token".
- EnhancedDnsLeakTest.vue — full client flow: parallel probe firing
with sequential UI animation, single-region flow card (status dot +
stage text + percent + shadcn Progress), sortable / dedupable result
Table with ECS-first, country-grouped, numeric-IP ordering, DNSSEC
DO/CD rendered as green check / amber cross, field-reference legend.
- Homepage DNS Leak Test gains a fade-slide banner that surfaces the
deeper tool once the basic test settles (sticky, won't retract on
re-run) and a new D keyboard shortcut.
- Shadcn Table primitive copied in (via shadcn-vue add table).
- i18n: full 4-locale coverage for the enhanced tool, the banner, the
advancedtools card blurb, and the D shortcut description.
- Tests: dns-leak-test smoke coverage added; configs test expectation
aligned with restored handler.
- AGENTS.md: sync api/ and frontend/ docs with the new handler, new
advanced tool, new Table primitive, and the extra private-API
header-passthrough consumer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Switching the fetch probe from HEAD to GET. Many servers / CDNs / WAFs silently drop HEAD, route it differently, or close the connection — in no-cors mode that rejects the promise and falsely marks reachable sites as unavailable. User-added custom endpoints are especially prone to this because most health-check / API paths only implement GET. The payload overhead (a few KB of favicon on a CDN edge, discarded as an opaque response) is a small price for the correctness. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously reloadMaxMindDatabases ran fire-and-forget at the top level
and only logged an error when the .mmdb files were missing — the server
would start but MaxMind API returned 503 forever until someone noticed.
Now backend-server.js runs an explicit bootBackend() that:
1. bootstrapMaxMindIfMissing — if files are absent, check for
MAXMIND_ACCOUNT_ID / MAXMIND_LICENSE_KEY and either print a clear
"how to fix" warning or run a synchronous download cycle capped at
5 minutes (AbortController threaded through fetch + pipeline so the
abort actually stops in-flight I/O).
2. reloadMaxMindDatabases — load readers.
3. startMaxMindFileWatcher + startMaxMindAutoUpdate — unchanged.
4. app.listen — only after the steps above.
The 5-minute cap is a total timeout only; we deliberately skipped the
"no-bytes-for-N-seconds stall detector" to keep the path simple. On any
failure (bad credentials, blocked outbound, timeout) the server still
starts — just with MaxMind returning 503 and a clear operator warning.
MAXMIND_AUTO_UPDATE is intentionally NOT consulted by bootstrap; that
flag only gates the periodic scheduler. Credentials alone are enough
signal that the operator wants MaxMind working.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace all backend console.* with a shared pino singleton at common/logger.js. Startup-style lines get emoji prefixes (🚀 listening, 📦 maxmind ready, 📥 downloading, 🗓️ schedule,⚠️ warning, ❌ failure); per-handler / per-request logs stay plain. Three .env-driven knobs, all without NODE_ENV: - LOG_LEVEL (default info) — pino level filter - LOG_FORMAT (default pretty) — "json" for log shippers - LOG_HTTP (default off) — pino-http on /api/* gated behind this flag so pm2 logs don't bloat with 2xx/3xx request lines on every deploy; when enabled, 4xx log as warn and 5xx as error logger.js calls dotenv.config({ quiet: true }) itself because ES module imports are hoisted above backend-server.js's dotenv call — without that LOG_LEVEL from .env was silently ignored. Fixes in common/rdap.js: - err: res.status → status: res.status (pino's err serializer expects an Error object, not a number) - broken single-quoted template literal in "No RDAP endpoint" message - demote "No RDAP endpoint" and "Domain not found" from error to warn (they're expected outcomes for user input, not server failures) README (en/zh/fr/tr) + .env.example + AGENTS.md (root and api/) all updated to document the three new vars and the "no console.* in backend" convention. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fixed overlay (position: fixed) avoids mobile 100vh scrollbars, and App.vue hands off in three stages — text fade, logo shrink, then reveal #app with a scale-in — instead of display:none'ing the boot screen instantly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )