mirror of
https://github.com/hanzoai/id.git
synced 2026-10-26 20:59:52 +00:00
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.
109 lines
4.0 KiB
TypeScript
109 lines
4.0 KiB
TypeScript
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,
|
||
},
|
||
})
|