diff --git a/.gitignore b/.gitignore index 347da86..9b34696 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ SPEC.md /docs/ node_modules/ +/frontend/dist/ diff --git a/Makefile b/Makefile index a179856..2d3358e 100644 --- a/Makefile +++ b/Makefile @@ -2,9 +2,11 @@ all: build -# Rebuild the dashboard UI (output is committed at internal/web/dist). +# Build the dashboard UI for a separate host (Vercel, Render, or any +# static host). Point it at the subidx API with VITE_API_BASE; the +# binary no longer embeds or serves the UI. ui: - cd internal/web && npm ci && npm run build + cd frontend && npm ci && npm run build build: go build -o subidx . @@ -15,7 +17,7 @@ vet: go vet ./... serve: go run . serve -store ./data -addr 127.0.0.1:8099 dev: - cd internal/web && SUBIDX_API=$${SUBIDX_API:-http://127.0.0.1:8080} npm run dev + cd frontend && SUBIDX_API=$${SUBIDX_API:-http://127.0.0.1:8080} npm run dev clean: rm -f subidx diff --git a/README.md b/README.md index b83a073..c4699bc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # subidx -Self-hosted passive subdomain enumeration from Certificate Transparency. subidx tails public CT logs (the records of every HTTPS certificate issued), indexes every hostname it sees, and serves them through a search API and dashboard you control. Think crt.name, but the data lives on your machine. +Self-hosted passive subdomain enumeration from Certificate Transparency. subidx tails public CT logs (the records of every HTTPS certificate issued), indexes every hostname it sees, and serves them through a search API you control. Think crt.name, but the data lives on your machine. ![subidx dashboard](assets/dashboard.png) @@ -16,7 +16,7 @@ go build -o subidx . curl "http://localhost:8099/v1/search?apex=letsencrypt.org" ``` -or open `http://localhost:8099/` for the dashboard: search any apex, filter and sort results instantly, toggle first-seen dates, export txt/csv, and watch a live feed of newly collected names. The UI is compiled into the binary; there is nothing extra to deploy. +The binary is API-only. The dashboard is a separate SPA in `frontend/` (Vite + Svelte): search any apex, filter and sort results instantly, toggle first-seen dates, export txt/csv, and watch a live feed of newly collected names. Deploy it to any static host (Vercel, Render, or your own), pointing it at the API with `VITE_API_BASE` and allowing its origin with `-cors-origins`. For local work, `make dev` runs it against a local `subidx serve`. ## What gets stored @@ -35,7 +35,7 @@ Tools like [subfinder](https://github.com/projectdiscovery/subfinder) query othe - **No keys, no quotas.** Your only dependencies are the log lists themselves. - **Verified provenance.** Log keys are pinned against the log lists, tree heads are cryptographically verified, and each fetched batch must pass an RFC 6962 inclusion-proof spot check before it is stored. - **Historical drains.** Years of history from retired logs can be backfilled, if you have the terabytes. -- **An API and dashboard, not just a CLI.** Pipe results into your tooling or browse them. +- **An API and dashboard, not just a CLI.** Pipe results into your tooling, or browse them in the separately hosted UI. Limitations: CT only sees names that were issued a certificate. Names that live only in DNS are invisible here, which is why subidx complements rather than replaces broader-source tools. @@ -46,14 +46,14 @@ Limitations: CT only sees names that were issued a certificate. Names that live 3. **Parsing.** Entries are decoded just far enough to read the SAN list; everything else is discarded. 4. **Normalizing.** Names are lowercased, wildcards and trailing dots stripped, reserved names rejected, and each name assigned to its registered domain (apex) via the Public Suffix List. 5. **Storing.** A single writer inserts into an embedded Pebble store. Duplicates keep only the earliest date; nothing is deleted. -6. **Serving.** An HTTP API with rate limiting and health checks, plus the embedded dashboard. +6. **Serving.** An HTTP API with rate limiting and health checks. The dashboard is a separate frontend built from `frontend/` and served by any static host. ## Commands | Command | What it does | |---|---| | `tail` | Watch CT logs and store names. Runs until you stop it. | -| `serve` | Serve the API and dashboard. Add `-no-tail` to serve without collecting. | +| `serve` | Serve the read-only search API and health endpoints. Add `-no-tail` to serve without collecting. | | `stats` | Print total records and the top 10 domains. `-recount` fixes drifted counters. | | `version` | Print the version. | diff --git a/internal/web/.gitignore b/frontend/.gitignore similarity index 100% rename from internal/web/.gitignore rename to frontend/.gitignore diff --git a/internal/web/index.html b/frontend/index.html similarity index 100% rename from internal/web/index.html rename to frontend/index.html diff --git a/internal/web/package-lock.json b/frontend/package-lock.json similarity index 96% rename from internal/web/package-lock.json rename to frontend/package-lock.json index 8b04108..86399b8 100644 --- a/internal/web/package-lock.json +++ b/frontend/package-lock.json @@ -7,6 +7,9 @@ "": { "name": "subidx-ui", "version": "0.1.0", + "dependencies": { + "@vercel/analytics": "^2.0.1" + }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^6.0.0", "svelte": "^5.0.0", @@ -461,7 +464,7 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -472,7 +475,7 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -483,7 +486,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -493,14 +496,14 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -920,7 +923,7 @@ "version": "1.0.13", "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.13.tgz", "integrity": "sha512-wgKggnhZVL9Bfx1OaKKTrYY9BFRk6C8UAkQNUcIv1+llzYrIqy+RZm5HPKzn0NpEBvTVhTqB4kQyllZywsRBRQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "acorn": "^8.9.0" @@ -979,21 +982,63 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "dev": true, + "devOptional": true, "license": "MIT" }, + "node_modules/@vercel/analytics": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-2.0.1.tgz", + "integrity": "sha512-MTQG6V9qQrt1tsDeF+2Uoo5aPjqbVPys1xvnIftXSJYG2SrwXRHnqEvVoYID7BTruDz4lCd2Z7rM1BdkUehk2g==", + "license": "MIT", + "peerDependencies": { + "@remix-run/react": "^2", + "@sveltejs/kit": "^1 || ^2", + "next": ">= 13", + "nuxt": ">= 3", + "react": "^18 || ^19 || ^19.0.0-rc", + "svelte": ">= 4", + "vue": "^3", + "vue-router": "^4" + }, + "peerDependenciesMeta": { + "@remix-run/react": { + "optional": true + }, + "@sveltejs/kit": { + "optional": true + }, + "next": { + "optional": true + }, + "nuxt": { + "optional": true + }, + "react": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vue": { + "optional": true + }, + "vue-router": { + "optional": true + } + } + }, "node_modules/acorn": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -1006,7 +1051,7 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" @@ -1016,7 +1061,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" @@ -1042,7 +1087,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -1062,7 +1107,7 @@ "version": "5.9.1", "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.9.1.tgz", "integrity": "sha512-+17vil3EVQRzvtDJSFuTWEb8XJRvXqAiV3qZyQWD398QeXUa6CxsUyMdD1fxzEhUrd4FojitFz7lhIHBTlV4fw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/esbuild": { @@ -1111,14 +1156,14 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/esrap": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.3.6.tgz", "integrity": "sha512-yc0OC12UjPqLoc+fe+v5GNs4TOjAigUw3sTikfC+xeBPGUw7gDRz3DtYaqEhxyMVJojcSWJw7jT0QWR+CbuE/A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -1169,7 +1214,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.6" @@ -1179,14 +1224,14 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" @@ -1371,7 +1416,7 @@ "version": "5.56.10", "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.56.10.tgz", "integrity": "sha512-Lcxbj8I/KAbpY+VjtY4ENQBV0dDCipfGAhqb51XQZ67CIQqXgsv/8dPkbILaj4Fb6/b6JAEM/PIVbILXgDQy2g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.4", @@ -1550,7 +1595,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", - "dev": true, + "devOptional": true, "license": "MIT" } } diff --git a/internal/web/package.json b/frontend/package.json similarity index 86% rename from internal/web/package.json rename to frontend/package.json index e72e245..93b8edf 100644 --- a/internal/web/package.json +++ b/frontend/package.json @@ -14,5 +14,8 @@ "svelte-check": "^4.1.0", "typescript": "^5.7.0", "vite": "^7.0.0" + }, + "dependencies": { + "@vercel/analytics": "^2.0.1" } } diff --git a/internal/web/dist/favicon.svg b/frontend/public/favicon.svg similarity index 100% rename from internal/web/dist/favicon.svg rename to frontend/public/favicon.svg diff --git a/internal/web/src/App.svelte b/frontend/src/App.svelte similarity index 100% rename from internal/web/src/App.svelte rename to frontend/src/App.svelte diff --git a/internal/web/src/app.css b/frontend/src/app.css similarity index 100% rename from internal/web/src/app.css rename to frontend/src/app.css diff --git a/internal/web/src/lib/VirtualList.svelte b/frontend/src/lib/VirtualList.svelte similarity index 100% rename from internal/web/src/lib/VirtualList.svelte rename to frontend/src/lib/VirtualList.svelte diff --git a/internal/web/src/lib/api.ts b/frontend/src/lib/api.ts similarity index 100% rename from internal/web/src/lib/api.ts rename to frontend/src/lib/api.ts diff --git a/internal/web/src/lib/format.ts b/frontend/src/lib/format.ts similarity index 100% rename from internal/web/src/lib/format.ts rename to frontend/src/lib/format.ts diff --git a/internal/web/src/main.ts b/frontend/src/main.ts similarity index 76% rename from internal/web/src/main.ts rename to frontend/src/main.ts index fcf8922..3bbf8c8 100644 --- a/internal/web/src/main.ts +++ b/frontend/src/main.ts @@ -1,7 +1,10 @@ import { mount } from 'svelte' +import { inject } from '@vercel/analytics' import './app.css' import App from './App.svelte' const app = mount(App, { target: document.getElementById('app')! }) +inject() + export default app diff --git a/internal/web/src/vite-env.d.ts b/frontend/src/vite-env.d.ts similarity index 100% rename from internal/web/src/vite-env.d.ts rename to frontend/src/vite-env.d.ts diff --git a/internal/web/svelte.config.js b/frontend/svelte.config.js similarity index 100% rename from internal/web/svelte.config.js rename to frontend/svelte.config.js diff --git a/internal/web/tsconfig.json b/frontend/tsconfig.json similarity index 100% rename from internal/web/tsconfig.json rename to frontend/tsconfig.json diff --git a/internal/web/vercel.json b/frontend/vercel.json similarity index 100% rename from internal/web/vercel.json rename to frontend/vercel.json diff --git a/internal/web/vite.config.ts b/frontend/vite.config.ts similarity index 100% rename from internal/web/vite.config.ts rename to frontend/vite.config.ts diff --git a/internal/server/server.go b/internal/server/server.go index 2d14c25..f44838a 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -16,7 +16,6 @@ import ( "subidx/internal/apex" "subidx/internal/store" - "subidx/internal/web" ) type Server struct { @@ -80,7 +79,6 @@ func (s *Server) Handler() http.Handler { mux.HandleFunc("/v1/feed", s.handleFeed) mux.HandleFunc("/healthz", s.handleHealth) mux.HandleFunc("/readyz", s.handleReady) - mux.Handle("/", web.Handler()) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !s.hostAllowed(r.Host) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") diff --git a/internal/server/server_test.go b/internal/server/server_test.go index d59a725..74b80c3 100644 --- a/internal/server/server_test.go +++ b/internal/server/server_test.go @@ -281,10 +281,10 @@ func TestHeadNotAllowed(t *testing.T) { if po.Code != http.StatusMethodNotAllowed { t.Errorf("post code = %d", po.Code) } - // Unknown paths serve the embedded UI shell instead of a bare 404. + // Unknown paths are a bare 404; the UI is a separately hosted app now. nf := do(t, s, http.MethodGet, "/nope") - if nf.Code != http.StatusOK || !strings.Contains(nf.Header().Get("Content-Type"), "text/html") { - t.Errorf("ui shell code = %d ct = %q", nf.Code, nf.Header().Get("Content-Type")) + if nf.Code != http.StatusNotFound { + t.Errorf("unknown path code = %d, want 404", nf.Code) } } diff --git a/internal/web/dist/assets/index-B5egPO6h.js b/internal/web/dist/assets/index-B5egPO6h.js deleted file mode 100644 index 0c156de..0000000 --- a/internal/web/dist/assets/index-B5egPO6h.js +++ /dev/null @@ -1,10 +0,0 @@ -(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))r(s);new MutationObserver(s=>{for(const i of s)if(i.type==="childList")for(const a of i.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&r(a)}).observe(document,{childList:!0,subtree:!0});function n(s){const i={};return s.integrity&&(i.integrity=s.integrity),s.referrerPolicy&&(i.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?i.credentials="include":s.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function r(s){if(s.ep)return;s.ep=!0;const i=n(s);fetch(s.href,i)}})();const gr=!1;var mr=Array.isArray,Vs=Array.prototype.indexOf,Ct=Array.prototype.includes,It=Array.from,Ys=Object.defineProperty,ct=Object.getOwnPropertyDescriptor,Ws=Object.getOwnPropertyDescriptors,Ks=Object.prototype,Gs=Array.prototype,wr=Object.getPrototypeOf,nr=Object.isExtensible;const br=()=>{};function Js(e){for(var t=0;t{e=r,t=s});return{promise:n,resolve:e,reject:t}}const H=2,Ke=4,Dt=8,Er=1<<24,ae=16,ee=32,xe=64,sn=128,Q=512,$=1024,D=2048,ue=4096,z=8192,Z=16384,Qe=32768,ln=1<<25,$e=65536,Nt=1<<17,Xs=1<<18,Ze=1<<19,Qs=1<<20,de=1<<25,He=65536,Mt=1<<21,We=1<<22,Ae=1<<23,kt=Symbol("$state"),Zs=Symbol(""),xr=Symbol("attributes"),an=Symbol("class"),on=Symbol("style"),fn=Symbol("text"),qt=Symbol("form reset"),gt=new class extends Error{name="StaleReactionError";message="The reaction that called `getAbortSignal()` was re-run or destroyed"};function ei(e){throw new Error("https://svelte.dev/e/lifecycle_outside_component")}function ti(){throw new Error("https://svelte.dev/e/async_derived_orphan")}function ni(e,t,n){throw new Error("https://svelte.dev/e/each_key_duplicate")}function ri(e){throw new Error("https://svelte.dev/e/effect_in_teardown")}function si(){throw new Error("https://svelte.dev/e/effect_in_unowned_derived")}function ii(e){throw new Error("https://svelte.dev/e/effect_orphan")}function li(){throw new Error("https://svelte.dev/e/effect_update_depth_exceeded")}function ai(){throw new Error("https://svelte.dev/e/state_descriptors_fixed")}function oi(){throw new Error("https://svelte.dev/e/state_prototype_fixed")}function fi(){throw new Error("https://svelte.dev/e/state_unsafe_mutation")}function ui(){throw new Error("https://svelte.dev/e/svelte_boundary_reset_onerror")}const ci=1,vi=2,Sr=4,di=8,hi=16,pi=2,P=Symbol("uninitialized"),_i="http://www.w3.org/1999/xhtml";function gi(){console.warn("https://svelte.dev/e/derived_inert")}function mi(){console.warn("https://svelte.dev/e/svelte_boundary_reset_noop")}function Tr(e){return e===this.v}function wi(e,t){return e!=e?t==t:e!==t||e!==null&&typeof e=="object"||typeof e=="function"}function kr(e){return!wi(e,this.v)}let bi=!1,j=null;function Ge(e){j=e}function _n(e,t=!1,n){j={p:j,i:!1,c:null,e:null,s:e,x:null,r:k,l:null}}function gn(e){var t=j,n=t.e;if(n!==null){t.e=null;for(var r of n)Vr(r)}return t.i=!0,j=t.p,{}}function qr(){return!0}let Le=[];function Ar(){var e=Le;Le=[],Js(e)}function Ee(e){if(Le.length===0&&!vt){var t=Le;queueMicrotask(()=>{t===Le&&Ar()})}Le.push(e)}function yi(){for(;Le.length>0;)Ar()}function Rr(e){var t=k;if(t===null)return T.f|=Ae,e;if((t.f&Qe)===0&&(t.f&Ke)===0)throw e;qe(e,t)}function qe(e,t){if(!(t!==null&&(t.f&Z)!==0)){for(;t!==null;){if((t.f&sn)!==0){if((t.f&Qe)===0)throw e;try{t.b.error(e);return}catch(n){e=n}}t=t.parent}throw e}}const Ei=-7169;function I(e,t){e.f=e.f&Ei|t}function mn(e){(e.f&Q)!==0||e.deps===null?I(e,$):I(e,ue)}function Cr(e){if(e!==null)for(const t of e)(t.f&H)===0||(t.f&He)===0||(t.f^=He,Cr(t.deps))}function Nr(e,t,n){(e.f&D)!==0?t.add(e):(e.f&ue)!==0&&n.add(e),Cr(e.deps),I(e,$)}let rr=!1;function xi(){rr||(rr=!0,document.addEventListener("reset",e=>{Promise.resolve().then(()=>{if(!e.defaultPrevented)for(const t of e.target.elements)t[qt]?.()})},{capture:!0}))}function et(e){var t=T,n=k;te(null),_e(null);try{return e()}finally{te(t),_e(n)}}function Si(e,t,n,r=n){e.addEventListener(t,()=>et(n));const s=e[qt];s?e[qt]=()=>{s(),r(!0)}:e[qt]=()=>r(!0),xi()}function Ti(e){let t=0,n=Ue(0),r;return()=>{Sn()&&(f(n),Tn(()=>(t===0&&(r=$t(()=>e(()=>dt(n)))),t+=1,()=>{Ee(()=>{t-=1,t===0&&(r?.(),r=void 0,dt(n))})})))}}var ki=$e|Ze;function qi(e,t,n,r){new Ai(e,t,n,r)}class Ai{parent;is_pending=!1;transform_error;#t;#l=null;#e;#o;#r;#i=null;#n=null;#a=null;#s=null;#p=0;#f=0;#u=!1;#v=new Set;#_=new Set;#c=null;#m=Ti(()=>(this.#c=Ue(this.#p),()=>{this.#c=null}));constructor(t,n,r,s){this.#t=t,this.#e=n,this.#o=i=>{var a=k;a.b=this,a.f|=sn,r(i)},this.parent=k.b,this.transform_error=s??this.parent?.transform_error??(i=>i),this.#r=Pt(()=>{this.#d()},ki)}#g(){try{this.#i=X(()=>this.#o(this.#t))}catch(t){this.error(t)}}#y(t){const n=this.#e.failed,{reset:r,invoke_onerror:s}=this.#w(t);Ee(s),n&&(this.#a=X(()=>{n(this.#t,()=>t,()=>r)}))}#w(t){var n=!1,r=!1;const s=()=>{if(n){mi();return}n=!0,r&&ui(),this.#a!==null&&De(this.#a,()=>{this.#a=null}),this.#h(()=>{this.#d()})};return{reset:s,invoke_onerror:()=>{try{r=!0,this.#e.onerror?.(t,s),r=!1}catch(a){qe(a,this.#r&&this.#r.parent)}}}}#E(){const t=this.#e.pending;t&&(this.is_pending=!0,this.#n=X(()=>t(this.#t)),Ee(()=>{var n=this.#s=document.createDocumentFragment(),r=Re();n.append(r),this.#i=this.#h(()=>X(()=>this.#o(r))),this.#f===0&&(this.#t.before(n),this.#s=null,De(this.#n,()=>{this.#n=null}),this.#b(S))}))}#d(){try{if(this.is_pending=this.has_pending_snippet(),this.#f=0,this.#p=0,this.#i=X(()=>{this.#o(this.#t)}),this.#f>0){var t=this.#s=document.createDocumentFragment();qn(this.#i,t);const n=this.#e.pending;this.#n=X(()=>n(this.#t))}else this.#b(S)}catch(n){this.error(n)}}#b(t){this.is_pending=!1,t.transfer_effects(this.#v,this.#_)}defer_effect(t){Nr(t,this.#v,this.#_)}is_rendered(){return!this.is_pending&&(!this.parent||this.parent.is_rendered())}has_pending_snippet(){return!!this.#e.pending}#h(t){var n=k,r=T,s=j;_e(this.#r),te(this.#r),Ge(this.#r.ctx);try{return Ce.ensure(),t()}catch(i){return Rr(i),null}finally{_e(n),te(r),Ge(s)}}#x(t,n){if(!this.has_pending_snippet()){this.parent&&this.parent.#x(t,n);return}this.#f+=t,this.#f===0&&(this.#b(n),this.#n&&De(this.#n,()=>{this.#n=null}),this.#s&&(this.#t.before(this.#s),this.#s=null))}update_pending_count(t,n){this.#x(t,n),this.#p+=t,!(!this.#c||this.#u)&&(this.#u=!0,Ee(()=>{this.#u=!1,this.#c&&Je(this.#c,this.#p)}))}get_effect_pending(){return this.#m(),f(this.#c)}error(t){if(!this.#e.onerror&&!this.#e.failed)throw t;S?.is_fork?(this.#i&&S.skip_effect(this.#i),this.#n&&S.skip_effect(this.#n),this.#a&&S.skip_effect(this.#a),S.oncommit(()=>{this.#S(t)})):this.#S(t)}#S(t){this.#i&&(Y(this.#i),this.#i=null),this.#n&&(Y(this.#n),this.#n=null),this.#a&&(Y(this.#a),this.#a=null);let n=this.#e.failed;const r=s=>{const{reset:i,invoke_onerror:a}=this.#w(s);a(),n&&(this.#a=this.#h(()=>{try{return X(()=>{var o=k;o.b=this,o.f|=sn,n(this.#t,()=>s,()=>i)})}catch(o){return qe(o,this.#r.parent),null}}))};Ee(()=>{var s;try{s=this.transform_error(t)}catch(i){qe(i,this.#r&&this.#r.parent);return}s!==null&&typeof s=="object"&&typeof s.then=="function"?s.then(r,i=>qe(i,this.#r&&this.#r.parent)):r(s)})}}function Ri(e,t,n,r){const s=wn;var i=e.filter(p=>!p.settled),a=t.map(s);if(n.length===0&&i.length===0){r(a);return}var o=k,l=Ci(),u=i.length===1?i[0].promise:i.length>1?Promise.all(i.map(p=>p.promise)):null;function v(p){if((o.f&Z)===0){l();try{r([...a,...p])}catch(g){qe(g,o)}Ft()}}var d=Mr();if(n.length===0){u.then(()=>v([])).finally(d);return}function c(){Promise.all(n.map(p=>Ni(p))).then(v).catch(p=>qe(p,o)).finally(d)}u?u.then(()=>{l(),c(),Ft()}):c()}function Ci(){var e=k,t=T,n=j,r=S;return function(i=!0){_e(e),te(t),Ge(n),i&&(e.f&Z)===0&&(r?.activate(),r?.apply())}}function Ft(e=!0){_e(null),te(null),Ge(null),e&&S?.deactivate()}function Mr(){var e=k,t=e.b,n=S,r=!!t?.is_rendered();return t?.update_pending_count(1,n),n.increment(r,e),()=>{t?.update_pending_count(-1,n),n.decrement(r,e)}}function wn(e){var t=H|D;return k!==null&&(k.f|=Ze),{ctx:j,deps:null,effects:null,equals:Tr,f:t,fn:e,reactions:null,rv:0,v:P,wv:0,parent:k,ac:null}}const ot=Symbol("obsolete");function Ni(e,t,n){let r=k;r===null&&ti();var s=void 0,i=Ue(P),a=!T,o=new Set;return Ki(()=>{var l=k,u=yr();s=u.promise;try{Promise.resolve(e()).then(u.resolve,p=>{p!==gt&&u.reject(p)}).finally(Ft)}catch(p){u.reject(p),Ft()}var v=S;if(a){if((l.f&Qe)!==0)var d=Mr();if(r.b?.is_rendered())v.async_deriveds.get(l)?.reject(ot);else for(const p of o.values())p.reject(ot);o.add(u),v.async_deriveds.set(l,u)}const c=(p,g=void 0)=>{d?.(),o.delete(u),g!==ot&&(v.activate(),g?(i.f|=Ae,Je(i,g)):((i.f&Ae)!==0&&(i.f^=Ae),Je(i,p)),v.deactivate())};u.promise.then(c,p=>c(null,p||"unknown"))}),zr(()=>{for(const l of o)l.reject(ot)}),new Promise(l=>{function u(v){function d(){v===s?l(i):u(s)}v.then(d,d)}u(s)})}function ke(e){const t=wn(e);return Jr(t),t}function Mi(e){const t=wn(e);return t.equals=kr,t}function Fi(e){var t=e.effects;if(t!==null){e.effects=null;for(var n=0;n{t.ac.abort(gt),t.ac=null}),t.fn!==null&&(t.teardown=br),pt(t,0),kn(t))}function Or(e){if(e.effects!==null)for(const t of e.effects)t.teardown&&t.fn!==null&&Xe(t)}let Jt=null,je=null,S=null,un=null,oe=null,cn=null,vt=!1,Xt=!1,Ve=null,At=null;var sr=0;let Li=1;class Ce{id=Li++;#t=!1;linked=!0;#l=null;#e=null;async_deriveds=new Map;current=new Map;previous=new Map;#o=new Set;#r=new Set;#i=0;#n=new Map;#a=null;#s=[];#p=[];#f=new Set;#u=new Set;#v=new Map;#_=new Set;is_fork=!1;#c=!1;constructor(){je===null?Jt=je=this:(je.#e=this,this.#l=je),je=this}#m(){if(this.is_fork)return!0;for(const r of this.#n.keys()){for(var t=r,n=!1;t.parent!==null;){if(this.#v.has(t)){n=!0;break}t=t.parent}if(!n)return!0}return!1}skip_effect(t){this.#v.has(t)||this.#v.set(t,{d:[],m:[]}),this.#_.delete(t)}unskip_effect(t,n=r=>this.schedule(r)){var r=this.#v.get(t);if(r){this.#v.delete(t);for(var s of r.d)I(s,D),n(s);for(s of r.m)I(s,ue),n(s)}this.#_.add(t)}#g(){this.#t=!0,sr++>1e3&&(this.#h(),Di());for(const l of this.#f)this.#u.delete(l),I(l,D),this.schedule(l);for(const l of this.#u)I(l,ue),this.schedule(l);const t=this.#s;this.#s=[],this.apply();var n=Ve=[],r=[],s=At=[];for(const l of t)try{this.#y(l,n,r)}catch(u){throw Dr(l),this.#m()||this.discard(),u}if(S=null,s.length>0){var i=Ce.ensure();for(const l of s)i.schedule(l)}if(Ve=null,At=null,this.#m()){this.#d(r),this.#d(n);for(const[l,u]of this.#v)Ir(l,u);s.length>0&&S.#g();return}const a=this.#w();if(a){this.#d(r),this.#d(n),a.#E(this);return}this.#f.clear(),this.#u.clear();for(const l of this.#o)l(this);this.#o.clear(),un=this,ir(r),ir(n),un=null,this.#a?.resolve();var o=S;if(this.#i===0&&(this.#s.length===0||o!==null)&&this.#h(),this.#s.length>0)if(o!==null){const l=o;l.#s.push(...this.#s.filter(u=>!l.#s.includes(u)))}else o=this;o!==null&&(he.clear(),o.#g())}#y(t,n,r){t.f^=$;for(var s=t.first;s!==null;){var i=s.f,a=(i&(ee|xe))!==0,o=a&&(i&$)!==0,l=o||(i&z)!==0||this.#v.has(s);if(!l&&s.fn!==null){a?s.f^=$:(i&Ke)!==0?n.push(s):wt(s)&&((i&ae)!==0&&this.#u.add(s),Xe(s));var u=s.first;if(u!==null){s=u;continue}}for(;s!==null;){var v=s.next;if(v!==null){s=v;break}s=s.parent}}}#w(){for(var t=this.#l;t!==null;){if(!t.is_fork){for(const[n,[,r]]of this.current)if(t.current.has(n)&&!r)return t}t=t.#l}return null}#E(t){for(const[r,s]of t.current)!this.previous.has(r)&&t.previous.has(r)&&this.previous.set(r,t.previous.get(r)),this.current.set(r,s);for(const[r,s]of t.async_deriveds){const i=this.async_deriveds.get(r);i&&s.promise.then(i.resolve).catch(i.reject)}t.async_deriveds.clear(),this.transfer_effects(t.#f,t.#u);const n=r=>{var s=r.reactions;if(s!==null&&!((r.f&H)!==0&&(r.f&(D|ue))===0))for(const o of s){var i=o.f;if((i&H)!==0)n(o);else{var a=o;i&(We|ae)&&!this.async_deriveds.has(a)&&(this.#u.delete(a),I(a,D),this.schedule(a))}}};for(const r of this.current.keys())n(r);this.oncommit(()=>t.discard()),t.#h(),S=this,this.#g()}#d(t){for(var n=0;n!d.current.get(c)[1]);if(!(!d.#t||s.length===0)){var i=s.filter(c=>!this.current.has(c));if(i.length===0)t&&d.discard();else if(n.length>0){if(t)for(const c of this.#_)d.unskip_effect(c,p=>{(p.f&(ae|We))!==0?d.schedule(p):d.#d([p])});d.activate();var a=new Set,o=new Map;for(var l of n)Lr(l,i,a,o);o=new Map;var u=[...d.current].filter(([c,p])=>{const g=this.current.get(c);return g?g[0]!==p[0]||g[1]!==p[1]:!0}).map(([c])=>c);if(u.length>0)for(const c of this.#p)(c.f&(Z|z|Nt))===0&&yn(c,u,o)&&((c.f&(We|ae))!==0?(I(c,D),d.schedule(c)):d.#f.add(c));if(d.#s.length>0&&!d.#c){d.apply();for(var v of d.#s)d.#y(v,[],[]);d.#s=[]}d.deactivate()}}}}increment(t,n){if(this.#i+=1,t){let r=this.#n.get(n)??0;this.#n.set(n,r+1)}}decrement(t,n){if(this.#i-=1,t){let r=this.#n.get(n)??0;r===1?this.#n.delete(n):this.#n.set(n,r-1)}this.#c||(this.#c=!0,Ee(()=>{this.#c=!1,this.linked&&this.flush()}))}transfer_effects(t,n){for(const r of t)this.#f.add(r);for(const r of n)this.#u.add(r);t.clear(),n.clear()}oncommit(t){this.#o.add(t)}ondiscard(t){this.#r.add(t)}settled(){return(this.#a??=yr()).promise}static ensure(){if(S===null){const t=S=new Ce;!Xt&&!vt&&Ee(()=>{t.#t||t.flush()})}return S}apply(){{oe=null;return}}schedule(t){if(cn=t,t.b?.is_pending&&(t.f&(Ke|Dt|Er))!==0&&(t.f&Qe)===0){t.b.defer_effect(t);return}for(var n=t;n.parent!==null;){n=n.parent;var r=n.f;if(Ve!==null&&n===k&&(T===null||(T.f&H)===0))return;if((r&(xe|ee))!==0){if((r&$)===0)return;n.f^=$}}this.#s.push(n)}#h(){if(this.linked){var t=this.#l,n=this.#e;t===null?Jt=n:t.#e=n,n===null?je=t:n.#l=t,this.linked=!1}}}function Ii(e){var t=vt;vt=!0;try{for(var n;;){if(yi(),S===null)return n;S.flush()}}finally{vt=t}}function Di(){try{li()}catch(e){qe(e,cn)}}let ye=null;function ir(e){var t=e.length;if(t!==0){for(var n=0;n0)){he.clear();for(const s of ye){if((s.f&(Z|z))!==0)continue;const i=[s];let a=s.parent;for(;a!==null;)ye.has(a)&&(ye.delete(a),i.push(a)),a=a.parent;for(let o=i.length-1;o>=0;o--){const l=i[o];(l.f&(Z|z))===0&&Xe(l)}}ye.clear()}}ye=null}}function Lr(e,t,n,r){if(!n.has(e)&&(n.add(e),e.reactions!==null))for(const s of e.reactions){const i=s.f;(i&H)!==0?Lr(s,t,n,r):(i&(We|ae))!==0&&(i&D)===0&&yn(s,t,r)&&(I(s,D),En(s))}}function yn(e,t,n){const r=n.get(e);if(r!==void 0)return r;if(e.deps!==null)for(const s of e.deps){if(Ct.call(t,s))return!0;if((s.f&H)!==0&&yn(s,t,n))return n.set(s,!0),!0}return n.set(e,!1),!1}function En(e){S.schedule(e)}function Ir(e,t){if(!((e.f&ee)!==0&&(e.f&$)!==0)){(e.f&D)!==0?t.d.push(e):(e.f&ue)!==0&&t.m.push(e),I(e,$);for(var n=e.first;n!==null;)Ir(n,t),n=n.next}}function Dr(e){I(e,$);for(var t=e.first;t!==null;)Dr(t),t=t.next}let Ot=new Set;const he=new Map;let Pr=!1;function Ue(e,t){var n={f:0,v:e,reactions:null,equals:Tr,rv:0,wv:0};return n}function q(e,t){const n=Ue(e);return Jr(n),n}function Pi(e,t=!1,n=!0){const r=Ue(e);return t||(r.equals=kr),r}function w(e,t,n=!1){T!==null&&(!fe||(T.f&Nt)!==0)&&qr()&&(T.f&(H|ae|We|Nt))!==0&&(pe===null||!pe.has(e))&&fi();let r=n?Ye(t):t;return Je(e,r,At)}function Je(e,t,n=null){if(!e.equals(t)){Ne?he.set(e,t):he.has(e)||he.set(e,e.v);var r=Ce.ensure();if(r.capture(e,t),(e.f&H)!==0){const s=e;(e.f&D)!==0&&bn(s),oe===null&&mn(s)}e.wv=Qr(),$r(e,D,n),k!==null&&(k.f&$)!==0&&(k.f&(ee|xe))===0&&(J===null?Xi([e]):J.push(e)),!r.is_fork&&Ot.size>0&&!Pr&&$i()}return t}function $i(){Pr=!1;for(const e of Ot){(e.f&$)!==0&&I(e,ue);let t;try{t=wt(e)}catch{t=!0}t&&Xe(e)}Ot.clear()}function lt(e,t=1){var n=f(e),r=t===1?n++:n--;return w(e,n),r}function dt(e){w(e,e.v+1)}function $r(e,t,n){var r=e.reactions;if(r!==null)for(var s=r.length,i=0;i{if(Pe===i)return o();var l=T,u=Pe;te(null),or(i);var v=o();return te(l),or(u),v};return r&&n.set("length",q(e.length)),new Proxy(e,{defineProperty(o,l,u){(!("value"in u)||u.configurable===!1||u.enumerable===!1||u.writable===!1)&&ai();var v=n.get(l);return v===void 0?a(()=>{var d=q(u.value);return n.set(l,d),d}):w(v,u.value,!0),!0},deleteProperty(o,l){var u=n.get(l);if(u===void 0){if(l in o){const v=a(()=>q(P));n.set(l,v),dt(s)}}else w(u,P),dt(s);return!0},get(o,l,u){if(l===kt)return e;var v=n.get(l),d=l in o;if(v===void 0&&(!d||ct(o,l)?.writable)&&(v=a(()=>{var p=Ye(d?o[l]:P),g=q(p);return g}),n.set(l,v)),v!==void 0){var c=f(v);return c===P?void 0:c}return Reflect.get(o,l,u)},getOwnPropertyDescriptor(o,l){var u=Reflect.getOwnPropertyDescriptor(o,l);if(u&&"value"in u){var v=n.get(l);v&&(u.value=f(v))}else if(u===void 0){var d=n.get(l),c=d?.v;if(d!==void 0&&c!==P)return{enumerable:!0,configurable:!0,value:c,writable:!0}}return u},has(o,l){if(l===kt)return!0;var u=n.get(l),v=u!==void 0&&u.v!==P||Reflect.has(o,l);if(u!==void 0||k!==null&&(!v||ct(o,l)?.writable)){u===void 0&&(u=a(()=>{var c=v?Ye(o[l]):P,p=q(c);return p}),n.set(l,u));var d=f(u);if(d===P)return!1}return v},set(o,l,u,v){var d=n.get(l),c=l in o;if(r&&l==="length")for(var p=u;pq(P)),n.set(p+"",g))}if(d===void 0)(!c||ct(o,l)?.writable)&&(d=a(()=>q(void 0)),w(d,Ye(u)),n.set(l,d));else{c=d.v!==P;var b=a(()=>Ye(u));w(d,b)}var _=Reflect.getOwnPropertyDescriptor(o,l);if(_?.set&&_.set.call(v,u),!c){if(r&&typeof l=="string"){var E=n.get("length"),N=Number(l);Number.isInteger(N)&&N>=E.v&&w(E,N+1)}dt(s)}return!0},ownKeys(o){f(s);var l=Reflect.ownKeys(o).filter(d=>{var c=n.get(d);return c===void 0||c.v!==P});for(var[u,v]of n)v.v!==P&&!(u in o)&&l.push(u);return l},setPrototypeOf(){oi()}})}var lr,Hr,Ur,jr;function Hi(){if(lr===void 0){lr=window,Hr=/Firefox/.test(navigator.userAgent);var e=Element.prototype,t=Node.prototype,n=Text.prototype;Ur=ct(t,"firstChild").get,jr=ct(t,"nextSibling").get,nr(e)&&(e[an]=void 0,e[xr]=null,e[on]=void 0,e.__e=void 0),nr(n)&&(n[fn]=void 0)}}function Re(e=""){return document.createTextNode(e)}function xn(e){return Ur.call(e)}function mt(e){return jr.call(e)}function x(e,t){return xn(e)}function Ui(e,t=!1){{var n=xn(e);return n instanceof Comment&&n.data===""?mt(n):n}}function C(e,t=1,n=!1){let r=e;for(;t--;)r=mt(r);return r}function ji(e){e.textContent=""}function Br(){return!1}function Bi(e,t,n){return n?document.createElement(e,{is:n}):document.createElement(e)}function zi(e){k===null&&(T===null&&ii(),si()),Ne&&ri()}function Vi(e,t){var n=t.last;n===null?t.last=t.first=e:(n.next=e,e.prev=n,t.last=e)}function Se(e,t){var n=k;n!==null&&(n.f&z)!==0&&(e|=z);var r={ctx:j,deps:null,nodes:null,f:e|D|Q,first:null,fn:t,last:null,next:null,parent:n,b:n&&n.b,prev:null,teardown:null,wv:0,ac:null};S?.register_created_effect(r);var s=r;if((e&Ke)!==0)Ve!==null?Ve.push(r):Ce.ensure().schedule(r);else if(t!==null){try{Xe(r)}catch(a){throw Y(r),a}s.deps===null&&s.teardown===null&&s.nodes===null&&s.first===s.last&&(s.f&Ze)===0&&(s=s.first,(e&ae)!==0&&(e&$e)!==0&&s!==null&&(s.f|=$e))}if(s!==null&&(s.parent=n,n!==null&&Vi(s,n),T!==null&&(T.f&H)!==0&&(e&xe)===0)){var i=T;(i.effects??=[]).push(s)}return r}function Sn(){return T!==null&&!fe}function zr(e){const t=Se(Dt,null);return I(t,$),t.teardown=e,t}function ht(e){zi();var t=k.f,n=!T&&(t&ee)!==0&&j!==null&&!j.i;if(n){var r=j;(r.e??=[]).push(e)}else return Vr(e)}function Vr(e){return Se(Ke|Qs,e)}function Yi(e){Ce.ensure();const t=Se(xe|Ze,e);return(n={})=>new Promise(r=>{n.outro?De(t,()=>{Y(t),r(void 0)}):(Y(t),r(void 0))})}function Wi(e){return Se(Ke,e)}function Ki(e){return Se(We|Ze,e)}function Tn(e,t=0){return Se(Dt|t,e)}function le(e,t=[],n=[],r=[]){Ri(r,t,n,s=>{Se(Dt,()=>{e(...s.map(f))})})}function Pt(e,t=0){var n=Se(ae|t,e);return n}function X(e){return Se(ee|Ze,e)}function Yr(e){var t=e.teardown;if(t!==null){const n=Ne,r=T;ar(!0),te(null);try{t.call(null)}finally{ar(n),te(r)}}}function kn(e,t=!1){var n=e.first;for(e.first=e.last=null;n!==null;){const s=n.ac;s!==null&&et(()=>{s.abort(gt)});var r=n.next;(n.f&xe)!==0?n.parent=null:Y(n,t),n=r}}function Gi(e){for(var t=e.first;t!==null;){var n=t.next;(t.f&ee)===0&&Y(t),t=n}}function Y(e,t=!0){var n=!1;(t||(e.f&Xs)!==0)&&e.nodes!==null&&e.nodes.end!==null&&(Ji(e.nodes.start,e.nodes.end),n=!0),e.f|=ln,kn(e,t&&!n),pt(e,0);var r=e.nodes&&e.nodes.t;if(r!==null)for(const i of r)i.stop();Yr(e),e.f^=ln,e.f|=Z;var s=e.parent;s!==null&&s.first!==null&&Wr(e),e.next=e.prev=e.teardown=e.ctx=e.deps=e.fn=e.nodes=e.ac=e.b=null}function Ji(e,t){for(;e!==null;){var n=e===t?null:mt(e);e.remove(),e=n}}function Wr(e){var t=e.parent,n=e.prev,r=e.next;n!==null&&(n.next=r),r!==null&&(r.prev=n),t!==null&&(t.first===e&&(t.first=r),t.last===e&&(t.last=n))}function De(e,t,n=!0){var r=[];Kr(e,r,!0);var s=()=>{n&&Y(e),t&&t()},i=r.length;if(i>0){var a=()=>--i||s();for(var o of r)o.out(a)}else s()}function Kr(e,t,n){if((e.f&z)===0){e.f^=z;var r=e.nodes&&e.nodes.t;if(r!==null)for(const o of r)(o.is_global||n)&&t.push(o);for(var s=e.first;s!==null;){var i=s.next;if((s.f&xe)===0){var a=(s.f&$e)!==0||(s.f&ee)!==0&&(e.f&ae)!==0;Kr(s,t,a?n:!1)}s=i}}}function Lt(e){Gr(e,!0)}function Gr(e,t){if((e.f&z)!==0){e.f^=z,(e.f&$)===0&&(I(e,D),Ce.ensure().schedule(e));for(var n=e.first;n!==null;){var r=n.next,s=(n.f&$e)!==0||(n.f&ee)!==0;Gr(n,s?t:!1),n=r}var i=e.nodes&&e.nodes.t;if(i!==null)for(const a of i)(a.is_global||t)&&a.in()}}function qn(e,t){if(e.nodes)for(var n=e.nodes.start,r=e.nodes.end;n!==null;){var s=n===r?null:mt(n);t.append(n),n=s}}let Rt=!1,Ne=!1;function ar(e){Ne=e}let T=null,fe=!1;function te(e){T=e}let k=null;function _e(e){k=e}let pe=null;function Jr(e){T!==null&&(pe??=new Set).add(e)}let V=null,G=0,J=null;function Xi(e){J=e}let Xr=1,Ie=0,Pe=Ie;function or(e){Pe=e}function Qr(){return++Xr}function wt(e){var t=e.f;if((t&D)!==0)return!0;if(t&H&&(e.f&=~He),(t&ue)!==0){for(var n=e.deps,r=n.length,s=0;se.wv)return!0}(t&Q)!==0&&oe===null&&I(e,$)}return!1}function Zr(e,t,n=!0){var r=e.reactions;if(r!==null&&!(pe!==null&&pe.has(e)))for(var s=0;s{e.ac.abort(gt)}),e.ac=null);try{e.f|=Mt;var v=e.fn,d=v();e.f|=Qe;var c=e.deps,p=S?.is_fork;if(V!==null){var g;if(p||pt(e,G),c!==null&&G>0)for(c.length=G+V.length,g=0;g{i.ac.abort(gt),i.ac=null,I(i,D)}),Oi(i),pt(i,0)}}function pt(e,t){var n=e.deps;if(n!==null)for(var r=t;rn?.call(this,i))}return e.startsWith("pointer")||e.startsWith("touch")||e==="wheel"?Ee(()=>{t.addEventListener(e,s,r)}):t.addEventListener(e,s,r),s}function ss(e,t,n,r,s){var i={capture:r,passive:s},a=nl(e,t,n,i);(t===document.body||t===window||t===document||t instanceof HTMLMediaElement)&&zr(()=>{t.removeEventListener(e,a,i)})}function me(e,t,n){(t[ft]??={})[e]=n}function rl(e){for(var t=0;t{Zt=!1,Qt=null}));var a=0,o=Qt===e&&e[ft];if(o){var l=s.indexOf(o);if(l!==-1&&(t===document||t===window)){e[ft]=t;return}var u=s.indexOf(t);if(u===-1)return;l<=u&&(a=l)}if(i=s[a]||e.target,i!==t){Ys(e,"currentTarget",{configurable:!0,get(){return i||n}});var v=T,d=k;te(null),_e(null);try{for(var c,p=[];i!==null&&i!==t;){try{var g=i[ft]?.[r];g!=null&&(!i.disabled||e.target===i)&&g.call(i,e)}catch(b){c?p.push(b):c=b}if(e.cancelBubble)break;a++,i=a{throw b});throw c}}finally{e[ft]=t,delete e.currentTarget,te(v),_e(d)}}}const sl=globalThis?.window?.trustedTypes&&globalThis.window.trustedTypes.createPolicy("svelte-trusted-html",{createHTML:e=>e});function il(e){return sl?.createHTML(e)??e}function ll(e){var t=Bi("template");return t.innerHTML=il(e.replaceAll("","")),t.content}function is(e,t){var n=k;n.nodes===null&&(n.nodes={start:e,end:t,a:null,t:null})}function W(e,t){var n=(t&pi)!==0,r,s=!e.startsWith("");return()=>{r===void 0&&(r=ll(s?e:""+e),r=xn(r));var i=n||Hr?document.importNode(r,!0):r.cloneNode(!0);return is(i,i),i}}function al(){var e=document.createDocumentFragment(),t=document.createComment(""),n=Re();return e.append(t,n),is(t,n),e}function B(e,t){e!==null&&e.before(t)}function U(e,t){var n=t==null?"":typeof t=="object"?`${t}`:t;n!==(e[fn]??=e.nodeValue)&&(e[fn]=n,e.nodeValue=`${n}`)}function ol(e,t){return fl(e,t)}const Tt=new Map;function fl(e,{target:t,anchor:n,props:r={},events:s,context:i,intro:a=!0,transformError:o}){Hi();var l=void 0,u=Yi(()=>{var v=n??t.appendChild(Re());qi(v,{pending:()=>{}},p=>{_n({});var g=j;i&&(g.c=i),s&&(r.$$events=s),l=e(p,r)||{},gn()},o);var d=new Set,c=p=>{for(var g=0;g{for(var p of d)for(const _ of[t,document]){var g=Tt.get(_),b=g.get(p);--b==0?(_.removeEventListener(p,dn),g.delete(p),g.size===0&&Tt.delete(_)):g.set(p,b)}vn.delete(c),v!==n&&v.parentNode?.removeChild(v)}});return ul.set(l,u),l}let ul=new WeakMap;class ls{anchor;#t=new Map;#l=new Map;#e=new Map;#o=new Set;#r=!0;constructor(t,n=!0){this.anchor=t,this.#r=n}#i=t=>{if(this.#t.has(t)){var n=this.#t.get(t),r=this.#l.get(n);if(r)Lt(r),this.#o.delete(n);else{var s=this.#e.get(n);s&&(Lt(s.effect),this.#l.set(n,s.effect),this.#e.delete(n),s.fragment.lastChild.remove(),this.anchor.before(s.fragment),r=s.effect)}for(const[i,a]of this.#t){if(this.#t.delete(i),i===t)break;const o=this.#e.get(a);o&&(Y(o.effect),this.#e.delete(a))}for(const[i,a]of this.#l){if(i===n||this.#o.has(i))continue;const o=()=>{if(Array.from(this.#t.values()).includes(i)){var u=document.createDocumentFragment();qn(a,u),u.append(Re()),this.#e.set(i,{effect:a,fragment:u})}else Y(a);this.#o.delete(i),this.#l.delete(i)};this.#r||!r?(this.#o.add(i),De(a,o,!1)):o()}}};#n=t=>{this.#t.delete(t);const n=Array.from(this.#t.values());for(const[r,s]of this.#e)n.includes(r)||(Y(s.effect),this.#e.delete(r))};ensure(t,n){var r=S,s=Br();if(n&&!this.#l.has(t)&&!this.#e.has(t))if(s){var i=document.createDocumentFragment(),a=Re();i.append(a),this.#e.set(t,{effect:X(()=>n(a)),fragment:i})}else this.#l.set(t,X(()=>n(this.anchor)));if(this.#t.set(r,t),s){for(const[o,l]of this.#l)o===t?r.unskip_effect(l):r.skip_effect(l);for(const[o,l]of this.#e)o===t?r.unskip_effect(l.effect):r.skip_effect(l.effect);r.oncommit(this.#i),r.ondiscard(this.#n)}else this.#i(r)}}function we(e,t,n=!1){var r=new ls(e),s=n?$e:0;function i(a,o){r.ensure(a,o)}Pt(()=>{var a=!1;t((o,l=0)=>{a=!0,i(l,o)}),a||i(-1,null)},s)}function cl(e,t,n){for(var r=[],s=t.length,i,a=t.length,o=0;o{if(i){if(i.pending.delete(d),i.done.add(d),i.pending.size===0){var c=e.outrogroups;hn(e,It(i.done)),c.delete(i),c.size===0&&(e.outrogroups=null)}}else a-=1},!1)}if(a===0){var l=r.length===0&&n!==null&&e.pending.size===0;if(l){var u=n,v=u.parentNode;ji(v),v.append(u),e.items.clear()}hn(e,t,!l)}else i={pending:new Set(t),done:new Set},(e.outrogroups??=new Set).add(i)}function hn(e,t,n=!0){var r;if(e.pending.size>0){r=new Set;for(const a of e.pending.values())for(const o of a)r.add(e.items.get(o).e)}for(var s=0;s{var A=n();return mr(A)?A:A==null?[]:It(A)}),c,p=new Map,g=!0;function b(A){(N.effect.f&Z)===0&&(N.pending.delete(A),N.fallback=v,vl(N,c,a,t,r),v!==null&&(c.length===0?(v.f&de)===0?Lt(v):(v.f^=de,ut(v,null,a)):De(v,()=>{v=null})))}function _(A){N.pending.delete(A)}var E=Pt(()=>{c=f(d);for(var A=c.length,L=new Set,K=S,ne=Br(),M=0;Mi(a)):(v=X(()=>i(fr??=Re())),v.f|=de)),A>L.size&&ni(),!g)if(p.set(K,L),ne){for(const[se,ie]of o)L.has(se)||K.skip_effect(ie.e);K.oncommit(b),K.ondiscard(_)}else b(K);f(d)}),N={effect:E,items:o,pending:p,outrogroups:null,fallback:v};g=!1}function at(e){for(;e!==null&&(e.f&ee)===0;)e=e.next;return e}function vl(e,t,n,r,s){var i=(r&di)!==0,a=t.length,o=e.items,l=at(e.effect.first),u,v=null,d,c=[],p=[],g,b,_,E;if(i)for(E=0;E0){var ce=(r&Sr)!==0&&a===0?n:null;if(i){for(E=0;E{if(d!==void 0)for(_ of d)_.nodes?.a?.apply()})}function dl(e,t,n,r,s,i,a,o){var l=(a&ci)!==0?(a&hi)===0?Pi(n,!1,!1):Ue(n):null,u=(a&vi)!==0?Ue(s):null;return{v:l,i:u,e:X(()=>(i(t,l??n,u??s,o),()=>{e.delete(r)}))}}function ut(e,t,n){if(e.nodes)for(var r=e.nodes.start,s=e.nodes.end,i=t&&(t.f&de)===0?t.nodes.start:n;r!==null;){var a=mt(r);if(i.before(r),r===s)return;r=a}}function Te(e,t,n){t===null?e.effect.first=n:t.next=n,n===null?e.effect.last=t:n.prev=t}function hl(e,t,...n){var r=new ls(e);Pt(()=>{const s=t()??null;r.ensure(s,s&&(i=>s(i,...n)))},$e)}const ur=[...` -\r\f \v\uFEFF`];function pl(e,t,n){var r=e==null?"":""+e;if(n){for(var s of Object.keys(n))if(n[s])r=r?r+" "+s:s;else if(r.length)for(var i=s.length,a=0;(a=r.indexOf(s,a))>=0;){var o=a+i;(a===0||ur.includes(r[a-1]))&&(o===r.length||ur.includes(r[o]))?r=(a===0?"":r.substring(0,a))+r.substring(o+1):a=o}}return r===""?null:r}function _l(e,t){return e==null?null:String(e)}function Be(e,t,n,r,s,i){var a=e[an];if(a!==n||a===void 0){var o=pl(n,r,i);o==null?e.removeAttribute("class"):e.className=o,e[an]=n}else if(i&&s!==i)for(var l in i){var u=!!i[l];(s==null||u!==!!s[l])&&e.classList.toggle(l,u)}return i}function pn(e,t,n,r){var s=e[on];if(s!==t){var i=_l(t);i==null?e.removeAttribute("style"):e.style.cssText=i,e[on]=t}return r}const gl=Symbol("is custom element"),ml=Symbol("is html");function ze(e,t,n,r){var s=wl(e);s[t]!==(s[t]=n)&&(t==="loading"&&(e[Zs]=n),n==null?e.removeAttribute(t):typeof n!="string"&&bl(e).includes(t)?e[t]=n:e.setAttribute(t,n))}function wl(e){return e[xr]??={[gl]:e.nodeName.includes("-"),[ml]:e.namespaceURI===_i}}var cr=new Map;function bl(e){var t=e.getAttribute("is")||e.nodeName,n=cr.get(t);if(n)return n;cr.set(t,n=[]);for(var r,s=e,i=Element.prototype;i!==s;){r=Ws(s);for(var a in r)r[a].set&&a!=="innerHTML"&&a!=="textContent"&&a!=="innerText"&&n.push(a);s=wr(s)}return n}function vr(e,t,n=t){var r=new WeakSet;Si(e,"input",async s=>{var i=s?e.defaultValue:e.value;if(i=en(e)?tn(i):i,n(i),S!==null&&r.add(S),await Zi(),i!==(i=t())){var a=e.selectionStart,o=e.selectionEnd,l=e.value.length;if(e.value=i??"",o!==null){var u=e.value.length;a===o&&o===l&&u>l?(e.selectionStart=u,e.selectionEnd=u):(e.selectionStart=a,e.selectionEnd=Math.min(o,u))}}}),$t(t)==null&&e.value&&(n(en(e)?tn(e.value):e.value),S!==null&&r.add(S)),Tn(()=>{var s=t();if(e===document.activeElement){var i=S;if(r.has(i))return}en(e)&&s===tn(e.value)||e.type==="date"&&!s&&!e.value||s!==e.value&&(e.value=s??"")})}function en(e){var t=e.type;return t==="number"||t==="range"}function tn(e){return e===""?null:+e}function nn(e,t){return e===t||e?.[kt]===t}function yl(e={},t,n,r){var s=j.r,i=k;return Wi(()=>{var a,o;return Tn(()=>{a=o,o=[],$t(()=>{nn(n(...o),e)||(t(e,...o),a&&nn(n(...a),e)&&t(null,...a))})}),()=>{let l=i;for(;l!==s&&l.parent!==null&&l.parent.f&ln;)l=l.parent;const u=()=>{o&&nn(n(...o),e)&&t(null,...o)},v=l.teardown;l.teardown=()=>{u(),v?.()}}}),e}function rn(e,t,n,r){var s=r,i=!0,a=()=>(i&&(i=!1,s=r),s),o;o=e[t],o===void 0&&r!==void 0&&(o=a());var l;return l=()=>{var u=e[t];return u===void 0?a():(i=!0,u)},l}function El(e){j===null&&ei(),ht(()=>{const t=$t(e);if(typeof t=="function")return t})}const xl="5";typeof window<"u"&&((window.__svelte??={}).v??=new Set).add(xl);const os="".replace(/\/+$/,""),Ht=e=>`${os}${e}`;class _t extends Error{status;retryAfter;constructor(t,n,r){super(n),this.status=t,this.retryAfter=r}}function Sl(e){const t=e.headers.get("x-ratelimit-limit"),n=e.headers.get("x-ratelimit-remaining");if(t===null||n===null)return null;const r=Number(t),s=Number(n);return!Number.isFinite(r)||!Number.isFinite(s)?null:{limit:r,remaining:s}}function fs(e){const t=Number(e.headers.get("retry-after"));return Number.isFinite(t)&&t>0?t:null}function An(e){const t=e.sub,n=t.toLowerCase();return{sub:t,lc:n===t?t:n,firstSeen:e.first_seen}}async function Tl(e,t,n,r){let s;try{s=await fetch(Ht(`/v1/search?apex=${encodeURIComponent(e)}&format=ndjson&dates=1`),{signal:r})}catch(p){throw p instanceof DOMException&&p.name==="AbortError"?p:new _t(0,"cannot reach the subidx server",null)}if(!s.ok){const p=(await s.text()).trim();throw new _t(s.status,p||s.statusText,fs(s))}const i=Number(s.headers.get("x-total-count")),a=Number(s.headers.get("x-max-seq"));t({total:Number.isFinite(i)&&i>=0?i:null,rate:Sl(s),maxSeq:Number.isFinite(a)&&a>=0?a:null});const o=s.body.getReader(),l=new TextDecoder;let u="",v=0,d=[];const c=()=>{d.length>0&&(n(d),d=[])};for(;;){const{done:p,value:g}=await o.read();if(p)break;u+=l.decode(g,{stream:!0});let b;for(;(b=u.indexOf(` -`,v))>=0;)b>v&&(d.push(An(JSON.parse(u.slice(v,b)))),d.length>=5e3&&c()),v=b+1;v>0&&(u=u.slice(v),v=0)}c()}async function dr(e){try{const t=await fetch(Ht("/v1/stats"),{signal:e});if(!t.ok)return null;const n=await t.json();return!Number.isFinite(n.total)||!Array.isArray(n.top)?null:n}catch{return null}}async function kl(e,t,n){let r;try{r=await fetch(Ht(`/v1/watch?apex=${encodeURIComponent(e)}&after=${t}&format=ndjson&dates=1`),{signal:n})}catch(l){throw l instanceof DOMException&&l.name==="AbortError"?l:new _t(0,"cannot reach the subidx server",null)}if(!r.ok){const l=(await r.text()).trim();throw new _t(r.status,l||r.statusText,fs(r))}const s=Number(r.headers.get("x-max-seq")),i=r.headers.get("x-truncated")==="1",a=await r.text(),o=[];for(const l of a.split(` -`))l&&o.push(An(JSON.parse(l)));return{rows:o,maxSeq:Number.isFinite(s)?s:t,truncated:i}}function ql(e,t){const n=new EventSource(Ht(`/v1/feed?apex=${encodeURIComponent(e)}`));return n.onopen=()=>t.onStateChange(!0),n.onerror=()=>t.onStateChange(!1),n.onmessage=r=>{try{t.onRow(An(JSON.parse(r.data)))}catch{}},n.addEventListener("resync",()=>t.onResync()),()=>n.close()}function be(e){return e.toLocaleString("en-US")}function Al(e){return e>=1e7?Math.round(e/1e6)+"M":e>=1e6?(e/1e6).toFixed(1)+"M":e>=1e4?Math.round(e/1e3)+"k":e>=1e3?(e/1e3).toFixed(1)+"k":String(e)}function Rl(e){return e?e.slice(0,10):""}function hr(e){return/[",\n\r]/.test(e)?'"'+e.replaceAll('"','""')+'"':e}function pr(e,t){return e.map(r=>t?`${r.sub} ${r.firstSeen??""}`:r.sub).join(` -`)+` -`}function Cl(e,t){const n=t?`subdomain,first_seen -`:`subdomain -`,r=e.map(s=>t?`${hr(s.sub)},${s.firstSeen??""}`:hr(s.sub)).join(` -`);return n+r+` -`}function _r(e,t,n){const r=new Blob([n],{type:t}),s=URL.createObjectURL(r),i=document.createElement("a");i.href=s,i.download=e,i.click(),URL.revokeObjectURL(s)}async function Nl(e){try{return await navigator.clipboard.writeText(e),!0}catch{return!1}}var Ml=W('
');function Fl(e,t){_n(t,!0);let n=rn(t,"rowHeight",3,30),r=rn(t,"overscan",3,10),s=rn(t,"epoch",3,0),i=q(void 0),a=q(0),o=q(480);const l=ke(()=>Math.max(0,Math.floor(f(a)/n())-r())),u=ke(()=>Math.min(t.items.length,Math.ceil((f(a)+f(o))/n())+r())),v=ke(()=>t.items.slice(f(l),f(u)));function d(){f(i)&&w(a,f(i).scrollTop,!0)}function c(){f(i)&&w(o,Math.min(f(i).clientHeight,2e4),!0)}ht(()=>{if(c(),!f(i))return;const _=new ResizeObserver(c);return _.observe(f(i)),()=>_.disconnect()}),ht(()=>{s(),f(i)&&(f(i).scrollTop=0,w(a,0))});var p=Ml(),g=x(p),b=x(g);as(b,23,()=>f(v),(_,E)=>f(l)+E,(_,E,N)=>{var A=al(),L=Ui(A);hl(L,()=>t.row,()=>f(E),()=>f(l)+f(N)),B(_,A)}),yl(p,_=>w(i,_),()=>f(i)),le(()=>{pn(g,`height: ${t.items.length*n()}px`),pn(b,`transform: translateY(${f(l)*n()}px)`)}),ss("scroll",p,d),B(e,p),gn()}var Ol=W('
names indexed
'),Ll=W(''),Il=W(''),Dl=W(''),Pl=W(''),$l=W('first seen'),Hl=W(" "),Ul=W('
'),jl=W('

List every hostname this collector has seen issued a certificate for. Type an apex — the registered domain, not a subdomain.

The same data is served over plain HTTP:

'),Bl=W('

No names collected for yet.

Certificate Transparency only records names that were issued a certificate. Names living only in DNS are invisible here.

'),zl=W('
search budget
'),Vl=W('self-hosted · no keys, no quotas'),Yl=W('
subidx passive subdomain index · certificate transparency
');function Wl(e,t){_n(t,!0);const n=30;let r=q(""),s=q(""),i=[],a=q(0),o=q(!1),l=q(!1),u=q(!1),v=q(null),d=q(null),c=q(null),p=q(!1),g=q(0),b=q(!1),_=q("off"),E=q(0),N=q(Ye({})),A=0,L=[],K=new Set,ne=null,M=null,re=!1,ce=!1,F=q(null),se=q(null),ie=q(!0),tt=q(""),Me=q("seen"),Fe=q(!1),nt=null;const us=new Intl.Collator("en",{numeric:!0});function cs(h){i=h,lt(a)}const Rn=ke(()=>{if(f(a),f(u))return i;if(f(Me)==="seen"){if(f(Fe))return i;const m=[...i];return m.reverse(),m}const h=[...i].sort((m,y)=>us.compare(m.sub,y.sub));return f(Fe)||h.reverse(),h}),ge=ke(()=>{const h=f(tt).trim().toLowerCase();return h?f(Rn).filter(m=>m.lc.includes(h)):f(Rn)}),Ut=ke(()=>(f(a),i.length)),Cn=ke(()=>{const h=f(r).trim()||"letsencrypt.org";return`curl "${os||location.origin}/v1/search?apex=${h}"`});function vs(h){let m=h.trim();return m=m.replace(/^[a-z][a-z0-9+.-]*:\/\//i,""),m=m.split("/")[0].split("?")[0],m.toLowerCase()}async function bt(h){const m=vs(h);if(w(r,m,!0),!m||!m.includes(".")){w(d,"enter a registered domain, e.g. example.com");return}if(f(c)!==null&&f(c)>0)return;nt?.abort(),nt=new AbortController;const y=nt.signal;w(l,!0),w(u,!0),w(v,null),w(d,null),cs([]),w(tt,""),lt(g),w(s,m,!0),w(o,!0),yt(!1),M!==null&&(clearTimeout(M),M=null),L=[],w(E,0),w(N,{},!0),A=0;const O=new URL(location.href);O.searchParams.set("apex",m),history.replaceState(null,"",O);try{await Tl(m,R=>{w(se,R.rate??f(se),!0),w(v,R.total,!0),R.maxSeq!==null&&(A=R.maxSeq)},R=>{y.aborted||(i=i.concat(R),lt(a))},y),hs(),f(b)&&!y.aborted&&Nn()}catch(R){if(R instanceof DOMException&&R.name==="AbortError")return;R instanceof _t?R.status===429&&R.retryAfter!==null?(w(c,R.retryAfter,!0),w(d,`daily search budget spent. resets in ${ds(R.retryAfter)}`)):w(d,R.status===0?R.message:`server said: ${R.message}`,!0):w(d,"search failed"),i.length>0&&w(d,`${f(d)} — showing ${be(i.length)} partial names`)}finally{nt?.signal===y&&(w(l,!1),w(u,!1),nt=null)}}function ds(h){const m=Math.floor(h/60),y=h%60;return m>0?`${m}m ${String(y).padStart(2,"0")}s`:`${y}s`}function hs(){K=new Set(i.map(h=>h.lc))}function ps(){!f(o)||f(l)||!f(s)||(w(b,!f(b)),f(b)?Nn():yt(!0))}function Nn(){yt(!1),re=!1,w(_,"connecting"),ne=ql(f(s),{onRow:Mn,onResync:()=>{jt()},onStateChange:h=>{h?(re&&jt(),w(_,"live")):(re=!0,w(_,"connecting"))}}),setTimeout(()=>{f(b)&&ne!==null&&jt()},3e3)}function yt(h){ne?.(),ne=null,h&&w(_,"off")}function Mn(h){K.has(h.lc)||(K.add(h.lc),L.push(h),w(E,L.length,!0),_s())}function _s(){M===null&&(M=setTimeout(On,2e3))}function Fn(){On()}function On(){if(M!==null&&(clearTimeout(M),M=null),L.length===0||f(u))return;const h=L;L=[],w(E,0),i=i.concat(h),lt(a);const m={...f(N)};for(const y of h)m[y.sub]=!0;w(N,m,!0),setTimeout(()=>gs(h.map(y=>y.sub)),8e3)}function gs(h){let m=!1;const y={...f(N)};for(const O of h)O in y&&(delete y[O],m=!0);m&&w(N,y,!0)}async function jt(){if(!(ce||!f(s)||A<=0)){ce=!0;try{const h=await kl(f(s),A);if(A=h.maxSeq,h.truncated){bt(f(s));return}for(const m of h.rows)Mn(m);Fn()}catch{}finally{ce=!1}}}ht(()=>{if(!f(b))return;const h=setInterval(()=>{document.visibilityState==="visible"&&dr().then(m=>{m&&w(F,m,!0)})},3e5);return()=>clearInterval(h)}),ht(()=>{if(f(c)===null)return;const h=setInterval(()=>{f(c)!==null&&(lt(c,-1),f(c)<=0&&(w(c,null),f(d)?.startsWith("daily search budget")&&w(d,null)))},1e3);return()=>clearInterval(h)});async function ms(){await Nl(pr(f(ge),f(ie)))&&(w(p,!0),setTimeout(()=>w(p,!1),1500))}function Ln(h){f(Me)===h?w(Fe,!f(Fe)):(w(Me,h,!0),w(Fe,h==="name"))}function ws(h){w(r,h,!0),bt(h)}El(()=>{dr().then(m=>w(F,m,!0));const h=new URLSearchParams(location.search).get("apex");return h&&bt(h),()=>{yt(!1),M!==null&&clearTimeout(M)}});const Bt=ke(()=>f(se)&&f(se).limit>0?f(se).remaining/f(se).limit*100:null);var In=Yl(),Dn=x(In),bs=C(x(Dn),2);{var ys=h=>{var m=Ol(),y=x(m),O=x(y);le(R=>U(O,R),[()=>be(f(F).total)]),B(h,m)};we(bs,h=>{f(F)&&h(ys)})}var zt=C(Dn,2),Pn=C(x(zt),2),$n=C(Pn,2),Es=x($n),Hn=C(zt,2);{var xs=h=>{var m=Il();as(m,21,()=>f(F).top.slice(0,8),y=>y.apex,(y,O)=>{var R=Ll(),ve=x(R),Oe=C(ve),st=x(Oe);le(it=>{U(ve,f(O).apex),U(st,it)},[()=>Al(f(O).count)]),me("click",R,()=>ws(f(O).apex)),B(y,R)}),B(h,m)};we(Hn,h=>{f(F)&&f(F).top.length>0&&h(xs)})}var Un=C(Hn,2);{var Ss=h=>{var m=Dl(),y=x(m);le(()=>U(y,f(d))),B(h,m)};we(Un,h=>{f(d)&&h(Ss)})}var Vt=C(Un,2);let jn;var Bn=x(Vt),zn=x(Bn),Ts=x(zn),Vn=C(zn,2);{var ks=h=>{var m=Pl(),y=x(m);le(O=>U(y,`+${O??""} new`),[()=>be(f(E))]),me("click",m,Fn),B(h,m)};we(Vn,h=>{f(E)>0&&h(ks)})}var qs=C(Vn,2),rt=x(qs),As=x(rt);let Yn;var Wn=C(rt,2),Kn=C(Wn,2),Et=x(Kn),Rs=x(Et),Yt=C(Et,2),Cs=x(Yt),Wt=C(Kn,2),Ns=C(Wt,2),xt=x(Ns),Ms=x(xt),Kt=C(xt,2),Gn=C(Kt,2),Jn=C(Bn,2);let Xn;var Qn=x(Jn),Fs=C(x(Qn),2);{var Os=h=>{var m=$l();B(h,m)};we(Fs,h=>{f(ie)&&h(Os)})}var Ls=C(Qn,2),Is=x(Ls);Fl(Is,{get items(){return f(ge)},rowHeight:n,get epoch(){return f(g)},row:(m,y=br)=>{var O=Ul();let R;var ve=x(O),Oe=x(ve),st=C(ve,2);{var it=Gt=>{var St=Hl();let tr;var Bs=x(St);le(zs=>{tr=Be(St,1,"seen svelte-1n46o8q",null,tr,{unknown:!y().firstSeen}),ze(St,"title",y().firstSeen??"date unknown"),U(Bs,zs)},[()=>y().firstSeen?Rl(y().firstSeen):"—"]),B(Gt,St)};we(st,Gt=>{f(ie)&&Gt(it)})}le(()=>{R=Be(O,1,"lrow svelte-1n46o8q",null,R,{fresh:f(N)[y().sub]===!0}),ze(ve,"title",y().sub),U(Oe,y().sub)}),B(m,O)},$$slots:{row:!0}});var Zn=C(Vt,2);{var Ds=h=>{var m=jl(),y=C(x(m),4),O=x(y);le(()=>U(O,f(Cn))),B(h,m)};we(Zn,h=>{!f(o)&&!f(d)&&h(Ds)})}var er=C(Zn,2);{var Ps=h=>{var m=Bl(),y=x(m),O=C(x(y)),R=x(O),ve=C(y,4),Oe=x(ve);le(()=>{U(R,f(s)),U(Oe,f(Cn))}),B(h,m)};we(er,h=>{f(o)&&!f(l)&&f(ge).length===0&&!f(d)&&h(Ps)})}var $s=C(er,2),Hs=x($s);{var Us=h=>{var m=zl();let y;var O=C(x(m),2),R=x(O),ve=C(O,2),Oe=x(ve);le((st,it)=>{y=Be(m,1,"budget svelte-1n46o8q",null,y,{low:f(Bt)!==null&&f(Bt)<10}),pn(R,`width: ${f(Bt)??0??""}%`),U(Oe,`${st??""}/${it??""}`)},[()=>be(Math.max(0,f(se).remaining)),()=>be(f(se).limit)]),B(h,m)},js=h=>{var m=Vl();B(h,m)};we(Hs,h=>{f(se)?h(Us):h(js,-1)})}le(h=>{$n.disabled=f(l)||f(c)!==null&&f(c)>0,U(Es,f(l)?"searching":"search"),jn=Be(Vt,1,"results svelte-1n46o8q",null,jn,{hidden:!f(o)}),U(Ts,h),ze(rt,"aria-pressed",f(b)),rt.disabled=!f(o)||f(l),Yn=Be(As,1,"livedot svelte-1n46o8q",null,Yn,{on:f(_)==="live",wait:f(_)==="connecting"}),ze(Et,"aria-pressed",f(Me)==="seen"),U(Rs,`first seen${f(Me)==="seen"?f(Fe)?" ↑":" ↓":""}`),ze(Yt,"aria-pressed",f(Me)==="name"),U(Cs,`name${f(Me)==="name"?f(Fe)?" ↑":" ↓":""}`),ze(Wt,"aria-pressed",f(ie)),xt.disabled=f(l)||f(ge).length===0,U(Ms,f(p)?"copied":"copy"),Kt.disabled=f(l)||f(ge).length===0,Gn.disabled=f(l)||f(ge).length===0,Xn=Be(Jn,1,"sheet svelte-1n46o8q",null,Xn,{nodates:!f(ie)})},[()=>f(l)?f(Ut)>0?`${be(f(Ut))}${f(v)!==null?` of ${be(f(v))}`:""} names…`:"searching…":`${be(f(ge).length)}${f(tt).trim()?` of ${be(f(Ut))}`:""} names`]),ss("submit",zt,h=>{h.preventDefault(),bt(f(r))}),vr(Pn,()=>f(r),h=>w(r,h)),me("click",rt,ps),vr(Wn,()=>f(tt),h=>w(tt,h)),me("click",Et,()=>Ln("seen")),me("click",Yt,()=>Ln("name")),me("click",Wt,()=>w(ie,!f(ie))),me("click",xt,ms),me("click",Kt,()=>_r(`${f(s)||"subidx"}.txt`,"text/plain",pr(f(ge),f(ie)))),me("click",Gn,()=>_r(`${f(s)||"subidx"}.csv`,"text/csv",Cl(f(ge),f(ie)))),B(e,In),gn()}rl(["click"]);ol(Wl,{target:document.getElementById("app")}); diff --git a/internal/web/dist/assets/index-LEhbGqLZ.css b/internal/web/dist/assets/index-LEhbGqLZ.css deleted file mode 100644 index c8aca51..0000000 --- a/internal/web/dist/assets/index-LEhbGqLZ.css +++ /dev/null @@ -1 +0,0 @@ -:root{--bg: #101216;--panel: #14171c;--panel-2: #171b21;--line: #242a33;--line-soft: #1c2128;--text: #d7dbe3;--muted: #8a919d;--faint: #5b6472;--accent: #e2a54a;--accent-dim: #9c7534;--err: #cf7060;--ok: #83ae85;--mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;--sans: system-ui, -apple-system, "Segoe UI", sans-serif}*{box-sizing:border-box}html,body{margin:0;padding:0}body{background:var(--bg);color:var(--text);font-family:var(--mono);font-size:13px;line-height:1.45;-webkit-font-smoothing:antialiased}button,input,select{font:inherit;color:inherit}:focus-visible{outline:2px solid var(--accent);outline-offset:1px}::selection{background:#e2a54a47}@media(prefers-reduced-motion:reduce){*,*:before,*:after{transition-duration:.01ms!important;animation-duration:.01ms!important}}.viewport.svelte-16nckvm{height:100%;overflow-y:auto;overscroll-behavior:contain}.spacer.svelte-16nckvm{position:relative}.offset.svelte-16nckvm{position:absolute;top:0;left:0;right:0}.page.svelte-1n46o8q{max-width:980px;margin:0 auto;padding:20px 24px 12px;height:100dvh;display:flex;flex-direction:column}.hidden.svelte-1n46o8q{display:none}.masthead.svelte-1n46o8q{display:flex;align-items:baseline;justify-content:space-between;gap:16px;padding-bottom:14px;border-bottom:1px solid var(--line)}.brand.svelte-1n46o8q{display:flex;align-items:baseline;gap:12px;min-width:0}.wordmark.svelte-1n46o8q{font-size:15px;font-weight:700;letter-spacing:.06em}.tagline.svelte-1n46o8q{font-family:var(--sans);font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--faint);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.total.svelte-1n46o8q{display:flex;align-items:baseline;gap:8px;white-space:nowrap}.total-n.svelte-1n46o8q{font-size:15px;font-weight:700;font-variant-numeric:tabular-nums}.total-label.svelte-1n46o8q{font-family:var(--sans);font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--faint)}.searchrow.svelte-1n46o8q{display:flex;align-items:center;gap:10px;margin-top:18px}.prompt.svelte-1n46o8q{color:var(--accent);font-weight:700;font-size:18px;-webkit-user-select:none;user-select:none}.q.svelte-1n46o8q{flex:1;background:var(--panel);border:1px solid var(--line);border-radius:3px;padding:9px 12px;caret-color:var(--accent)}.q.svelte-1n46o8q::placeholder{color:var(--faint)}.q.svelte-1n46o8q:focus-visible,.filter.svelte-1n46o8q:focus-visible{outline-offset:0;border-color:var(--accent-dim)}.go.svelte-1n46o8q{background:transparent;border:1px solid var(--accent-dim);color:var(--accent);border-radius:3px;padding:9px 16px;cursor:pointer;letter-spacing:.04em}.go.svelte-1n46o8q:hover:not(:disabled){background:#e2a54a17}.go.svelte-1n46o8q:disabled{opacity:.45;cursor:default}.chips.svelte-1n46o8q{display:flex;flex-wrap:wrap;gap:6px;margin-top:10px}.chip.svelte-1n46o8q{background:transparent;border:none;color:var(--muted);padding:3px 8px;border-radius:3px;cursor:pointer;font-size:12px}.chip.svelte-1n46o8q:hover{color:var(--accent);background:#e2a54a12}.chip-n.svelte-1n46o8q{margin-left:7px;color:var(--faint);font-variant-numeric:tabular-nums}.chip.svelte-1n46o8q:hover .chip-n:where(.svelte-1n46o8q){color:var(--accent-dim)}.error.svelte-1n46o8q{margin:14px 0 0;color:var(--err)}.results.svelte-1n46o8q{margin-top:16px;flex:1;min-height:0;display:flex;flex-direction:column}.toolbar.svelte-1n46o8q{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;padding-bottom:8px}.count.svelte-1n46o8q{color:var(--muted);font-variant-numeric:tabular-nums}.tools.svelte-1n46o8q{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.filter.svelte-1n46o8q{width:130px;background:var(--panel);border:1px solid var(--line);border-radius:3px;padding:4px 8px;font-size:12px}.filter.svelte-1n46o8q::placeholder{color:var(--faint)}.seg.svelte-1n46o8q,.exports.svelte-1n46o8q{display:inline-flex}.seg.svelte-1n46o8q button:where(.svelte-1n46o8q),.exports.svelte-1n46o8q button:where(.svelte-1n46o8q),.toggle.svelte-1n46o8q{background:transparent;border:1px solid var(--line);border-right-width:0;color:var(--muted);padding:4px 9px;font-family:var(--sans);font-size:10px;letter-spacing:.07em;text-transform:uppercase;cursor:pointer;white-space:nowrap}.exports.svelte-1n46o8q button:where(.svelte-1n46o8q):last-child,.seg.svelte-1n46o8q button:where(.svelte-1n46o8q):last-child{border-right-width:1px;border-radius:0 3px 3px 0}.seg.svelte-1n46o8q button:where(.svelte-1n46o8q):first-child,.exports.svelte-1n46o8q button:where(.svelte-1n46o8q):first-child{border-radius:3px 0 0 3px}.toggle.svelte-1n46o8q{border-radius:3px;border-right-width:1px}.livebtn.svelte-1n46o8q{display:inline-flex;align-items:center;gap:7px}.livedot.svelte-1n46o8q{width:7px;height:7px;border-radius:50%;background:var(--faint)}.livedot.on.svelte-1n46o8q{background:var(--ok)}.livedot.wait.svelte-1n46o8q{background:var(--accent-dim);animation:svelte-1n46o8q-pulse 1.2s ease-in-out infinite}@keyframes svelte-1n46o8q-pulse{50%{opacity:.35}}.newpill.svelte-1n46o8q{background:#e2a54a1a;color:var(--accent);border:1px solid var(--accent-dim);border-radius:10px;padding:2px 10px;font-size:11px;cursor:pointer;white-space:nowrap;font-variant-numeric:tabular-nums}.newpill.svelte-1n46o8q:hover{background:#e2a54a33}.seg.svelte-1n46o8q button[aria-pressed=true]:where(.svelte-1n46o8q),.toggle[aria-pressed=true].svelte-1n46o8q{color:var(--accent);border-color:var(--accent-dim)}.seg.svelte-1n46o8q button:where(.svelte-1n46o8q):hover:not([aria-pressed=true]),.exports.svelte-1n46o8q button:where(.svelte-1n46o8q):hover:not(:disabled),.toggle.svelte-1n46o8q:hover:not([aria-pressed=true]){color:var(--text)}.exports.svelte-1n46o8q button:where(.svelte-1n46o8q):disabled{opacity:.4;cursor:default}.sheet.svelte-1n46o8q{border:1px solid var(--line);border-radius:3px;background:var(--panel);flex:1;min-height:0;display:flex;flex-direction:column}.thead.svelte-1n46o8q{display:grid;grid-template-columns:minmax(0,1fr) 140px;gap:16px;padding:7px 14px;border-bottom:1px solid var(--line);font-family:var(--sans);font-size:10px;letter-spacing:.09em;text-transform:uppercase;color:var(--faint);-webkit-user-select:none;user-select:none}.nodates.svelte-1n46o8q .thead:where(.svelte-1n46o8q){grid-template-columns:minmax(0,1fr)}.th-date.svelte-1n46o8q{text-align:right}.rows.svelte-1n46o8q{flex:1;min-height:0}.lrow.svelte-1n46o8q{height:30px;display:grid;grid-template-columns:minmax(0,1fr) 140px;gap:16px;align-items:center;padding:0 14px;border-bottom:1px solid var(--line-soft)}.nodates.svelte-1n46o8q .lrow:where(.svelte-1n46o8q){grid-template-columns:minmax(0,1fr)}.sub.svelte-1n46o8q{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:color 1.4s ease}.lrow.fresh.svelte-1n46o8q .sub:where(.svelte-1n46o8q){color:var(--accent)}.seen.svelte-1n46o8q{text-align:right;color:var(--muted);font-variant-numeric:tabular-nums}.unknown.svelte-1n46o8q{color:var(--faint)}.rows.svelte-1n46o8q .viewport{height:100%}.hint.svelte-1n46o8q{margin-top:22px;border-left:2px solid var(--line);padding:2px 0 2px 16px;color:var(--muted);max-width:640px}.hint.svelte-1n46o8q p:where(.svelte-1n46o8q){margin:6px 0}.hint.svelte-1n46o8q strong:where(.svelte-1n46o8q){color:var(--text)}.hint.svelte-1n46o8q code:where(.svelte-1n46o8q){display:inline-block;margin-top:8px;padding:7px 11px;background:var(--panel);border:1px solid var(--line-soft);border-radius:3px;color:var(--accent-dim);font-size:12px;overflow-x:auto;max-width:100%}.foot.svelte-1n46o8q{display:flex;align-items:center;justify-content:space-between;gap:16px;padding-top:14px;margin-top:auto;border-top:1px solid var(--line);color:var(--faint)}.budget.svelte-1n46o8q{display:flex;align-items:center;gap:8px}.budget-label.svelte-1n46o8q,.api-link.svelte-1n46o8q{font-family:var(--sans);font-size:10px;letter-spacing:.08em;text-transform:uppercase}.budget-bar.svelte-1n46o8q{width:72px;height:3px;background:var(--line);border-radius:2px;overflow:hidden}.budget-fill.svelte-1n46o8q{display:block;height:100%;background:var(--ok)}.budget.low.svelte-1n46o8q .budget-fill:where(.svelte-1n46o8q){background:var(--err)}.budget.low.svelte-1n46o8q .budget-n:where(.svelte-1n46o8q){color:var(--err)}.budget-n.svelte-1n46o8q{color:var(--muted);font-variant-numeric:tabular-nums;font-size:11px}.api-link.svelte-1n46o8q{color:var(--faint);text-decoration:none}.api-link.svelte-1n46o8q:hover{color:var(--accent)}@media(max-width:640px){.page.svelte-1n46o8q{padding:14px 14px 10px;height:auto;min-height:100dvh}.tagline.svelte-1n46o8q{display:none}.thead.svelte-1n46o8q{grid-template-columns:minmax(0,1fr) 96px}.lrow.svelte-1n46o8q{grid-template-columns:minmax(0,1fr) 96px;gap:10px}} diff --git a/internal/web/dist/index.html b/internal/web/dist/index.html deleted file mode 100644 index 0e8dd28..0000000 --- a/internal/web/dist/index.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - subidx - - - - - -
- - diff --git a/internal/web/public/favicon.svg b/internal/web/public/favicon.svg deleted file mode 100644 index 0c42e4d..0000000 --- a/internal/web/public/favicon.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/internal/web/web.go b/internal/web/web.go deleted file mode 100644 index 23cef00..0000000 --- a/internal/web/web.go +++ /dev/null @@ -1,65 +0,0 @@ -// Package web serves the dashboard UI embedded from ./dist (the Vite -// build output). Assets are immutable-cached by content hash; the HTML -// shell is never cached and ships a strict CSP. -package web - -import ( - "embed" - "io/fs" - "net/http" - "path" - "strings" -) - -//go:embed all:dist -var dist embed.FS - -const csp = "default-src 'none'; script-src 'self'; style-src 'self'; " + - "img-src 'self' data:; connect-src 'self'; font-src 'self'; " + - "base-uri 'none'; form-action 'self'; frame-ancestors 'none'" - -// Handler returns a handler serving the built UI. Paths that look like -// API routes but matched nothing fall through to a plain 404; anything -// else unknown serves the HTML shell so the app owns its own states. -func Handler() http.Handler { - files, err := fs.Sub(dist, "dist") - if err != nil { - panic("web: embedded dist missing: " + err.Error()) - } - fileServer := http.FileServerFS(files) - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet && r.Method != http.MethodHead { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusMethodNotAllowed) - w.Write([]byte("Method Not Allowed\n")) - return - } - - p := path.Clean(r.URL.Path) - if strings.HasPrefix(p, "/v1/") || p == "/v1" || p == "/healthz" || p == "/readyz" { - http.NotFound(w, r) - return - } - - name := strings.TrimPrefix(p, "/") - isShell := name == "" || name == "index.html" - if !isShell { - if _, err := fs.Stat(files, name); err != nil { - name = "index.html" - isShell = true - } - } - - h := w.Header() - h.Set("X-Content-Type-Options", "nosniff") - h.Set("Referrer-Policy", "no-referrer") - if isShell { - h.Set("Content-Security-Policy", csp) - h.Set("Cache-Control", "no-cache") - r.URL.Path = "/" - } else { - h.Set("Cache-Control", "public, max-age=31536000, immutable") - } - fileServer.ServeHTTP(w, r) - }) -} diff --git a/internal/web/web_test.go b/internal/web/web_test.go deleted file mode 100644 index 60620ea..0000000 --- a/internal/web/web_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package web - -import ( - "io/fs" - "net/http" - "net/http/httptest" - "strings" - "testing" -) - -func get(t *testing.T, h http.Handler, target string) *httptest.ResponseRecorder { - t.Helper() - req := httptest.NewRequest(http.MethodGet, target, nil) - req.Host = "localhost" - rec := httptest.NewRecorder() - h.ServeHTTP(rec, req) - return rec -} - -func TestShellHeaders(t *testing.T) { - rec := get(t, Handler(), "/") - if rec.Code != http.StatusOK { - t.Fatalf("code = %d", rec.Code) - } - if ct := rec.Header().Get("Content-Type"); !strings.Contains(ct, "text/html") { - t.Errorf("content-type = %q", ct) - } - csp := rec.Header().Get("Content-Security-Policy") - if !strings.Contains(csp, "default-src 'none'") || !strings.Contains(csp, "script-src 'self'") { - t.Errorf("csp = %q", csp) - } - if rec.Header().Get("X-Content-Type-Options") != "nosniff" { - t.Error("missing nosniff") - } - if cc := rec.Header().Get("Cache-Control"); cc != "no-cache" { - t.Errorf("cache-control = %q", cc) - } - if !strings.Contains(rec.Body.String(), `id="app"`) { - t.Error("body is not the app shell") - } -} - -func TestAssetsImmutable(t *testing.T) { - files, err := fs.Sub(dist, "dist") - if err != nil { - t.Fatal(err) - } - asset := "" - err = fs.WalkDir(files, ".", func(p string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if asset == "" && !d.IsDir() && strings.HasPrefix(d.Name(), "index-") && - (strings.HasSuffix(p, ".js") || strings.HasSuffix(p, ".css")) { - asset = p - } - return nil - }) - if err != nil { - t.Fatal(err) - } - if asset == "" { - t.Fatal("no built assets found under dist/assets") - } - - h := Handler() - rec := get(t, h, "/"+asset) - if rec.Code != http.StatusOK { - t.Fatalf("code = %d", rec.Code) - } - if cc := rec.Header().Get("Cache-Control"); !strings.Contains(cc, "immutable") { - t.Errorf("cache-control = %q", cc) - } - if strings.HasSuffix(asset, ".css") { - if ct := rec.Header().Get("Content-Type"); !strings.Contains(ct, "text/css") { - t.Errorf("css content-type = %q", ct) - } - } else { - // Go resolves .js via the OS mime database, which differs between - // platforms; both spellings are fine for browsers. - if ct := rec.Header().Get("Content-Type"); !strings.Contains(ct, "javascript") { - t.Errorf("js content-type = %q", ct) - } - } - if csp := rec.Header().Get("Content-Security-Policy"); csp != "" { - t.Errorf("csp on hashed asset: %q", csp) - } -} - -func TestFallbackAndReservedPaths(t *testing.T) { - h := Handler() - - fb := get(t, h, "/some/deep/link") - if fb.Code != http.StatusOK || !strings.Contains(fb.Body.String(), `id="app"`) { - t.Errorf("fallback code = %d", fb.Code) - } - - api := get(t, h, "/v1/nope") - if api.Code != http.StatusNotFound { - t.Errorf("/v1/nope code = %d, want 404", api.Code) - } - hz := get(t, h, "/healthz") - if hz.Code != http.StatusNotFound { - t.Errorf("/healthz code = %d, want 404 (server mux owns it)", hz.Code) - } - - post := httptest.NewRequest(http.MethodPost, "/", nil) - rec := httptest.NewRecorder() - h.ServeHTTP(rec, post) - if rec.Code != http.StatusMethodNotAllowed { - t.Errorf("post / code = %d", rec.Code) - } -}