Browser-Plugin: Multi-User/X-API-Key + Footer-Download
Plugin (Manifest V3) refactored für gehostete Multi-User-Instanz: - Tracker-URL hardcoded https://nextjobs.cc (keine URL-Eingabe mehr) - Authentifizierung ausschließlich über persönlichen REST-API-Key (Header X-API-Key), pro Benutzer in chrome.storage.sync - Server: /api/indeed-import nutzt neuen requireApiKey-Middleware, ordnet Import dem Benutzerkonto mit passendem cfg:API_TOKEN zu Download: /plugin/nextjobs-stellen-import.zip packt extension/ on the fly (dep-freier STORE-Zip in lib/zipdir.js, deterministischer Timestamp), route hinter requireAuth. Footer-Link im App-Footer. Dockerfile kopiert extension/ ins Image. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+27
-16
@@ -1,15 +1,18 @@
|
||||
// Service worker: performs the cross-origin POST to the NextJobs.
|
||||
// Routing the request through the background worker (instead of the content
|
||||
// script) keeps it independent of the Indeed page's Content-Security-Policy.
|
||||
// Service worker: performs the cross-origin POST to NextJobs.
|
||||
// Routing the request through the background worker (instead of a content
|
||||
// script) keeps it independent of the page's Content-Security-Policy.
|
||||
//
|
||||
// Die Tracker-Adresse ist fest (https://nextjobs.cc). Die Authentifizierung
|
||||
// läuft über den persönlichen REST-API-Key (Header X-API-Key), den jeder
|
||||
// Benutzer im Popup hinterlegt — der Server ordnet den Import damit dem
|
||||
// richtigen Benutzerkonto zu. Ohne Key lehnt der Server mit 401 ab.
|
||||
|
||||
const DEFAULT_TRACKER_URL = 'http://localhost:3000';
|
||||
const TRACKER_URL = 'https://nextjobs.cc';
|
||||
|
||||
function getTrackerUrl() {
|
||||
function getApiKey() {
|
||||
return new Promise((resolve) => {
|
||||
chrome.storage.sync.get({ trackerUrl: DEFAULT_TRACKER_URL }, (items) => {
|
||||
let url = (items.trackerUrl || DEFAULT_TRACKER_URL).trim();
|
||||
url = url.replace(/\/+$/, ''); // strip trailing slashes
|
||||
resolve(url);
|
||||
chrome.storage.sync.get({ apiKey: '' }, (items) => {
|
||||
resolve((items.apiKey || '').trim());
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -18,10 +21,14 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
if (message && message.type === 'IMPORT_JOB') {
|
||||
(async () => {
|
||||
try {
|
||||
const base = await getTrackerUrl();
|
||||
const res = await fetch(base + '/api/indeed-import', {
|
||||
const apiKey = await getApiKey();
|
||||
if (!apiKey) {
|
||||
sendResponse({ ok: false, needKey: true, error: 'Kein API-Key hinterlegt.' });
|
||||
return;
|
||||
}
|
||||
const res = await fetch(TRACKER_URL + '/api/indeed-import', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: { 'Content-Type': 'application/json', 'X-API-Key': apiKey },
|
||||
body: JSON.stringify(message.payload),
|
||||
});
|
||||
|
||||
@@ -39,6 +46,10 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (res.status === 401) {
|
||||
sendResponse({ ok: false, needKey: true, error: 'API-Key abgelehnt (401). Bitte in den Einstellungen prüfen.' });
|
||||
return;
|
||||
}
|
||||
sendResponse({
|
||||
ok: false,
|
||||
error: (data && data.error) || `Server antwortete mit ${res.status}`,
|
||||
@@ -50,17 +61,17 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
ok: true,
|
||||
id: data.id,
|
||||
message: data.message,
|
||||
openUrl: data.url ? base + data.url : null,
|
||||
openUrl: data.url ? TRACKER_URL + data.url : null,
|
||||
});
|
||||
} catch (err) {
|
||||
sendResponse({
|
||||
ok: false,
|
||||
error:
|
||||
'Konnte den NextJobs nicht erreichen. Läuft er, und ist die ' +
|
||||
'Adresse in den Erweiterungs-Einstellungen korrekt? (' + String(err.message) + ')',
|
||||
'Konnte NextJobs nicht erreichen (' + String(err.message) + '). ' +
|
||||
'Prüfe deine Internetverbindung und ob der API-Key in den Einstellungen korrekt ist.',
|
||||
});
|
||||
}
|
||||
})();
|
||||
return true; // keep the message channel open for the async response
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user