Skip to content

Commit

Permalink
Added web sender and wired up to example/apollo-client
Browse files Browse the repository at this point in the history
  • Loading branch information
kgpax committed Sep 15, 2023
1 parent 18e5c5f commit 5d203ae
Show file tree
Hide file tree
Showing 15 changed files with 171 additions and 26 deletions.
3 changes: 2 additions & 1 deletion examples/apollo-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
"license": "MIT",
"private": true,
"scripts": {
"start": "parcel ./src/index.html --port 4001"
"start": "parcel ./src/index.html --port 4001 --no-cache"
},
"dependencies": {
"@envy/web": "*",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"urql": "^4.0.5"
Expand Down
2 changes: 1 addition & 1 deletion examples/apollo-client/src/components/Sanity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default function Sanity() {
<div className="flex flex-col gap-2">
{category.products.map((product: any) => {
return product.variants.map((variant: any) => (
<div className="flex flex-row justify-between ml-4 mr-12">
<div key={variant.id} className="flex flex-row justify-between ml-4 mr-12">
<div>{variant.name}</div>
<div>${variant.price.toFixed(2)}</div>
</div>
Expand Down
3 changes: 3 additions & 0 deletions examples/apollo-client/src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { enableTracing } from '@envy/web';
import { createRoot } from 'react-dom/client';

enableTracing({ serviceName: 'examples/apollo', debug: true, port: 9999 });

import { App } from './App';

const container = document.getElementById('app');
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@
"examples/*"
],
"devDependencies": {
"@changesets/cli": "2.26.2",
"@tsconfig/node16": "^16.1.1",
"@types/jest": "^29.5.4",
"@typescript-eslint/eslint-plugin": "^6.6.0",
"@typescript-eslint/parser": "^6.6.0",
"@changesets/cli": "2.26.2",
"concurrently": "^8.2.1",
"eslint": "^8.48.0",
"eslint-config-prettier": "^9.0.0",
Expand Down
14 changes: 3 additions & 11 deletions packages/browser/src/model/CollectorClient.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Event, EventType, HttpRequest } from '@envy/core';
import { DEFAULT_WEB_SOCKET_PORT, Event, EventType, HttpRequest } from '@envy/core';

import { Traces } from '@/types';
import { safeParseJson } from '@/utils';
Expand Down Expand Up @@ -44,7 +44,8 @@ export default class CollectorClient {
}

private _connect() {
const socket = new WebSocket(`ws://localhost:${this._port}/viewer`);
const port = this._port ?? DEFAULT_WEB_SOCKET_PORT;
const socket = new WebSocket(`ws://localhost:${port}/viewer`);

socket.onopen = () => {
this._connecting = false;
Expand All @@ -57,15 +58,6 @@ export default class CollectorClient {
this._connected = false;
this._connecting = true;
this._signalChange();
this._retryCount += 1;
if (this._shouldRetry && this._retryCount < 3) {
// TODO: implement incremental back-off?
setTimeout(this._connect, 3000);
} else {
this._shouldRetry = false;
this._connecting = false;
this._signalChange();
}
};

socket.onmessage = ({ data }) => {
Expand Down
12 changes: 9 additions & 3 deletions packages/browser/src/model/mockData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@ function requestData(
host: Trace['host'],
port: Trace['port'],
path: Trace['path'],
): Pick<Trace, 'httpVersion' | 'method' | 'host' | 'port' | 'path' | 'url'> {
): Pick<Trace, 'method' | 'host' | 'port' | 'path' | 'url'> {
const protocol = port === 433 ? 'https://' : 'http://';
const hostString = port === 80 || port === 443 ? `${host}` : `${host}:${port.toString()}`;

return {
httpVersion: '1.1',
method,
host,
port,
Expand Down Expand Up @@ -47,6 +46,7 @@ const mockTraces: Trace[] = [
},
requestBody: undefined,
// ---------
httpVersion: '1.1',
statusCode: 200,
statusMessage: 'OK',
responseHeaders: {
Expand Down Expand Up @@ -128,6 +128,7 @@ const mockTraces: Trace[] = [
},
requestBody: undefined,
// ---------
httpVersion: '1.1',
statusCode: 200,
statusMessage: 'OK',
responseHeaders: {
Expand Down Expand Up @@ -158,6 +159,7 @@ const mockTraces: Trace[] = [
},
requestBody: undefined,
// ---------
httpVersion: '1.1',
statusCode: 404,
statusMessage: 'Not found',
responseHeaders: {
Expand Down Expand Up @@ -191,6 +193,7 @@ const mockTraces: Trace[] = [
lastName: 'Bear',
}),
// ---------
httpVersion: '1.1',
statusCode: 200,
statusMessage: 'OK',
responseHeaders: {
Expand All @@ -205,7 +208,6 @@ const mockTraces: Trace[] = [
'connection': 'keep-alive',
'keep-alive': 'timeout=5',
},
httpVersion: '1.1',
responseBody: JSON.stringify({
id: '4',
}),
Expand Down Expand Up @@ -239,6 +241,7 @@ const mockTraces: Trace[] = [
},
}),
// ---------
httpVersion: '1.1',
statusCode: 200,
statusMessage: 'OK',
responseHeaders: {
Expand Down Expand Up @@ -276,6 +279,7 @@ const mockTraces: Trace[] = [
},
requestBody: undefined,
// ---------
httpVersion: '1.1',
statusCode: 500,
statusMessage: 'Internal Server Error',
responseHeaders: {
Expand Down Expand Up @@ -303,6 +307,7 @@ const mockTraces: Trace[] = [
},
requestBody: undefined,
// ---------
httpVersion: '1.1',
statusCode: 200,
statusMessage: 'OK',
responseHeaders: {
Expand Down Expand Up @@ -330,6 +335,7 @@ const mockTraces: Trace[] = [
},
requestBody: undefined,
// ---------
httpVersion: '1.1',
statusCode: undefined,
statusMessage: undefined,
responseHeaders: undefined,
Expand Down
13 changes: 5 additions & 8 deletions packages/browser/src/scripts/startCollector.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,18 @@ wss.on('listening', () => {
});

wss.on('connection', (ws, request) => {
if (request.url === '/viewer' && !viewer) {
if (request.url === '/viewer') {
log(chalk.green('✅ Envy viewer client connected'));
viewer = ws;
}

if (request.url === '/node' && !viewer) {
if (request.startsWith === '/node' && !viewer) {
log(chalk.green('✅ Envy node sender connected'));
}

ws.on('close', () => {
if (viewer !== null) {
log(chalk.red('❌ Envy viewer client disconnected'));
viewer = null;
}
});
if (request.startsWith === '/web' && !viewer) {
log(chalk.green('✅ Envy web sender connected'));
}

ws.on('message', data => {
if (!viewer || viewer.readyState !== WebSocket.OPEN) {
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/systems/GraphQL.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default class GraphQL implements System<GraphQLData> {
name = 'GraphQL';

isMatch(trace: Trace) {
return trace.path === '/api/graphql';
return trace.path?.endsWith('/graphql') ?? false;
}

getData(trace: Trace) {
Expand Down
15 changes: 15 additions & 0 deletions packages/web/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "@envy/web",
"version": "0.1.0",
"description": "Node.js Network & Telemetry Viewer",
"main": "dist/index.js",
"repository": "https://github.com/FormidableLabs/envy.git",
"license": "MIT",
"scripts": {
"prebuild": "rimraf dist",
"build": "tsc"
},
"dependencies": {
"@envy/core": "0.2.0"
}
}
64 changes: 64 additions & 0 deletions packages/web/src/http.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { EventType, HttpRequest } from '@envy/core';

function formatHeaders(headers: HeadersInit | Headers | undefined): HttpRequest['requestHeaders'] {
if (headers) {
if (Array.isArray(headers)) {
return headers.reduce<HttpRequest['requestHeaders']>((acc, [key, value]) => {
acc[key] = value;
return acc;
}, {});
} else if (headers instanceof Headers) {
return Object.fromEntries(headers.entries());
} else {
return headers;
}
}

return {};
}

export function fetchRequestToEvent(
timestamp: number,
id: string,
input: RequestInfo | URL,
init?: RequestInit,
): HttpRequest {
let url: URL;
if (typeof input === 'string') {
url = new URL(input);
} else if (input instanceof Request) {
url = new URL(input.url);
} else {
url = input;
}

return {
id,
parentId: undefined,
timestamp,
type: EventType.HttpRequest,
method: (init?.method ?? 'GET') as HttpRequest['method'],
host: url.host,
port: parseInt(url.port, 10),
path: url.pathname,
url: url.toString(),
requestHeaders: formatHeaders(init?.headers),
requestBody: init?.body?.toString() ?? undefined,
};
}

export async function fetchResponseToEvent(
timestamp: number,
req: HttpRequest,
response: Response,
): Promise<HttpRequest> {
return {
...req,
httpVersion: response.type,
statusCode: response.status,
statusMessage: response.statusText,
responseHeaders: formatHeaders(response.headers),
responseBody: await response.text(),
duration: timestamp - req.timestamp,
};
}
1 change: 1 addition & 0 deletions packages/web/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './tracing';
10 changes: 10 additions & 0 deletions packages/web/src/log.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* eslint-disable no-console */

const { name } = require('../package.json');

export default {
info: (msg: string, ...args: unknown[]) => console.log(`✅ %c${name} ${msg}`, 'color: green', ...args),
warn: (msg: string, ...args: unknown[]) => console.log(`🚸 %c${name} ${msg}`, 'color: yellow', ...args),
error: (msg: string, ...args: unknown[]) => console.log(`❌ %c${name} ${msg}`, 'color: red', ...args),
debug: (msg: string, ...args: unknown[]) => console.log(`🔧 %c$${name} ${msg}`, 'color: cyan', ...args),
};
4 changes: 4 additions & 0 deletions packages/web/src/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface Options {
serviceName: string;
debug?: boolean;
}
43 changes: 43 additions & 0 deletions packages/web/src/tracing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { DEFAULT_WEB_SOCKET_PORT } from '@envy/core';

import { fetchRequestToEvent, fetchResponseToEvent } from './http';
import log from './log';
import { Options } from './options';

export interface TracingOptions extends Options {
port?: number;
}

export function enableTracing(options: TracingOptions) {
if (typeof window === 'undefined') {
log.error('Attempted to use @envy/web in a non-browser environment');
return;
}

if (options.debug) log.info('Starting in debug mode');

const wsUri = `ws://127.0.0.1:${options.port ?? DEFAULT_WEB_SOCKET_PORT}/web/${options.serviceName}`;
const ws = new WebSocket(wsUri);
ws.onopen = () => {
if (options.debug) log.info(`Connected to ${wsUri}`);

const { fetch: originalFetch } = window;

window.fetch = async (...args) => {
// TODO: better unique id
const tsReq = Date.now();
const id = `${tsReq}`;

const reqEvent = fetchRequestToEvent(tsReq, id, ...args);
ws.send(JSON.stringify(reqEvent));

const response = await originalFetch(...args);
const tsRes = Date.now();
const responseClone = response.clone();
const resEvent = await fetchResponseToEvent(tsRes, reqEvent, responseClone);
ws.send(JSON.stringify(resEvent));

return response;
};
};
}
9 changes: 9 additions & 0 deletions packages/web/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"include": ["src/**/*"],
"exclude": ["**/*.spec.ts"],
"compilerOptions": {
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"outDir": "dist"
}
}

0 comments on commit 5d203ae

Please sign in to comment.