diff --git a/.changeset/sour-trees-build.md b/.changeset/sour-trees-build.md
new file mode 100644
index 0000000..4d3846f
--- /dev/null
+++ b/.changeset/sour-trees-build.md
@@ -0,0 +1,5 @@
+---
+'@envyjs/webui': minor
+---
+
+Added support for self-hosting and customization of the Envy viewer
diff --git a/.gitignore b/.gitignore
index fb051db..5246998 100644
--- a/.gitignore
+++ b/.gitignore
@@ -54,3 +54,6 @@ bin
# examples/next.js
**/.next/
**/next-env.d.ts
+
+# other
+.DS_Store
diff --git a/README.md b/README.md
index 637bc00..95869f4 100644
--- a/README.md
+++ b/README.md
@@ -28,12 +28,13 @@ Envy will trace the network calls from every application in your stack and allow
_Note: Envy is intended for development usage only, and is not a replacement for optimized production telemetry_
-
+
## Contents
- [Getting Started](#getting-started)
+- [Customizing](#customizing)
- [Production Bundles](#production-bundles)
- [Contributing](#contributing)
@@ -125,11 +126,17 @@ enableTracing({ serviceName: 'your-website-name' }).then(() => {
_Browsers prevent full timing data from being accessed from cross-origin requests unless the server responds with the [Timing-Allow-Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Timing-Allow-Origin) header_.
-### Production Bundles
+## Customizing
+
+Whilst Envy will run as a zero-config standalone viewer, it is also possible to run the Envy viewer locally from your application and to define your own systems to customize how traces are presented.
+
+See the [customization docs](docs/customizing.md) for more information.
+
+## Production Bundles
Envy is designed to enhance your developer experience and is not intended for production usage. Depending on your application, there are various ways to exclude it from your bundle in production.
-#### Dynamic Imports (Typescript)
+### Dynamic Imports (Typescript)
```ts
if (process.env.NODE_ENV !== 'production') {
@@ -139,7 +146,7 @@ if (process.env.NODE_ENV !== 'production') {
}
```
-#### Dynamic Require (Javascript)
+### Dynamic Require (Javascript)
```ts
if (process.env.NODE_ENV !== 'production') {
@@ -148,7 +155,7 @@ if (process.env.NODE_ENV !== 'production') {
}
```
-#### Disabling Tracing
+### Disabling Tracing
This option is the simplest, but will leave the code in your output bundle. Depending on your application and its deployment and packaging method, this may be acceptable in your usage.
diff --git a/docs/customizing.md b/docs/customizing.md
new file mode 100644
index 0000000..184e390
--- /dev/null
+++ b/docs/customizing.md
@@ -0,0 +1,107 @@
+# Customizing Envy
+
+## Creating your own systems
+
+A system is a `class` which defines the following:
+
+- What identifies the trace as belonging to the system, e.g., the hostname, path, etc.
+- What icon to display for the system
+- What data to show in the list view for the trace
+- What data to show in the detail for the trace
+
+**Let's start by example:**
+
+In the application you are sending traces from, you can create a new `class` like the following:
+
+```tsx
+// ./src/systems/CatFactsSystem.tsx
+
+import { System, Trace } from '@envyjs/webui';
+
+export default class CatFactsSystem implements System {
+ name = 'Cat Facts API';
+
+ isMatch(trace: Trace) {
+ // this system applies to all traces which are requests to the `cat-fact.herokuapp.com` host
+ return trace.http?.host === 'cat-fact.herokuapp.com';
+ }
+
+ getIconUri() {
+ // to avoid the need for external resources, icons can be defined as base64 data
+ return '';
+ }
+
+ getTraceRowData() {
+ // this is the text which will be displayed below the host and path in the list view
+ return {
+ data: 'This is a cat fact',
+ };
+ }
+}
+```
+
+Once you have that system, we need to register it with the Envy viewer. The only way to do this currently is to host the envy viewer yourself as a react component, and pass the systems to register in the props.
+
+For example:
+
+```tsx
+// ./src/MyEnvyViewer.tsx
+
+import EnvyViewer from '@envyjs/webui';
+import { createRoot } from 'react-dom/client';
+
+import CatFactsSystem from './systems/CatFactsSystem';
+
+function MyEnvyViewer() {
+ return ;
+}
+```
+
+Then, you would serve this component up either on a new route in your application, or as a separate application. For example, using parcel you might have the following:
+
+```tsx
+// src/myEnvyViewer.html
+
+
+
+
+ My custome Envy viewer
+
+
+
+
+
+
+
+
+// src/myEnvyViewer.js
+import EnvyViewer from '@envyjs/webui';
+import { createRoot } from 'react-dom/client';
+
+import MyEnvyViewer from './MyEnvyViewer';
+
+const container = document.getElementById('root');
+const root = createRoot(container);
+
+root.render();
+```
+
+Finally, in your `package.json`, you would have to start the `@envyjs/webui` collector, opting out of launching the default viewer UI, and load your UI instead:
+
+```
+// package.json
+
+{
+ "scripts": {
+ "start:envy": "concurrently \"yarn start:collector\" \"yarn start:viewer\"",
+ "start:collector": "npx @envyjs/webui --noUi",
+ "start:viewer": "parcel ./src/myEnvyViewer.html --port 4002 --no-cache"
+ }
+}
+```
+
+Then, running `yarn start:envy` in your application would start the collector process and launch your customized viewer:
+
+