Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2x 2x 2x 2x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 4x 4x 4x 4x 4x 4x 4x 4x 3x 3x 3x 3x 3x 3x 2x 1x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 4x 1x 1x 1x 1x 3x 1x 1x 1x 1x 1x 1x 1x 1x 3x 1x 1x 3x 3x 3x 1x 1x 2x 2x 3x 1x 1x 1x 1x 4x 4x 1x 4x 4x | import {
analyzeJobPage,
analyzePastedText,
analyzeScopedRegion,
cancelAnalysis,
handleActionClick,
resolveActiveTab,
trackContentTabs,
} from "../services/jobFlow";
import { readLiveSelection } from "../services/selectionService";
import { MessageType } from "../types/messages";
import type { ActiveTabResponse, ExtensionMessage } from "../types/messages";
const ANALYZE_SELECTION_MENU_ID = "analyze-selection";
declare const WXT_AZURE_FUNCTION_URL: string;
export default defineBackground({
main() {
console.log("[background] service URL configured:", !!WXT_AZURE_FUNCTION_URL);
// Do NOT use setPanelBehavior({ openPanelOnActionClick: true }): panels
// opened that way never receive the activeTab grant (crbug.com/40926394),
// so page extraction would always fail. Instead the click lands on
// action.onClicked — which does grant activeTab — and the panel is opened
// here, synchronously within the user gesture as sidePanel.open requires.
chrome.action.onClicked.addListener((tab) => {
if (tab.windowId !== undefined) {
void chrome.sidePanel.open({ windowId: tab.windowId });
}
handleActionClick(tab, broadcastToSidePanel);
});
trackContentTabs(broadcastToSidePanel);
// "Analyze selection" (spec FR-023a): works without the panel already
// open, because the contextMenus onClicked handler carries its own
// activeTab grant — unlike everything else here, which depends on the
// toolbar-click grant.
chrome.contextMenus.create({
id: ANALYZE_SELECTION_MENU_ID,
title: "Analyze selection",
contexts: ["selection"],
});
chrome.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId !== ANALYZE_SELECTION_MENU_ID || tab?.id === undefined) return;
if (tab.windowId !== undefined) {
void chrome.sidePanel.open({ windowId: tab.windowId });
}
void (async () => {
const tabId = tab.id as number;
const selection = await readLiveSelection(tabId);
if (!selection.ok) {
// FR-036a: cause-specific wording, not one generic message — an
// empty selection is fixed by selecting again; a shadow-root or
// cross-origin-iframe selection never can be, so it points at the
// one path that does work instead (paste).
const action =
selection.cause === "unscopeable"
? "Try a different part of the page, or paste the text instead."
: "Select the posting text on the page, then try again.";
broadcastToSidePanel({
type: MessageType.JOB_ANALYSIS_ERROR,
error: {
code: "thin-content",
message: selection.reason,
action,
retryable: false,
},
fallback: null,
canonicalUrl: null,
sourceUrl: null,
});
return;
}
await analyzeScopedRegion(
{
type: MessageType.ANALYZE_SCOPED_REGION,
tabId,
text: selection.text,
selector: selection.selector,
mechanism: "selection",
},
broadcastToSidePanel
);
})();
});
chrome.runtime.onMessage.addListener(
(message: ExtensionMessage, sender, sendResponse) => {
if (message.type === MessageType.GET_ACTIVE_TAB) {
// sender.tab is set when the panel page runs as a regular tab; its
// URL is not readable without the "tabs" permission, so the id is
// the only reliable way to keep the panel from analyzing itself.
void resolveActiveTab(sender.tab?.id ?? null).then(
(response: ActiveTabResponse) => sendResponse(response)
);
return true; // async response
}
if (message.type === MessageType.ANALYZE_JOB_PAGE) {
void analyzeJobPage(message, broadcastToSidePanel);
}
if (message.type === MessageType.CANCEL_ANALYSIS) {
cancelAnalysis(message);
}
if (message.type === MessageType.ANALYZE_PASTED_TEXT) {
void analyzePastedText(message, broadcastToSidePanel);
}
if (message.type === MessageType.ANALYZE_SCOPED_REGION) {
void analyzeScopedRegion(message, broadcastToSidePanel);
}
sendResponse({ received: true });
return true;
}
);
},
});
function broadcastToSidePanel(message: ExtensionMessage): void {
chrome.runtime.sendMessage(message).catch(() => {
// Side panel may not be open
});
}
|