Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
6 changes: 5 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@
"material-icon-theme.folders.associations": {
"shapes": "middleware"
},
"cSpell.words": [],
"material-icon-theme.files.associations": {
"vite.config.chrome.ts": "vite",
"vite.config.firefox.ts": "vite"
},
"cSpell.words": ["posthog", "Resolvables"],
"todo-tree.general.tags": ["=====", "NOTE", "IDEA", "TODO", "FIX", "HACK"],
"todo-tree.highlights.customHighlight": {
"=====": {
Expand Down
6 changes: 0 additions & 6 deletions apps/browser-extension/.postcssrc.json

This file was deleted.

50 changes: 50 additions & 0 deletions apps/browser-extension/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"$schema": "https://json.schemastore.org/chrome-manifest",

"manifest_version": 3,
"version": "0.2.0",
"author": {
"name": "Apteryx Software",
"email": "[email protected]"
},
"name": "Evaluate - Execute Any Code, Anywhere, Anytime",
"short_name": "Evaluate",
"description": "Execute any code snippet on any website with Evaluate.",
"homepage_url": "https://evaluate.run",

"icons": {
"16": "images/icon/16.png",
"32": "images/icon/32.png",
"48": "images/icon/48.png",
"64": "images/icon/64.png",
"128": "images/icon/128.png"
},
"action": {
"default_icon": {
"16": "images/icon/16.png",
"32": "images/icon/32.png",
"48": "images/icon/48.png",
"64": "images/icon/64.png",
"128": "images/icon/128.png"
}
},

"permissions": ["contextMenus"],
"background": {
"service_worker": "src/background/index.ts",
"type": "module"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["src/content-script/index.tsx"]
}
],
"web_accessible_resources": [
{
"matches": ["<all_urls>"],
"resources": ["images/icon.png"],
"use_dynamic_url": false
}
]
}
51 changes: 27 additions & 24 deletions apps/browser-extension/package.json
Original file line number Diff line number Diff line change
@@ -1,44 +1,47 @@
{
"name": "browser-extension",
"version": "0.1.3",
"version": "0.2.0",
"type": "module",
"manifest": {
"name": "Evaluate - Execute Any Code, Anywhere, Anytime",
"short_name": "Evaluate",
"description": "Execute any code snippet on any website with Evaluate.",
"author": "Apteryx Software",
"homepage_url": "https://evaluate.run",
"manifest_version": 3,
"permissions": ["contextMenus", "storage"]
},
"scripts": {
"check": "tsc --noEmit",
"dev": "use-env -p PLASMO -- plasmo dev",
"build:styles": "cp ../../packages/react/style.css ./src/style.css",
"build": "pnpm build:chrome",
"build:chrome": "use-env -p PLASMO -P -- plasmo build --target=chrome-mv3 --zip"
"build": "pnpm run \"/build:.*/\"",
"build:chrome": "use-env -p VITE -P -- vite build --config vite.config.chrome.ts",
"build:firefox": "use-env -p VITE -P -- vite build --config vite.config.firefox.ts",
"dev": "pnpm run \"/dev:.*/\"",
"dev:chrome": "use-env -p VITE -- vite --config vite.config.chrome.ts",
"dev:firefox": "use-env -p VITE -- vite --config vite.config.firefox.ts"
},
"dependencies": {
"@evaluate/components": "workspace:^",
"@evaluate/engine": "workspace:^",
"@evaluate/helpers": "workspace:^",
"@evaluate/hooks": "workspace:^",
"@evaluate/shapes": "workspace:^",
"@evaluate/style": "workspace:^",
"@t3-oss/env-core": "^0.11.1",
"framer-motion": "^11.12.0",
"lucide-react": "^0.338.0",
"posthog-js": "1.161.3",
"lucide-react": "^0.475.0",
"posthog-js": "^1.218.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"sonner": "^1.7.4",
"tailwind-merge": "^2.6.0",
"webext-bridge": "^6.0.1",
"webextension-polyfill": "^0.12.0",
"zod": "3.22.4"
},
"devDependencies": {
"@types/chrome": "^0.0.263",
"@types/react": "^18.3.12",
"autoprefixer": "^10.4.20",
"plasmo": "^0.85.2",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.15"
"@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.18",
"@types/react-dom": "^18.3.5",
"@types/webextension-polyfill": "^0.12.1",
"@vitejs/plugin-react": "^4.3.4",
"vite": "3.2.11",
"vite-plugin-zip-pack": "^1.2.4",
"vite-tsconfig-paths": "^5.1.4"
}
}
6 changes: 6 additions & 0 deletions apps/browser-extension/postcss.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
Binary file added apps/browser-extension/public/images/icon/128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/browser-extension/public/images/icon/16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/browser-extension/public/images/icon/180.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/browser-extension/public/images/icon/192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/browser-extension/public/images/icon/32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/browser-extension/public/images/icon/48.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/browser-extension/public/images/icon/512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/browser-extension/public/images/icon/64.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
200 changes: 90 additions & 110 deletions apps/browser-extension/src/background/index.ts
Original file line number Diff line number Diff line change
@@ -1,124 +1,104 @@
import { executeCode } from '@evaluate/engine/dist/execute';
import { searchRuntimes } from '@evaluate/engine/dist/runtimes';
import type { PartialRuntime } from '@evaluate/shapes';
import env from '~env';
import analytics from '~services/analytics';
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 { 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 {
getSelectionInfo: ProtocolWithReturn<
void,
{ code: string; resolvables: string[] }
>;
unknownRuntime: ProtocolWithReturn<{ code: string }, void>;
executionStarted: ProtocolWithReturn<
{ runtimeNameOrCount: string | number },
void
>;
executionFailed: ProtocolWithReturn<{ errorMessage: string }, void>;
executionFinished: ProtocolWithReturn<
{ code: string; runtimes: PartialRuntime[]; results: ExecuteResult[] },
void
>;
getBackgroundSessionId: ProtocolWithReturn<void, string | undefined>;
}
}

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

chrome.runtime.onInstalled.addListener(async (details) => {
if (details.reason === chrome.runtime.OnInstalledReason.INSTALL) {
analytics?.capture('extension installed', {
$current_url: '',
platform: 'browser extension',
});
} else if (details.reason === chrome.runtime.OnInstalledReason.UPDATE) {
analytics?.capture('extension updated', {
$current_url: '',
platform: 'browser extension',
});
browser.runtime.onInstalled.addListener((details) => {
if (details.reason === 'install') {
posthog?.capture('installed_extension');
} else if (details.reason === 'update') {
posthog?.capture('updated_extension');
}

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

chrome.action.onClicked.addListener(async () => {
analytics?.capture('browser action clicked', {
$current_url: '',
platform: 'browser extension',
});

chrome.tabs.create({
url: `${env.PLASMO_PUBLIC_WEBSITE_URL}`,
});
browser.contextMenus.create({
id: 'runCodeSelection',
title: 'Execute Code',
contexts: ['selection'],
});

chrome.contextMenus.onClicked.addListener(async (info, tab) => {
if (info.menuItemId === 'runCodeSelection' && tab?.id) {
analytics?.capture('context menu item clicked', {
$current_url: tab?.url || '',
platform: 'browser extension',
});

// While the info.selectionText is available, we need the element
// to extract the runtime resolvables, so we send a message to the
// messaging content script to get the runtime resolvables
chrome.tabs.sendMessage(tab.id, {
subject: 'parseSelection',
});
}
browser.action.onClicked.addListener(async () => {
console.log(posthog);
posthog?.capture('clicked_browser_action');
browser.tabs.create({ url: `${env.VITE_PUBLIC_WEBSITE_URL}` });
});

chrome.runtime.onMessage.addListener(async (message, sender) => {
if (typeof sender.tab?.id !== 'number') return;

if (message.relay === true) {
chrome.tabs.sendMessage(sender.tab.id, message);
return;
}

if (message.subject === 'prepareCode') {
const code = message.code as string;
const runtimeResolvables = message.runtimeResolvables as string[];

const runtimes = await searchRuntimes(...runtimeResolvables) //
.then((r) => r.slice(0, 5));

if (runtimes.length) {
message = {
subject: 'runCode',
...{ code, runtimes },
};
} else {
chrome.tabs.sendMessage(sender.tab.id, {
subject: 'unknownRuntime',
...{ code },
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);
const { code, resolvables } = selection;
const runtimes = (await searchRuntimes(...resolvables)).slice(0, 5);
if (!runtimes.length)
return sendMessage('unknownRuntime', { code }, endpoint);

const runtimeNameOrCount =
runtimes.length === 1 ? runtimes[0]!.name : runtimes.length;
sendMessage('executionStarted', { runtimeNameOrCount }, endpoint);

const promises = [];
for (const runtime of runtimes) {
const initialPromise = executeCode({
runtime: runtime.id,
files: { 'file.code': code },
entry: 'file.code',
})
.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);
}

//

if (message.subject === 'runCode') {
const code = message.code as string;
const runtimes = message.runtimes as PartialRuntime[];

chrome.tabs.sendMessage(sender.tab.id, {
subject: 'executionStarted',
...{ runtimes },
});

const promises = [];
for (const runtime of runtimes) {
promises.push(
executeCode({
runtime: runtime.id,
files: { 'file.code': code },
entry: 'file.code',
}).then(async (r) => {
analytics?.capture('code executed', {
$current_url: sender.tab?.url || '',
platform: 'browser extension',
'runtime id': runtime.id,
'was successful':
r.run.code === 0 && (!r.compile || r.compile.code === 0),
});
return { ...r, runtime };
}),
);

if (runtimes.length > 1)
await new Promise((resolve) => setTimeout(resolve, 500));
}
const results = await Promise.all(promises);
sendMessage('executionFinished', { code, runtimes, results }, endpoint);
});

const results = await Promise.all(promises);
chrome.tabs.sendMessage(sender.tab.id, {
subject: 'showResults',
...{ code, results },
});
}
onMessage('getBackgroundSessionId', () => {
const sessionId = posthog?.get_session_id();
sessionLog(sessionId);
return sessionId;
});
Loading