Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion apps/browser-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
"@evaluate/shapes": "workspace:^",
"@evaluate/style": "workspace:^",
"@t3-oss/env-core": "^0.11.1",
"@webext-core/storage": "^1.2.0",
"lucide-react": "^0.475.0",
"posthog-js": "^1.217.4",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"sonner": "^1.7.2",
Expand All @@ -27,7 +27,13 @@
"zod": "3.22.4"
},
"devDependencies": {
"@babel/generator": "^7.26.9",
"@babel/parser": "^7.26.9",
"@babel/traverse": "^7.26.9",
"@babel/types": "^7.26.9",
"@crxjs/vite-plugin": "2.0.0-beta.30",
"@types/babel__generator": "^7.6.8",
"@types/babel__traverse": "^7.20.6",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.5",
"@types/webextension-polyfill": "^0.12.1",
Expand Down
60 changes: 46 additions & 14 deletions apps/browser-extension/src/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { executeCode } from '@evaluate/engine/execute';
import { searchRuntimes } from '@evaluate/engine/runtimes';
import type { ExecuteResult, PartialRuntime } from '@evaluate/shapes';
import type { ProtocolWithReturn } from 'webext-bridge';
import { sendMessage } from 'webext-bridge/background';
import { onMessage, sendMessage } from 'webext-bridge/background';
import browser from 'webextension-polyfill';
import env from '~/env';
import posthog, { sessionLog } from '~/services/posthog';

declare module 'webext-bridge' {
export interface ProtocolMap {
Expand All @@ -22,25 +23,36 @@ declare module 'webext-bridge' {
{ code: string; runtimes: PartialRuntime[]; results: ExecuteResult[] },
void
>;
getBackgroundSessionId: ProtocolWithReturn<void, string | undefined>;
}
}

browser.action.setTitle({ title: 'Evaluate' });

browser.action.onClicked.addListener(async () => {
browser.tabs.create({ url: `${env.VITE_PUBLIC_WEBSITE_URL}` });
browser.runtime.onInstalled.addListener((details) => {
if (details.reason === 'install') {
posthog?.capture('installed_extension');
} else if (details.reason === 'update') {
posthog?.capture('updated_extension');
}
});

browser.contextMenus.create({
id: 'runCodeSelection',
title: 'Execute Code',
contexts: ['selection'],
});

browser.runtime.onInstalled.addListener(async () => {
browser.contextMenus.create({
id: 'runCodeSelection',
title: 'Execute Code',
contexts: ['selection'],
});
browser.action.onClicked.addListener(async () => {
console.log(posthog);
posthog?.capture('clicked_browser_action');
browser.tabs.create({ url: `${env.VITE_PUBLIC_WEBSITE_URL}` });
});

browser.contextMenus.onClicked.addListener(async (info, tab) => {
if (info.menuItemId !== 'runCodeSelection' || !tab?.id) return;

posthog?.capture('clicked_context_menu_item', { $current_url: tab.url });
const endpoint = `content-script@${tab.id}`;

const selection = await sendMessage('getSelectionInfo', void 0, endpoint);
Expand All @@ -59,14 +71,34 @@ browser.contextMenus.onClicked.addListener(async (info, tab) => {
runtime: runtime.id,
files: { 'file.code': code },
entry: 'file.code',
}).catch((error) => {
const message = error instanceof Error ? error.message : 'Unknown error';
sendMessage('executionFailed', { errorMessage: message }, endpoint);
throw error;
});
})
.then((result) => {
posthog?.capture('executed_code', {
runtime_id: runtime.id,
code_length: code.length,
code_lines: code.split('\n').length,
compile_successful: result.compile ? result.compile.code === 0 : null,
execution_successful:
result.run.code === 0 &&
(!result.compile || result.compile.code === 0),
});
return result;
})
.catch((error) => {
const message =
error instanceof Error ? error.message : 'Unknown error';
sendMessage('executionFailed', { errorMessage: message }, endpoint);
throw error;
});
promises.push(initialPromise);
}

const results = await Promise.all(promises);
sendMessage('executionFinished', { code, runtimes, results }, endpoint);
});

onMessage('getBackgroundSessionId', () => {
const sessionId = posthog?.get_session_id();
sessionLog(sessionId);
return sessionId;
});
12 changes: 9 additions & 3 deletions apps/browser-extension/src/content-script/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { Toaster } from '@evaluate/components/toast';
import tailwindCss from '@evaluate/style/.css';
import tailwindCss from '@evaluate/style/.css?inline';
import { createRoot } from 'react-dom/client';
import sonnerCss from 'sonner/dist/styles.css';
import { onMessage } from 'webext-bridge/content-script';
import sonnerCss from 'sonner/dist/styles.css?inline';
import { onMessage, sendMessage } from 'webext-bridge/content-script';
import { extractRuntimeResolvables } from '~/helpers/runtime-resolvables';
import posthog, { sessionLog } from '~/services/posthog';
import { Execution } from './execution';
import { createIsolatedElement } from './shadow-root';

sendMessage('getBackgroundSessionId', void 0).then((id) => {
sessionLog(id);
Reflect.set(posthog?.sessionManager ?? {}, '_sessionId', id);
});

onMessage('getSelectionInfo', () => {
const selection = window.getSelection();
const element = selection?.anchorNode?.parentElement;
Expand Down
2 changes: 2 additions & 0 deletions apps/browser-extension/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ export default createEnv({
.string()
.url()
.transform((v) => new URL2(v)),
VITE_PUBLIC_POSTHOG_KEY: z.string().min(1).optional(),
},

runtimeEnv: {
...import.meta.env,
VITE_PUBLIC_WEBSITE_URL: import.meta.env.VITE_PUBLIC_WEBSITE_URL,
VITE_PUBLIC_POSTHOG_KEY: import.meta.env.VITE_PUBLIC_POSTHOG_KEY,
},
});
50 changes: 50 additions & 0 deletions apps/browser-extension/src/services/posthog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { createLogger } from '@evaluate/helpers/logger';
import posthog from 'posthog-js';
import env from '~/env';

export const captureLog = createLogger('posthog capture', '#eb9d2a');
export const sessionLog = createLogger('posthog session', '#eb9d2a');

export function isAvailable() {
return Boolean(
env.VITE_PUBLIC_POSTHOG_KEY, // has a posthog key
);
}

export function initPostHog() {
if (!isAvailable()) return;

posthog.init(env.VITE_PUBLIC_POSTHOG_KEY!, {
api_host: `${env.VITE_PUBLIC_WEBSITE_URL}/api/ingest`,
ui_host: 'https://us.posthog.com/',

// Minimal tracking, only sessions, never people
persistence: 'memory',
person_profiles: 'never',

// Don't track anything unrelated to this extension
autocapture: { css_selector_allowlist: ['#evaluate-shadow-container'] },
capture_pageview: false,
capture_pageleave: false,
disable_compression: true,
disable_external_dependency_loading: true,
disable_session_recording: true,
disable_surveys: true,
enable_heatmaps: false,
advanced_disable_decide: true,

before_send(capture) {
if (capture) captureLog(capture.event, capture);
return capture;
},
});

posthog.register({
$set_once: { platform: 'browser' },
source: 'browser extension',
});

return posthog;
}

export default initPostHog();
45 changes: 45 additions & 0 deletions apps/browser-extension/vite-plugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import generate_ from '@babel/generator';
import { parse } from '@babel/parser';
import traverse_ from '@babel/traverse';
import {
isArrowFunctionExpression,
isBlockStatement,
isIfStatement,
isReturnStatement,
} from '@babel/types';
import type { Plugin } from 'vite';
const generate = Reflect.get(generate_, 'default') as typeof generate_;
const traverse = Reflect.get(traverse_, 'default') as typeof traverse_;

/**
* Vite plugin to remove the external script loading from posthog-js.
* This is required because chrome extensions do not allow loading external scripts due to security reasons.
*/
export function removeExternalScriptLoading(): Plugin {
return {
name: 'modify-load-script',
enforce: 'pre',
transform(code, id) {
if (!id.includes('posthog-js/dist/module.js')) return null;

const ast = parse(code, { sourceType: 'module' });

traverse(ast, {
VariableDeclarator(path) {
if (
isArrowFunctionExpression(path.node.init) &&
path.node.init.params.length === 3 &&
isBlockStatement(path.node.init.body)
) {
const ifStatement = path.node.init.body.body //
.find((node) => isIfStatement(node));
if (isReturnStatement(ifStatement?.consequent))
path.node.init.body = ifStatement.consequent.argument!;
}
},
});

return generate(ast);
},
};
}
2 changes: 2 additions & 0 deletions apps/browser-extension/vite.config.chrome.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { defineConfig } from 'vite';
import zipPack from 'vite-plugin-zip-pack';
import tsconfigPaths from 'vite-tsconfig-paths';
import manifest from './manifest.json';
import { removeExternalScriptLoading } from './vite-plugins';

export default defineConfig(({ mode }) => ({
plugins: [
removeExternalScriptLoading(),
tsconfigPaths(),
react(),
chromeExtension({
Expand Down
2 changes: 2 additions & 0 deletions apps/browser-extension/vite.config.firefox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { defineConfig } from 'vite';
import zipPack from 'vite-plugin-zip-pack';
import tsconfigPaths from 'vite-tsconfig-paths';
import manifest from './manifest.json';
import { removeExternalScriptLoading } from './vite-plugins';

export default defineConfig(({ mode }) => ({
plugins: [
removeExternalScriptLoading(),
tsconfigPaths(),
react(),
chromeExtension({
Expand Down
4 changes: 3 additions & 1 deletion apps/discord-bot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
"@evaluate/shapes": "workspace:^",
"@t3-oss/env-core": "^0.11.1",
"@vercel/functions": "^1.5.1",
"posthog-node": "^4.3.1",
"atomic-fns": "^1.1.6",
"date-fns": "^4.1.0",
"posthog-node": "^4.6.0",
"zod": "3.22.4"
}
}
20 changes: 13 additions & 7 deletions apps/discord-bot/src/commands/evaluate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
import { EditEvaluationButton } from '~/components/edit-evaluation-button';
import { EvaluateModal, EvaluateModalEdit } from '~/components/evaluate-modal';
import { handleEvaluating } from '~/handlers/evaluate';
import { captureEvent } from '~/services/posthog';
import { getInteractionContext } from '~/utilities/session-context';

export class EvaluateCommand extends Command {
name = 'evaluate';
Expand Down Expand Up @@ -42,17 +44,21 @@ export class EvaluateCommand extends Command {
components = [EditEvaluationButton];
modals = [EvaluateModal, EvaluateModalEdit];

async run(interaction: CommandInteraction) {
const runtime = interaction.options.getString('runtime');
const code = interaction.options.getString('code');
const input = interaction.options.getString('input');
const args = interaction.options.getString('arguments');
async run(use: CommandInteraction) {
captureEvent(getInteractionContext(use.rawData), 'used_command', {
command_name: this.name,
});

const runtime = use.options.getString('runtime');
const code = use.options.getString('code');
const input = use.options.getString('input');
const args = use.options.getString('arguments');

if (runtime && code) {
return handleEvaluating(interaction, { runtime, code, args, input });
return handleEvaluating(use, { runtime, code, args, input });
} else {
const modal = new EvaluateModal({ runtime, code, args, input });
return interaction.showModal(modal);
return use.showModal(modal);
}
}
}
6 changes: 6 additions & 0 deletions apps/discord-bot/src/components/edit-evaluation-button.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Button, type ButtonInteraction, ButtonStyle } from '@buape/carbon';
import { captureEvent } from '~/services/posthog';
import { getEvaluateOptions } from '~/utilities/evaluate-helpers';
import { resolveEmoji } from '~/utilities/resolve-emoji';
import { getInteractionContext } from '~/utilities/session-context';
import { EvaluateModalEdit } from './evaluate-modal';

export class EditEvaluationButton extends Button {
Expand All @@ -10,6 +12,10 @@ export class EditEvaluationButton extends Button {
emoji = resolveEmoji('pencil', true);

async run(click: ButtonInteraction) {
captureEvent(getInteractionContext(click.rawData), 'clicked_button', {
button_id: this.customId,
});

const embed = click.embeds?.[0]!;
const options = getEvaluateOptions(embed);
return click.showModal(new EvaluateModalEdit(options));
Expand Down
18 changes: 12 additions & 6 deletions apps/discord-bot/src/components/evaluate-modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
TextInputStyle,
} from '@buape/carbon';
import { handleEvaluating } from '~/handlers/evaluate';
import { captureEvent } from '~/services/posthog';
import { getInteractionContext } from '~/utilities/session-context';

export class EvaluateModal extends Modal {
title = 'Evaluate';
Expand All @@ -27,13 +29,17 @@ export class EvaluateModal extends Modal {
}
}

async run(interaction: ModalInteraction) {
const runtime = interaction.fields.getText('runtime', true);
const code = interaction.fields.getText('code', true);
const args = interaction.fields.getText('args');
const input = interaction.fields.getText('input');
async run(submit: ModalInteraction) {
captureEvent(getInteractionContext(submit.rawData), 'submitted_modal', {
modal_id: this.customId,
});

return handleEvaluating(interaction, { runtime, code, args, input });
const runtime = submit.fields.getText('runtime', true);
const code = submit.fields.getText('code', true);
const args = submit.fields.getText('args');
const input = submit.fields.getText('input');

return handleEvaluating(submit, { runtime, code, args, input });
}
}

Expand Down
Loading