Files
id/apps/web/vite.config.ts
zeekay aa73c86846 Count every arrival on the brand's key; @hanzo/event 0.3.61
The stream is our own first-party analytics and now runs for every
visitor, whatever they chose: before a choice, after the bar's x, and
from a browser sending GPC or DNT. The route gate stays: a page whose
address carries a code or a user_code still emits nothing, and a host
with no key still reports nothing. No Google or Meta tag loads here,
whatever is chosen. The policy cloud serves is still fetched on arrival,
because it tells the bar whether to ask; the stream does not wait on it.

@hanzo/ui 8.29.14's bar has Manage preferences, Accept all and an x that
keeps essential cookies only. Its x draws through react-native-svg, so
vite prefers .web.* extensions and maps the asset registry to
react-native-web's, as the other gui hosts do.

The wire, policy and e2e consent suites now hold the new contract.
2026-10-25 22:32:16 -07:00

109 lines
4.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { resolve } from 'path'
import { readFileSync, existsSync } from 'fs'
import { createRequire } from 'module'
// ESM Vite config has no global `require`; build one bound to this file so
// `require.resolve('@scope/brand/brand.json')` works at config-eval time.
const req = createRequire(import.meta.url)
/**
* Per-brand brand.json copy plugin.
*
* Each per-org brand package (`@hanzo/brand`, `@luxfi/brand`, `@zooai/brand`,
* `@parsdao/brand`) ships a `brand.json` at the package root. We serve each at
* a FLAT, encoding-safe path `/brand/<scope>.json` (scope = the npm scope:
* `@hanzo/brand` -> `hanzo`). A nested `/brand/@hanzo/brand/brand.json` URL
* carries a literal `@` and an encoded `%2F` that the production static server
* (hanzoai/static) cannot map to the on-disk file — it falls through to the
* SPA catch-all and returns index.html, so the runtime brand fetch would parse
* HTML as JSON. The flat slug avoids that entirely. `loadBrand` fetches the
* same `/brand/<scope>.json`.
*
* Only the package's `brand` object is served, because it is the one part
* `loadBrand` reads. The rest of a package's file (chains, RPC, API endpoints,
* WalletConnect ids) describes other products and has no reader here.
*
* Assets (logos, favicons) are imported by URL inside the per-brand `brand.json`
* (CDN URLs in production), so no further asset copying is needed.
*/
const BRAND_PACKAGES = ['@hanzo/brand', '@luxfi/brand', '@zooai/brand', '@parsdao/brand']
/** npm scope -> flat brand slug: `@hanzo/brand` -> `hanzo`. */
const brandSlug = (pkg: string): string => pkg.replace(/^@/, '').split('/')[0]!
/** The served body for a brand package: its `brand` object and nothing else. */
const brandBody = (path: string): string => JSON.stringify({ brand: JSON.parse(readFileSync(path, 'utf8')).brand })
function brandJsonPlugin() {
return {
name: 'id-brand-json',
configureServer(server: any) {
server.middlewares.use((req2: any, res: any, next: any) => {
const m = /^\/brand\/([^/]+)\.json$/.exec(req2.url ?? '')
if (!m) return next()
const slug = m[1]!
const pkg = BRAND_PACKAGES.find((p) => brandSlug(p) === slug)
if (!pkg) {
res.statusCode = 404
return res.end()
}
try {
const path = req.resolve(`${pkg}/brand.json`)
res.setHeader('Content-Type', 'application/json')
res.setHeader('Cache-Control', 'no-store')
return res.end(brandBody(path))
} catch {
res.statusCode = 404
return res.end()
}
})
},
generateBundle(this: any) {
for (const pkg of BRAND_PACKAGES) {
try {
const path = req.resolve(`${pkg}/brand.json`)
if (!existsSync(path)) continue
this.emitFile({
type: 'asset',
fileName: `brand/${brandSlug(pkg)}.json`,
source: brandBody(path),
})
} catch {
// pkg not installed — skip silently; only the brands listed in
// package.json deps actually need their JSON shipped.
}
}
},
}
}
export default defineConfig({
plugins: [react(), brandJsonPlugin()],
resolve: {
// The cookie banner (`src/banner.tsx`) renders on @hanzo/gui, which draws
// through react-native-web: `react-native` means `react-native-web` here, and
// `.web.js` wins over `.js`, which is how react-native-svg (under the bar's ×)
// offers its browser implementation instead of its native Fabric bindings.
alias: {
'@': resolve(__dirname, 'src'),
'react-native': 'react-native-web',
'@react-native/assets-registry/registry': 'react-native-web/dist/modules/AssetRegistry',
},
extensions: ['.web.tsx', '.web.ts', '.web.jsx', '.web.js', '.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json'],
},
server: {
port: 5173,
host: '0.0.0.0',
},
preview: {
port: 5174,
host: '0.0.0.0',
},
build: {
target: 'es2022',
sourcemap: false,
},
})