Skip to content

Commit d0faa28

Browse files
Junyi-99Copilot
andauthored
feat: self-hosted overleaf (#41)
<!-- CURSOR_SUMMARY --> > [!NOTE] > Adds a settings page to request host permissions and dynamically register content scripts, enabling self-hosted Overleaf support; converts the popup to a React app and updates build/manifest. > > - **Extension Manifest/Permissions**: > - Replace static `content_scripts` with dynamic registration via `scripting` API; widen `web_accessible_resources` to `*://*/*`. > - Add `optional_host_permissions: '*://*/*'`, new permissions (`scripting`, `activeTab`), and `options_page` (`settings.html`). > - Keep `host_permissions` for Overleaf; strip `permissions_explanation` at build time. > - **Background/Runtime**: > - Add `requestHostPermission` handler and auto-register content scripts when permissions are granted (`registerContentScriptsIfPermitted`). > - New `libs/permissions.ts` to (un)register content scripts for granted origins. > - Expose `requestHostPermission` in `intermediate.ts` and add handler name in `constants`. > - **Settings UI**: > - New React-based settings (`public/settings.html`, `views/extension-settings/...`) with Host Permission widget to request and list granted origins (uses `chrome.permissions`). > - **Popup**: > - Replace static HTML with React popup (`public/popup.html`, `views/extension-popup/...`) and guidance + link to settings. > - **Build/Tooling**: > - Vite configs and `package.json` scripts updated to build `settings` and `popup` bundles. > - Minor log added in `main.tsx`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 677ab6b. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 58a64fd commit d0faa28

25 files changed

Lines changed: 660 additions & 122 deletions

webapp/_webapp/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
"scripts": {
77
"dev": "nodemon --watch src --ext ts,js,tsx,jsx,json --exec 'npm run build'",
88
"dev:chat": "vite dev --config vite.config.dev.ts",
9-
"build": "tsc -b && npm run _build:default && npm run _build:background && npm run _build:intermediate",
9+
"build": "tsc -b && npm run _build:default && npm run _build:background && npm run _build:intermediate && npm run _build:settings && npm run _build:popup",
1010
"_build": "vite build",
1111
"_build:default": "VITE_CONFIG=default npm run _build",
1212
"_build:background": "VITE_CONFIG=background npm run _build",
1313
"_build:intermediate": "VITE_CONFIG=intermediate npm run _build",
14+
"_build:settings": "VITE_CONFIG=settings npm run _build",
15+
"_build:popup": "VITE_CONFIG=popup npm run _build",
1416
"lint": "eslint .",
1517
"format": "prettier --write .",
1618
"build:local:chrome": "bash -c 'source ./scripts/build-local-chrome.sh'",
235 KB
Loading

webapp/_webapp/public/popup.html

Lines changed: 4 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -3,112 +3,10 @@
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6-
<title>PaperDebugger</title>
7-
<link href="https://fonts.googleapis.com/css2?family=Exo+2:wght@600&display=swap" rel="stylesheet" />
8-
<style>
9-
html,
10-
body {
11-
height: 100%;
12-
margin: 0;
13-
padding: 0;
14-
}
15-
body {
16-
padding: 1rem;
17-
padding-left: 1.5rem;
18-
min-width: 280px;
19-
max-width: 340px;
20-
min-height: 220px;
21-
background: #fafafa;
22-
color: #222;
23-
font-family:
24-
"Exo 2",
25-
-apple-system,
26-
BlinkMacSystemFont,
27-
"Segoe UI",
28-
Roboto,
29-
"Helvetica Neue",
30-
Arial,
31-
sans-serif;
32-
border: 1px solid #ececec;
33-
box-sizing: border-box;
34-
display: flex;
35-
flex-direction: column;
36-
justify-content: space-between;
37-
height: 100%;
38-
}
39-
.title {
40-
font-size: 1.5rem;
41-
font-weight: 600;
42-
letter-spacing: 0.02em;
43-
text-align: left;
44-
font-family: "Exo 2", sans-serif;
45-
}
46-
.subtitle {
47-
font-size: 1rem;
48-
font-weight: 600;
49-
letter-spacing: 0.02em;
50-
text-align: left;
51-
font-family: "Exo 2", sans-serif;
52-
}
53-
.steps {
54-
margin: 0.5rem 0 0 0;
55-
padding: 0 0rem;
56-
flex: 1;
57-
}
58-
.step {
59-
font-size: 0.9rem;
60-
font-weight: 400;
61-
margin-bottom: 0.7rem;
62-
line-height: 1.5;
63-
border-left: 3px solid #e0e0e0;
64-
padding-left: 0.7em;
65-
background: none;
66-
border-radius: 0;
67-
}
68-
.step strong {
69-
color: #3b82f6;
70-
font-weight: 600;
71-
margin-right: 0.3em;
72-
}
73-
.footer {
74-
font-size: 0.75rem;
75-
text-align: right;
76-
font-weight: 100;
77-
color: #888;
78-
padding: 0.7rem 1.2rem 1.1rem 0;
79-
border-top: 1px solid #ececec;
80-
background: none;
81-
border-radius: 0;
82-
letter-spacing: 0.01em;
83-
}
84-
.highlight {
85-
color: #3b82f6;
86-
font-weight: 600;
87-
}
88-
.noselect {
89-
-webkit-user-select: none;
90-
-moz-user-select: none;
91-
-ms-user-select: none;
92-
user-select: none;
93-
}
94-
.nodrag {
95-
-webkit-user-drag: none;
96-
-webkit-user-select: none;
97-
-webkit-user-select: none;
98-
}
99-
</style>
6+
<title>PaperDebugger Popup</title>
1007
</head>
101-
<body oncontextmenu="return false" class="nodrag">
102-
<div oncontextmenu="return false" class="title noselect">PaperDebugger</div>
103-
<div class="subtitle noselect">How to use</div>
104-
<div class="steps noselect">
105-
<div class="step">
106-
<strong>1.</strong>In
107-
<a class="highlight nodrag" href="https://overleaf.com/project" target="_blank">overleaf.com</a>, open any of
108-
your projects.
109-
</div>
110-
<div class="step"><strong>2.</strong>PaperDebugger is in the <b>top left</b> of the project page.</div>
111-
</div>
112-
<div class="footer noselect">Happy writing!</div>
8+
<body>
9+
<div id="root"></div>
10+
<script type="module" src="popup.js"></script>
11311
</body>
11412
</html>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>PaperDebugger Settings</title>
7+
</head>
8+
<body>
9+
<div id="root"></div>
10+
<script type="module" src="settings.js"></script>
11+
</body>
12+
</html>

webapp/_webapp/src/background.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import { getAllCookies } from "./libs/browser";
1818
import { HANDLER_NAMES } from "./shared/constants";
1919
import { blobToBase64 } from "./libs/helpers";
20+
import { registerContentScripts } from "./libs/permissions";
2021

2122
export type Handler<A, T> = {
2223
name: string;
@@ -78,12 +79,50 @@ export const fetchImageHandler: Handler<string, string> = {
7879
},
7980
};
8081

82+
const registerContentScriptsIfPermitted = async () => {
83+
try {
84+
const { origins = [] } = await chrome.permissions.getAll();
85+
if (!origins.length) {
86+
console.log("[PaperDebugger] No origins found, skipping content script registration");
87+
return;
88+
}
89+
await registerContentScripts(origins);
90+
} catch (error) {
91+
console.error("[PaperDebugger] Unable to register content scripts", error);
92+
}
93+
};
94+
95+
export const requestHostPermissionHandler: Handler<string, boolean> = {
96+
name: HANDLER_NAMES.REQUEST_HOST_PERMISSION,
97+
handler: async (origin, sendResponse) => {
98+
const granted = await chrome.permissions.request({ origins: [origin] });
99+
if (granted) {
100+
// chrome.permissions.request requires a user gesture context, the requestHostPermissionHandler is in the background script
101+
// and called via async messaging from the settings page.
102+
// Here we must register content scripts because when a message is sent through chrome.runtime.sendMessage,
103+
// the user gesture context is not preserved in the background script handler,
104+
// causing the permission request to fail with "This function must be called during a user gesture."
105+
// The permission request needs to be called directly from the settings page where the user click occurs,
106+
// not delegated to the background script.
107+
// Therefore, we must register content scripts here.
108+
await registerContentScriptsIfPermitted();
109+
}
110+
sendResponse(granted);
111+
},
112+
};
113+
81114
// @ts-expect-error: browser may not be defined in all environments
82115
const browserAPI = typeof browser !== "undefined" ? browser : chrome;
83116

84117
browserAPI.runtime?.onMessage?.addListener(
85118
(request: { action: string; args: unknown }, _: unknown, sendResponse: (response: unknown) => void) => {
86-
const handlers = [getCookiesHandler, getUrlHandler, getOrCreateSessionIdHandler, fetchImageHandler];
119+
const handlers = [
120+
getCookiesHandler,
121+
getUrlHandler,
122+
getOrCreateSessionIdHandler,
123+
fetchImageHandler,
124+
requestHostPermissionHandler,
125+
];
87126

88127
const handler = handlers.find((h) => h.name === request.action) as HandlerAny;
89128
if (!handler) {
@@ -100,3 +139,5 @@ browserAPI.runtime?.onMessage?.addListener(
100139
return true;
101140
},
102141
);
142+
143+
registerContentScriptsIfPermitted();

webapp/_webapp/src/intermediate.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,4 @@ export { getCookies };
104104
export const getUrl = makeFunction<string, string>(HANDLER_NAMES.GET_URL);
105105
export const getOrCreateSessionId = makeFunction<void, string>(HANDLER_NAMES.GET_OR_CREATE_SESSION_ID);
106106
export const fetchImage = makeFunction<string, string>(HANDLER_NAMES.FETCH_IMAGE);
107+
export const requestHostPermission = makeFunction<string, boolean>(HANDLER_NAMES.REQUEST_HOST_PERMISSION);

webapp/_webapp/src/libs/manifest.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ export function getManifest() {
1010
// This is the version on github tag.
1111
manifestJSON.version = semver.clean(version || "") || "0.0.0";
1212

13+
// @ts-expect-error we don't use this variable permissions_explanation
14+
delete manifestJSON.permissions_explanation;
15+
1316
if (betaBuild === "true") {
1417
manifestJSON.version_name = `v${manifestJSON.version}-${monorepoRevision}-beta`;
1518
manifestJSON.name = "PaperDebugger BETA";
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// can not running in content_script. registerContentScripts can only be called in service_worker.
2+
export async function registerContentScripts(origins?: string[]) {
3+
try {
4+
const resolvedOrigins = origins ?? (await chrome.permissions.getAll()).origins ?? [];
5+
if (resolvedOrigins.length === 0) {
6+
console.log("[PaperDebugger] No origins found, skipping content script registration");
7+
return;
8+
}
9+
10+
const scriptIds = (await chrome.scripting.getRegisteredContentScripts()).map((script) => script.id);
11+
if (scriptIds.length > 0) {
12+
console.log("[PaperDebugger] Unregistering dynamic content scripts", scriptIds);
13+
await chrome.scripting.unregisterContentScripts({ ids: scriptIds });
14+
}
15+
16+
await chrome.scripting.registerContentScripts([
17+
{
18+
id: "content-script-main",
19+
js: ["paperdebugger.js"],
20+
persistAcrossSessions: true,
21+
matches: resolvedOrigins,
22+
world: "MAIN",
23+
},
24+
{
25+
id: "content-script-intermediate",
26+
js: ["intermediate.js"],
27+
persistAcrossSessions: true,
28+
matches: resolvedOrigins,
29+
runAt: "document_start",
30+
},
31+
]);
32+
33+
console.log("[PaperDebugger] Registration complete", resolvedOrigins);
34+
} catch (error) {
35+
console.error("[PaperDebugger] Failed to register content scripts", error);
36+
}
37+
}

webapp/_webapp/src/main.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ export const Main = () => {
156156
);
157157
};
158158

159+
console.log("[PaperDebugger] PaperDebugger injected, find toolbar-left or ide-redesign-toolbar-menu-bar to add button");
160+
159161
if (!import.meta.env.DEV) {
160162
onElementAppeared(".toolbar-left .toolbar-item, .ide-redesign-toolbar-menu-bar", () => {
161163
logInfo("initializing");

webapp/_webapp/src/manifest.json

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,20 @@
1111
"48": "images/logo-1024.png"
1212
},
1313
"host_permissions": ["*://*.overleaf.com/"],
14-
"permissions": ["cookies", "storage"],
14+
"optional_host_permissions": ["*://*/*"],
15+
"permissions_explanation": "The optional_host_permissions pattern '*://*/*' allows the extension to request access to any website. This is necessary to support self-hosted Overleaf instances and similar use cases. Users will be prompted to grant access only when needed. Please review the extension documentation for details on security and privacy implications.",
16+
"permissions": ["cookies", "storage", "scripting", "activeTab"],
17+
"options_page": "settings.html",
1518
"action": {
1619
"default_popup": "popup.html"
1720
},
1821
"background": {
1922
"service_worker": "background.js"
2023
},
21-
"content_scripts": [
22-
{
23-
"js": ["paperdebugger.js"],
24-
"matches": ["https://*.overleaf.com/project/*"],
25-
"world": "MAIN"
26-
},
27-
{
28-
"js": ["intermediate.js"],
29-
"matches": ["https://*.overleaf.com/project/*"],
30-
"run_at": "document_start"
31-
}
32-
],
3324
"web_accessible_resources": [
3425
{
3526
"resources": ["images/*"],
36-
"matches": ["https://*.overleaf.com/*"]
27+
"matches": ["*://*/*"]
3728
}
3829
],
3930
"key": "[AUTO-GENERATED]"

0 commit comments

Comments
 (0)