first version of browser extension hireflow

This commit is contained in:
2026-05-04 14:37:21 +02:00
parent 613f0bda5f
commit e182f87cac
10 changed files with 724 additions and 0 deletions
@@ -0,0 +1,216 @@
const TRACKER_FILE = "content/tracker.js";
function storageGet(keys) {
return new Promise((resolve) => {
chrome.storage.local.get(keys, resolve);
});
}
function storageSet(obj) {
return new Promise((resolve) => {
chrome.storage.local.set(obj, resolve);
});
}
function storageRemove(keys) {
return new Promise((resolve) => {
chrome.storage.local.remove(keys, resolve);
});
}
function tabsQuery(queryInfo) {
return new Promise((resolve) => {
chrome.tabs.query(queryInfo, resolve);
});
}
function executeScript(details) {
return new Promise((resolve, reject) => {
chrome.scripting.executeScript(details, () => {
const err = chrome.runtime.lastError;
if (err) {
reject(new Error(err.message));
return;
}
resolve();
});
});
}
const els = {
record: document.getElementById("record"),
stop: document.getElementById("stop"),
exportSection: document.getElementById("export-section"),
exportJson: document.getElementById("export-json"),
copy: document.getElementById("copy"),
copyStatus: document.getElementById("copy-status"),
hint: document.getElementById("hint"),
navHint: document.getElementById("nav-hint"),
};
function isRestrictedUrl(url) {
if (!url) {
return true;
}
const u = url.toLowerCase();
return (
u.startsWith("chrome://") ||
u.startsWith("chrome-extension://") ||
u.startsWith("edge://") ||
u.startsWith("about:") ||
u.startsWith("moz-extension://") ||
u.startsWith("devtools:") ||
u.startsWith("view-source:")
);
}
async function getActiveTab() {
const tabs = await tabsQuery({ active: true, currentWindow: true });
return tabs[0];
}
async function injectTracker(tabId) {
await executeScript({
target: { tabId },
files: [TRACKER_FILE],
});
}
function sendToTab(tabId, message) {
return new Promise((resolve, reject) => {
chrome.tabs.sendMessage(tabId, message, (response) => {
const err = chrome.runtime.lastError;
if (err) {
reject(new Error(err.message));
return;
}
resolve(response);
});
});
}
async function clearRecordingStorage() {
await storageRemove(["recording", "recordingTabId"]);
}
function setRecordingUi(isRecording, showExport) {
els.record.classList.toggle("hidden", isRecording);
els.stop.classList.toggle("hidden", !isRecording);
els.stop.disabled = !isRecording;
els.navHint.classList.toggle("hidden", !isRecording);
els.exportSection.classList.toggle("hidden", !showExport);
}
async function syncStateFromTab() {
const tab = await getActiveTab();
const data = await storageGet(["recording", "recordingTabId"]);
if (!tab?.id) {
setRecordingUi(false, !!els.exportJson.value);
return;
}
if (!data.recording || data.recordingTabId !== tab.id) {
setRecordingUi(false, !!els.exportJson.value);
return;
}
if (isRestrictedUrl(tab.url)) {
await clearRecordingStorage();
setRecordingUi(false, !!els.exportJson.value);
return;
}
try {
await injectTracker(tab.id);
const status = await sendToTab(tab.id, { type: "GET_STATUS" });
if (status && status.recording) {
setRecordingUi(true, !!els.exportJson.value);
} else {
await clearRecordingStorage();
setRecordingUi(false, !!els.exportJson.value);
}
} catch {
await clearRecordingStorage();
setRecordingUi(false, !!els.exportJson.value);
}
}
els.record.addEventListener("click", async () => {
els.copyStatus.textContent = "";
const tab = await getActiveTab();
if (!tab?.id) {
return;
}
if (isRestrictedUrl(tab.url)) {
els.hint.textContent =
"Opnemen werkt niet op deze pagina (browser-interne URL). Open een normale website.";
return;
}
try {
await injectTracker(tab.id);
await sendToTab(tab.id, { type: "START_RECORDING" });
await storageSet({
recording: true,
recordingTabId: tab.id,
});
els.exportJson.value = "";
setRecordingUi(true, false);
els.hint.textContent =
"Interacties worden vastgelegd. Druk op Stop om de JSON te tonen.";
} catch (e) {
els.hint.textContent =
"Kon niet starten: ververs de pagina of controleer of de site scripts toestaat.";
console.error(e);
}
});
els.stop.addEventListener("click", async () => {
els.copyStatus.textContent = "";
const tab = await getActiveTab();
const data = await storageGet(["recordingTabId"]);
if (!tab?.id || tab.id !== data.recordingTabId) {
await clearRecordingStorage();
setRecordingUi(false, false);
els.hint.textContent = "Geen actieve opname op dit tabblad.";
return;
}
try {
await injectTracker(tab.id);
const response = await sendToTab(tab.id, {
type: "STOP_AND_GET_INTERACTIONS",
});
await clearRecordingStorage();
const interactions = response?.interactions ?? [];
els.exportJson.value = JSON.stringify(interactions, null, 2);
setRecordingUi(false, true);
els.hint.textContent =
"JSON hieronder. Gebruik Kopiëren of selecteer de tekst handmatig.";
} catch (e) {
await clearRecordingStorage();
setRecordingUi(false, false);
els.hint.textContent =
"Stoppen mislukt (pagina mogelijk vernieuwd). Start opnieuw met Opnemen.";
console.error(e);
}
});
els.copy.addEventListener("click", async () => {
const text = els.exportJson.value;
if (!text) {
return;
}
try {
await navigator.clipboard.writeText(text);
els.copyStatus.textContent = "Gekopieerd naar klembord.";
} catch {
els.exportJson.select();
els.copyStatus.textContent = "Selecteer Cmd/Ctrl+C om te kopiëren.";
}
});
syncStateFromTab();