Compare commits

...

3 Commits

Author SHA1 Message Date
lebaudantoine 3d7f2957f9 wip support experimental calendar api 2026-05-01 18:43:37 +02:00
lebaudantoine d83d3ccc18 insert link in mail 2026-05-01 17:43:18 +02:00
lebaudantoine 52a9a583e8 wip initiate dev environment 2026-05-01 17:12:53 +02:00
8 changed files with 4388 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+17
View File
@@ -0,0 +1,17 @@
{
"name": "thunderbird",
"version": "1.0.0",
"main": "index.js",
"private": true,
"scripts": {
"dev": "web-ext run",
"lint": "web-ext lint --source-dir=./src"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"devDependencies": {
"web-ext": "^10.1.0"
}
}
+48
View File
@@ -0,0 +1,48 @@
import { buildMeetingMessage } from "./lib/meeting-message.js";
console.log("[meeting-link] background loaded at", new Date().toISOString());
(async () => {
try {
console.log("[meeting-link] browser.calendar exists?", !!browser.calendar);
if (browser.calendar) {
console.log("[meeting-link] calendar namespace keys:",
Object.keys(browser.calendar));
}
// Try the most basic read call — list configured calendars.
// The exact method name varies between experiment versions; we'll
// try the common one first.
if (browser.calendar.calendars?.query) {
const calendars = await browser.calendar.calendars.query({});
console.log("[meeting-link] calendars found:", calendars.length, calendars);
} else {
console.warn("[meeting-link] calendars.query not present — namespace shape:",
browser.calendar);
}
} catch (err) {
console.error("[meeting-link] calendar probe failed:", err);
}
})();
// Hardcoded for Spike 1. Spike 2 replaces this with a fetch() to your API.
const STUB_MEETING_DATA = {
url: "https://meet.example.com/m/abc-123-xyz",
telephony: {
phone_number: "+33123456789",
pin_code: "1234567890",
},
};
browser.runtime.onMessage.addListener(async (msg) => {
if (msg?.type === "GET_MEETING_MESSAGE") {
try {
const built = buildMeetingMessage(STUB_MEETING_DATA);
return { ok: true, ...built };
} catch (err) {
console.error("[meeting-link] build failed", err);
return { ok: false, error: String(err.message || err) };
}
}
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

+41
View File
@@ -0,0 +1,41 @@
{
"manifest_version": 3,
"name": "Meeting Link (dev)",
"version": "0.1.0",
"description": "Spike 0 — verifying the dev loop",
"browser_specific_settings": {
"gecko": {
"id": "meeting-link-dev@yourcompany.example",
"strict_min_version": "128.0"
}
},
"background": {
"scripts": ["background.js"],
"type": "module"
},
"compose_action": {
"default_title": "Insert meeting link",
"default_popup": "popup/popup.html",
"default_icon": "icons/icon-32.png"
},
"permissions": ["compose"],
"experiment_apis": {
"calendar_provider": {
"schema": "experiments/calendar/schema/calendar-provider.json",
"parent": {
"scopes": ["addon_parent"],
"paths": [["calendar", "provider"]],
"script": "experiments/calendar/parent/ext-calendar-provider.js",
"events": ["startup"]
}
},
"calendar_calendars": {
"schema": "experiments/calendar/schema/calendar-calendars.json",
"parent": {
"scopes": ["addon_parent"],
"paths": [["calendar", "calendars"]],
"script": "experiments/calendar/parent/ext-calendar-calendars.js"
}
}
}
}
@@ -0,0 +1,18 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Meeting link</title>
<style>
body { font: 13px system-ui; padding: 12px; min-width: 240px; margin: 0; }
button { font: inherit; padding: 6px 12px; cursor: pointer; }
#status { margin-top: 8px; color: #555; min-height: 1.2em; white-space: pre-wrap; }
#status.error { color: #b00020; }
</style>
</head>
<body>
<button id="insert">Insert meeting link</button>
<div id="status"></div>
<script src="popup.js"></script>
</body>
</html>
+65
View File
@@ -0,0 +1,65 @@
const insertBtn = document.getElementById("insert");
const statusEl = document.getElementById("status");
function setStatus(text, isError = false) {
statusEl.textContent = text;
statusEl.classList.toggle("error", isError);
}
insertBtn.addEventListener("click", async () => {
insertBtn.disabled = true;
setStatus("Generating link…");
try {
// 1. Find the compose tab this popup belongs to.
// A compose_action popup is anchored to a compose window, so the
// "active tab in the current window" is the compose tab itself.
const [composeTab] = await browser.tabs.query({
active: true,
currentWindow: true,
});
if (!composeTab) throw new Error("No compose tab found");
// 2. Read the current compose state — we need to know if we're
// in HTML mode or plain-text mode, and we need the existing body
// so we can append rather than overwrite.
const details = await browser.compose.getComposeDetails(composeTab.id);
// 3. Ask the background to produce the meeting message.
const reply = await browser.runtime.sendMessage({
type: "GET_MEETING_MESSAGE",
});
if (!reply?.ok) throw new Error(reply?.error || "Background error");
// 4. Append to the existing body in the right format.
if (details.isPlainText) {
const newBody = (details.plainTextBody || "") + "\n\n" + reply.text;
await browser.compose.setComposeDetails(composeTab.id, {
plainTextBody: newBody,
});
} else {
const newBody = appendHtmlBeforeBodyEnd(details.body || "", reply.html);
await browser.compose.setComposeDetails(composeTab.id, {
body: newBody,
});
}
setStatus("Inserted ✓");
setTimeout(() => window.close(), 600);
} catch (err) {
console.error("[meeting-link] popup insert failed", err);
setStatus("Failed: " + (err.message || err), true);
insertBtn.disabled = false;
}
});
/**
* Append HTML right before </body>, or fall back to concatenation if no
* </body> tag is present (Thunderbird's compose body is usually a full
* HTML document, but be defensive).
*/
function appendHtmlBeforeBodyEnd(currentHtml, fragment) {
const idx = currentHtml.toLowerCase().lastIndexOf("</body>");
if (idx === -1) return currentHtml + fragment;
return currentHtml.slice(0, idx) + fragment + currentHtml.slice(idx);
}
+30
View File
@@ -0,0 +1,30 @@
// web-ext-config.cjs
const os = require("os");
const path = require("path");
function thunderbirdBinary() {
if (process.env.WEB_EXT_FIREFOX) return process.env.WEB_EXT_FIREFOX;
switch (process.platform) {
case "darwin":
return "/Applications/Thunderbird Beta.app/Contents/MacOS/thunderbird";
case "linux":
return "/usr/bin/thunderbird"; // adjust to your install
case "win32":
return "C:\\Program Files\\Mozilla Thunderbird\\thunderbird.exe";
default:
throw new Error("Unsupported platform: " + process.platform);
}
}
module.exports = {
sourceDir: "./src",
artifactsDir: "./web-ext-artifacts",
run: {
firefox: thunderbirdBinary(),
firefoxProfile: path.join(os.homedir(), ".thunderbird-dev-profile"),
profileCreateIfMissing: true,
keepProfileChanges: true,
browserConsole: true,
},
ignoreFiles: ["package-lock.json", "web-ext-config.cjs", "*.md"],
};