mirror of
https://github.com/hanzoai/o11y.git
synced 2026-10-15 12:58:33 +00:00
o11y is a self-hosted observability tool that was phoning home to three
third-party SaaS vendors with our users' data — inherited from the upstream
fork and, worse, enabled by DEFAULT: vite.config.ts gated each on
`env.VITE_X_ENABLED !== 'false'` (opt-OUT), so any operator who never set the
vars shipped all three. index.html injected two vendor <script> tags on every
page load, and AppRoutes HMAC-hashed the logged-in user's email to identify
them to the support-chat vendor.
Removed end to end:
- Go: web.Settings/SettingsConfig drop the three trackers, their configs,
defaults, and the O11Y_WEB_SETTINGS_* env surface.
- Schema: docs/config/web-settings.json declares only sentry;
types/generated/webSettings.ts regenerated from it (not hand-edited).
- Frontend: both vendor <script> loaders deleted from index.html; vite
defines, env types, example.env, window typings and widget styles gone.
The identify + theme/bubble effects in AppRoutes go with them, along with
the now-dead crypto-js Hex/HmacSHA256 and useIsDarkMode imports, and a
useEffect that existed only to compute the widget's feature-flag gates.
Support chat is now Hanzo Chat, native and one way: utils/supportChat.ts
exposes a single openSupportChat(); the three inline widget call sites
(SideNav, Support, LaunchChatSupport) all route through it. It delegates to
the existing openInNewTab helper rather than calling window.open itself —
the repo's own o11y/no-raw-absolute-path rule mandates that helper.
openInNewTab now passes `noopener`. window.open, unlike <a target="_blank">,
does not imply it, so all 57 call sites were leaving the opened tab able to
navigate us via window.opener (reverse tabnabbing). No caller can be affected:
the function returns void.
Sentry stays — it is our own fork (hanzoai/sentry), and it is now opt-IN
(`=== 'true'`) rather than opt-out.
Also drops two pre-existing dead references that the lint gate surfaced in
the touched files: an unused brand-logo import in SideNav and an empty
`declare interface Window {}` in global.d.ts (the real Window augmentation
lives in typings/window.ts — now the only one).
Verified: go test ./pkg/web/... ok (web + routerweb); tsgo --noEmit clean;
oxlint 0 errors; zero vendor references and zero outbound vendor script
loads remain.
Co-authored-by: Hanzo Dev <[email protected]>
199 lines
6.1 KiB
TypeScript
199 lines
6.1 KiB
TypeScript
import { sentryVitePlugin } from '@sentry/vite-plugin';
|
|
import react from '@vitejs/plugin-react';
|
|
import { resolve } from 'path';
|
|
import { visualizer } from 'rollup-plugin-visualizer';
|
|
import type { Plugin, TransformResult, UserConfig } from 'vite';
|
|
import { defineConfig, loadEnv } from 'vite';
|
|
import vitePluginChecker from 'vite-plugin-checker';
|
|
import viteCompression from 'vite-plugin-compression';
|
|
import { createHtmlPlugin } from 'vite-plugin-html';
|
|
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer';
|
|
import tsconfigPaths from 'vite-tsconfig-paths';
|
|
|
|
// The `[[.BaseHref]]` / `[[.Settings]]` placeholders in index.html were meant to be
|
|
// filled by the o11y Go backend at serve time. o11y now runs headless and the SPA is
|
|
// served by hanzoai/static (which does no templating), so we resolve them at build
|
|
// time — and in the dev server, where the backend is likewise not involved. Both
|
|
// paths produce identical placeholder-free HTML. Set VITE_BASE_PATH to serve under a
|
|
// URL prefix (e.g. /o11y/); defaults to '/'.
|
|
function baseHrefPlugin(basePath: string): Plugin {
|
|
return {
|
|
name: 'base-href',
|
|
transformIndexHtml(html): string {
|
|
return html.replaceAll('[[.BaseHref]]', basePath);
|
|
},
|
|
};
|
|
}
|
|
|
|
function bootSettingsPlugin(env: Record<string, string>): Plugin {
|
|
return {
|
|
name: 'boot-settings',
|
|
transformIndexHtml(html): string {
|
|
// No third-party trackers. Analytics is Hanzo Insights; support chat
|
|
// is Hanzo Chat. Sentry is opt-IN (=== 'true'), pointed at our own
|
|
// fork — never opt-out, which shipped it enabled by default.
|
|
const settings = {
|
|
sentry: { enabled: env.VITE_SENTRY_ENABLED === 'true' },
|
|
};
|
|
return html.replaceAll('[[.Settings]]', JSON.stringify(settings));
|
|
},
|
|
};
|
|
}
|
|
|
|
function rawMarkdownPlugin(): Plugin {
|
|
return {
|
|
name: 'raw-markdown',
|
|
transform(code, id): TransformResult | undefined {
|
|
if (!id.endsWith('.md')) {
|
|
return undefined;
|
|
}
|
|
return {
|
|
code: `export default ${JSON.stringify(code)};`,
|
|
map: null,
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
export default defineConfig(({ mode }): UserConfig => {
|
|
const env = loadEnv(mode, process.cwd(), '');
|
|
// Base path for serving the app (e.g., '/o11y/'). Defaults to '/'.
|
|
const basePath = env.VITE_BASE_PATH || '/';
|
|
|
|
const plugins = [
|
|
tsconfigPaths(),
|
|
rawMarkdownPlugin(),
|
|
baseHrefPlugin(basePath),
|
|
bootSettingsPlugin(env),
|
|
react(),
|
|
createHtmlPlugin({
|
|
inject: {
|
|
data: {},
|
|
},
|
|
}),
|
|
vitePluginChecker({
|
|
typescript: true,
|
|
// this doubles the build time
|
|
// disabled to use Biome/tsgo (in the future) as alternative
|
|
enableBuild: false,
|
|
}),
|
|
];
|
|
|
|
if (env.VITE_SENTRY_AUTH_TOKEN) {
|
|
plugins.push(
|
|
sentryVitePlugin({
|
|
authToken: env.VITE_SENTRY_AUTH_TOKEN,
|
|
org: env.VITE_SENTRY_ORG,
|
|
project: env.VITE_SENTRY_PROJECT_ID,
|
|
}),
|
|
);
|
|
}
|
|
|
|
if (env.BUNDLE_ANALYSER === 'true') {
|
|
plugins.push(
|
|
visualizer({
|
|
open: true,
|
|
gzipSize: true,
|
|
brotliSize: true,
|
|
}),
|
|
);
|
|
}
|
|
|
|
if (mode === 'production') {
|
|
plugins.push(
|
|
ViteImageOptimizer({
|
|
jpeg: { quality: 80 },
|
|
jpg: { quality: 80 },
|
|
}),
|
|
);
|
|
plugins.push(viteCompression());
|
|
}
|
|
|
|
return {
|
|
plugins,
|
|
resolve: {
|
|
alias: {
|
|
// The @hanzo/ui barrel eagerly imports these Next-only modules at the
|
|
// top level. o11y is not a Next app and uses none of them, but a browser
|
|
// cannot resolve bare `next-themes` / `next/*` specifiers — externalizing
|
|
// them leaves bare imports that crash the SPA at load (blank page). Alias
|
|
// to inert browser stubs so the bundle stays self-contained.
|
|
'next-themes': resolve(__dirname, './stubs/next-themes.ts'),
|
|
'next/image': resolve(__dirname, './stubs/next-image.ts'),
|
|
'next/link': resolve(__dirname, './stubs/next-link.ts'),
|
|
'@': resolve(__dirname, './src'),
|
|
utils: resolve(__dirname, './src/utils'),
|
|
types: resolve(__dirname, './src/types'),
|
|
constants: resolve(__dirname, './src/constants'),
|
|
parser: resolve(__dirname, './src/parser'),
|
|
providers: resolve(__dirname, './src/providers'),
|
|
lib: resolve(__dirname, './src/lib'),
|
|
},
|
|
},
|
|
css: {
|
|
preprocessorOptions: {
|
|
less: {
|
|
javascriptEnabled: true,
|
|
},
|
|
},
|
|
modules: {
|
|
localsConvention: 'camelCaseOnly',
|
|
},
|
|
},
|
|
define: {
|
|
// TODO: Remove this in favor of import.meta.env
|
|
'process.env.NODE_ENV': JSON.stringify(mode),
|
|
'process.env.FRONTEND_API_ENDPOINT': JSON.stringify(
|
|
env.VITE_FRONTEND_API_ENDPOINT,
|
|
),
|
|
'process.env.WEBSOCKET_API_ENDPOINT': JSON.stringify(
|
|
env.VITE_WEBSOCKET_API_ENDPOINT,
|
|
),
|
|
'process.env.SENTRY_ORG': JSON.stringify(env.VITE_SENTRY_ORG),
|
|
'process.env.SENTRY_PROJECT_ID': JSON.stringify(env.VITE_SENTRY_PROJECT_ID),
|
|
'process.env.SENTRY_DSN': JSON.stringify(env.VITE_SENTRY_DSN),
|
|
'process.env.TUNNEL_URL': JSON.stringify(env.VITE_TUNNEL_URL),
|
|
'process.env.TUNNEL_DOMAIN': JSON.stringify(env.VITE_TUNNEL_DOMAIN),
|
|
'process.env.DOCS_BASE_URL': JSON.stringify(env.VITE_DOCS_BASE_URL),
|
|
},
|
|
// In production, use relative paths so assets work with any base path injected by the backend.
|
|
// In dev, use the configured base path for proper HMR and routing.
|
|
base: mode === 'production' ? './' : basePath,
|
|
build: {
|
|
sourcemap: true,
|
|
outDir: 'build',
|
|
cssMinify: 'esbuild',
|
|
rollupOptions: {
|
|
// @hanzo/ui eagerly imports all optional peer deps at the module
|
|
// level. These heavy web3/viz packages are used by @hanzo/ui subpaths
|
|
// that o11y never imports, so they tree-shake out entirely (0 refs in
|
|
// the built bundle) — externalizing keeps the bundler from erroring on
|
|
// missing resolution. (next-themes/next/* are NOT here: they survive
|
|
// tree-shaking into o11y's used graph, so they are aliased to browser
|
|
// stubs above instead — a bare import of them would crash the SPA.)
|
|
external: [
|
|
'chrono-node',
|
|
'mermaid',
|
|
'recharts',
|
|
'sql.js',
|
|
'react-qrcode-logo',
|
|
'@rainbow-me/rainbowkit',
|
|
'wagmi',
|
|
/^@tanstack\/react-query/,
|
|
'@modelcontextprotocol/sdk',
|
|
'@hanzo/docs-core',
|
|
/^@o11yhq\//,
|
|
],
|
|
},
|
|
},
|
|
server: {
|
|
open: true,
|
|
port: 3301,
|
|
host: true,
|
|
},
|
|
preview: {
|
|
port: 3301,
|
|
},
|
|
};
|
|
});
|