| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113 |
- // content/duck-mail.js — Content script for DuckDuckGo Email Protection autofill settings
- console.log('[MultiPage:duck-mail] Content script loaded on', location.href);
- chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
- if (message.type === 'FETCH_DUCK_EMAIL') {
- resetStopState();
- fetchDuckEmail(message.payload).then(result => {
- sendResponse(result);
- }).catch(err => {
- if (isStopError(err)) {
- log('Duck Mail: Stopped by user.', 'warn');
- sendResponse({ stopped: true, error: err.message });
- return;
- }
- sendResponse({ error: err.message });
- });
- return true;
- }
- if (message.type === 'EXTRACT_DUCK_TOKEN') {
- extractDuckToken().then(result => {
- sendResponse(result);
- }).catch(err => {
- sendResponse({ error: err.message });
- });
- return true;
- }
- });
- async function extractDuckToken() {
- log('Duck Mail: Extracting access token...');
- // The DuckDuckGo email app stores userData in a React state.
- // We can intercept it by reading the __NEXT_DATA__ or by calling their API
- // with cookies that the browser already has.
- // Strategy: call the dashboard API endpoint which returns user info including token.
- // Strategy 1: Try to find token in page's fetch calls by hooking into the app state
- // The app calls quack.duckduckgo.com/api/email/dashboard with credentials
- try {
- const resp = await fetch('https://quack.duckduckgo.com/api/email/dashboard', {
- credentials: 'include',
- });
- if (resp.ok) {
- const data = await resp.json();
- // Response has { user: { access_token, username, ... } }
- const token = data?.user?.access_token;
- const username = data?.user?.username;
- if (token) {
- log(`Duck Mail: Token extracted for ${username}`, 'ok');
- return { token, username };
- }
- }
- } catch (e) {
- log(`Duck Mail: Dashboard API failed: ${e.message}`, 'warn');
- }
- throw new Error('Could not extract DuckDuckGo access token. Make sure you are logged in.');
- }
- async function fetchDuckEmail(payload = {}) {
- const { generateNew = true } = payload;
- log(`Duck Mail: ${generateNew ? 'Generating' : 'Reading'} private address...`);
- await waitForElement(
- 'input.AutofillSettingsPanel__PrivateDuckAddressValue, button.AutofillSettingsPanel__GeneratorButton',
- 15000
- );
- const getAddressInput = () => document.querySelector('input.AutofillSettingsPanel__PrivateDuckAddressValue');
- const getGeneratorButton = () => document.querySelector('button.AutofillSettingsPanel__GeneratorButton')
- || Array.from(document.querySelectorAll('button')).find(btn => /generate private duck address/i.test(btn.textContent || ''));
- const readEmail = () => {
- const value = getAddressInput()?.value?.trim() || '';
- return value.includes('@duck.com') ? value : '';
- };
- const waitForEmailValue = async (previousValue = '') => {
- for (let i = 0; i < 100; i++) {
- const nextValue = readEmail();
- if (nextValue && nextValue !== previousValue) {
- return nextValue;
- }
- await sleep(150);
- }
- throw new Error('Timed out waiting for Duck address to appear.');
- };
- const currentEmail = readEmail();
- if (currentEmail && !generateNew) {
- log(`Duck Mail: Found existing address ${currentEmail}`);
- return { email: currentEmail, generated: false };
- }
- await humanPause(500, 1300);
- const generatorButton = getGeneratorButton();
- if (!generatorButton) {
- if (currentEmail) {
- log(`Duck Mail: Reusing existing address ${currentEmail}`, 'warn');
- return { email: currentEmail, generated: false };
- }
- throw new Error('Could not find "Generate Private Duck Address" button.');
- }
- generatorButton.click();
- log('Duck Mail: Clicked "Generate Private Duck Address"');
- const nextEmail = await waitForEmailValue(currentEmail);
- log(`Duck Mail: Ready address ${nextEmail}`, 'ok');
- return { email: nextEmail, generated: true };
- }
|