Goal
Build a Chrome Extension that acts as a secure bridge between the user's LinkedIn browser session and the sync service. This is the recommended auth approach — zero detection risk because the user logs in normally.
Why Chrome Extension?
| Approach |
Detection |
2FA |
Auto-refresh |
| Chrome Extension |
❌ none |
✅ user handles |
✅ automatic |
| Playwright login |
⚠️ medium |
❌ blocks |
❌ manual |
| Manual cookie paste |
❌ none |
✅ user handles |
❌ manual |
Architecture
chrome-extension/
├── manifest.json # MV3, permissions: cookies, webRequest, storage
├── background.js # service worker
│ ├── cookie watcher # chrome.cookies.onChanged → POST /accounts/refresh
│ ├── header interceptor # chrome.webRequest → capture x-li-track, csrf-token
│ └── sync trigger # manual button → POST /sync
├── popup.html # UI: status, account, sync button, service URL config
└── popup.js
manifest.json (key parts)
{
"manifest_version": 3,
"permissions": ["cookies", "storage", "webRequest"],
"host_permissions": [
"https://www.linkedin.com/*",
"http://localhost:8899/*"
],
"background": { "service_worker": "background.js" }
}
Cookie auto-refresh flow
// background.js
chrome.cookies.onChanged.addListener(({ cookie, removed }) => {
if (cookie.domain.includes("linkedin.com") && cookie.name === "li_at" && !removed) {
// Get JSESSIONID too
chrome.cookies.get({ url: "https://www.linkedin.com", name: "JSESSIONID" }, (jsession) => {
fetch(`${SERVICE_URL}/accounts/refresh`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
account_id: storedAccountId,
li_at: cookie.value,
jsessionid: jsession?.value || null
})
});
});
}
});
Header capture (x-li-track, csrf-token)
chrome.webRequest.onSendHeaders.addListener(
(details) => {
const track = details.requestHeaders.find(h => h.name === "x-li-track");
const csrf = details.requestHeaders.find(h => h.name === "csrf-token");
if (track || csrf) {
// store for provider use
chrome.storage.local.set({ xLiTrack: track?.value, csrfToken: csrf?.value });
}
},
{ urls: ["https://www.linkedin.com/voyager/api/*"] },
["requestHeaders"]
);
Service-side: POST /accounts/refresh
See issue #8 — this endpoint must exist for the extension to push updated cookies.
Acceptance criteria
- User installs extension, opens LinkedIn (normal login)
- Extension captures cookies and registers account via POST /accounts
- When li_at changes → auto-calls POST /accounts/refresh
- Popup shows sync status + button to trigger manual sync
Goal
Build a Chrome Extension that acts as a secure bridge between the user's LinkedIn browser session and the sync service. This is the recommended auth approach — zero detection risk because the user logs in normally.
Why Chrome Extension?
Architecture
manifest.json (key parts)
{ "manifest_version": 3, "permissions": ["cookies", "storage", "webRequest"], "host_permissions": [ "https://www.linkedin.com/*", "http://localhost:8899/*" ], "background": { "service_worker": "background.js" } }Cookie auto-refresh flow
Header capture (x-li-track, csrf-token)
Service-side: POST /accounts/refresh
See issue #8 — this endpoint must exist for the extension to push updated cookies.
Acceptance criteria