Packages: core (BIP39/32/44/49/84/86 derivation, WIF), keystore (PBKDF2 + AES-GCM), psbt, p2p (BIP157/158 SPV daemon, Dandelion++ broadcast with relay-echo verification), blockbook, rpc, electrum, net, bns, web-ui (React/Vite), mobile (Expo). ops/ holds the nginx vhost, systemd unit and push.sh deploy script for digibyte.x/web.
108 lines
4.8 KiB
TypeScript
108 lines
4.8 KiB
TypeScript
import { defineConfig, loadEnv, type Plugin } from 'vite';
|
|
import react from '@vitejs/plugin-react';
|
|
import { nodePolyfills } from 'vite-plugin-node-polyfills';
|
|
import { createHash } from 'node:crypto';
|
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
|
|
// Subresource integrity (docs/SECURITY-ROADMAP.md item 2): after the
|
|
// bundle is written, stamp every <script src> / <link rel=stylesheet>
|
|
// in dist/index.html with integrity="sha384-…" and crossorigin. The
|
|
// entry HTML is then the only thing a host has to serve faithfully —
|
|
// a swapped or truncated asset file simply refuses to load. Relative
|
|
// URLs (./assets/…) are resolved against dist/.
|
|
function sri(): Plugin {
|
|
let outDir = 'dist';
|
|
return {
|
|
name: 'dgb-sri',
|
|
apply: 'build',
|
|
configResolved(cfg) { outDir = cfg.build.outDir; },
|
|
closeBundle() {
|
|
const htmlPath = join(outDir, 'index.html');
|
|
let html = readFileSync(htmlPath, 'utf8');
|
|
const digest = (rel: string) =>
|
|
'sha384-' + createHash('sha384').update(readFileSync(join(outDir, rel.replace(/^\.\//, '')))).digest('base64');
|
|
html = html.replace(/<script([^>]*?)\ssrc="(\.\/assets\/[^"]+)"([^>]*)>/g, (m, a, src, b) =>
|
|
m.includes('integrity=') ? m : `<script${a} src="${src}" integrity="${digest(src)}"${/crossorigin/.test(m) ? '' : ' crossorigin="anonymous"'}${b}>`);
|
|
html = html.replace(/<link([^>]*?)rel="stylesheet"([^>]*?)href="(\.\/assets\/[^"]+)"([^>]*)>/g, (m, a, b, href, c) =>
|
|
m.includes('integrity=') ? m : `<link${a}rel="stylesheet"${b}href="${href}" integrity="${digest(href)}"${/crossorigin/.test(m) ? '' : ' crossorigin="anonymous"'}${c}>`);
|
|
writeFileSync(htmlPath, html);
|
|
},
|
|
};
|
|
}
|
|
|
|
// @dgb-wallet/core depends on `bip39`, `bip32`, `bitcoinjs-lib`, etc.
|
|
// which use Node's Buffer + crypto internally. Polyfill so the wallet
|
|
// runs unchanged in the browser.
|
|
//
|
|
// The `/rpc/*` proxy solves the browser-→-localhost-RPC problem:
|
|
// browsers can't easily POST with Basic Auth to a different origin
|
|
// (14022) from 5173. Vite intercepts /rpc/ requests server-side,
|
|
// injects the Basic Auth header from env vars, and forwards to Core.
|
|
// The browser only ever sees same-origin JSON-RPC responses.
|
|
//
|
|
// Env vars (set in your shell before `npm run dev`):
|
|
// DGB_RPC_URL default http://127.0.0.1:14022
|
|
// DGB_RPC_USER required for /rpc/ to work
|
|
// DGB_RPC_PASSWORD required for /rpc/ to work
|
|
export default defineConfig(({ mode }) => {
|
|
// Look for env in the package dir (where vite lives), not process.cwd()
|
|
// — with npm workspaces, cwd can be the monorepo root.
|
|
const pkgDir = new URL('.', import.meta.url).pathname.replace(/^\//, '');
|
|
const env = { ...loadEnv(mode, pkgDir, 'DGB_'), ...loadEnv(mode, process.cwd(), 'DGB_') };
|
|
const rpcUrl = env.DGB_RPC_URL ?? process.env.DGB_RPC_URL ?? 'http://127.0.0.1:14022';
|
|
const rpcUser = env.DGB_RPC_USER ?? process.env.DGB_RPC_USER ?? '';
|
|
const rpcPassword = env.DGB_RPC_PASSWORD ?? process.env.DGB_RPC_PASSWORD ?? '';
|
|
const authHeader =
|
|
rpcUser && rpcPassword
|
|
? 'Basic ' + Buffer.from(`${rpcUser}:${rpcPassword}`).toString('base64')
|
|
: '';
|
|
console.log(`[vite proxy] target=${rpcUrl} user=${rpcUser || '(none)'} auth=${authHeader ? 'ON' : 'OFF'}`);
|
|
|
|
// `base` controls the leading path for every generated asset URL.
|
|
// dev : '/' (Vite serves at localhost:5173/)
|
|
// production : './' (relative — anchors against the runtime
|
|
// <base> tag our index.html injects, so
|
|
// the same bundle works under digibyte.x/web/
|
|
// AND navigate.st/bns/digibyte.x/web/)
|
|
// Override with DGB_UI_BASE if you're serving from something exotic.
|
|
const defaultBase = mode === 'production' ? './' : '/';
|
|
const base = env.DGB_UI_BASE ?? process.env.DGB_UI_BASE ?? defaultBase;
|
|
|
|
return {
|
|
base,
|
|
plugins: [
|
|
react(),
|
|
sri(),
|
|
nodePolyfills({
|
|
globals: { Buffer: true, global: true, process: true },
|
|
protocolImports: true,
|
|
}),
|
|
],
|
|
server: {
|
|
port: 5173,
|
|
host: '127.0.0.1',
|
|
proxy: {
|
|
'/rpc': {
|
|
target: rpcUrl,
|
|
changeOrigin: true,
|
|
rewrite: (p) => p.replace(/^\/rpc\/?/, '/'),
|
|
configure: (proxy) => {
|
|
proxy.on('proxyReq', (proxyReq) => {
|
|
if (authHeader) proxyReq.setHeader('Authorization', authHeader);
|
|
});
|
|
},
|
|
},
|
|
// /p2p/ → the @dgb-wallet/p2p daemon (a small Node HTTP server
|
|
// wrapping the SPV client). Enables browser broadcast via P2P
|
|
// instead of Core RPC. Start the daemon with:
|
|
// npm run daemon -w @dgb-wallet/p2p
|
|
'/p2p': {
|
|
target: 'http://127.0.0.1:5174',
|
|
changeOrigin: true,
|
|
rewrite: (p) => p.replace(/^\/p2p\/?/, '/'),
|
|
},
|
|
},
|
|
},
|
|
};
|
|
});
|