duck-mail.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. // content/duck-mail.js — Content script for DuckDuckGo Email Protection autofill settings
  2. console.log('[MultiPage:duck-mail] Content script loaded on', location.href);
  3. chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  4. if (message.type === 'FETCH_DUCK_EMAIL') {
  5. resetStopState();
  6. fetchDuckEmail(message.payload).then(result => {
  7. sendResponse(result);
  8. }).catch(err => {
  9. if (isStopError(err)) {
  10. log('Duck Mail: Stopped by user.', 'warn');
  11. sendResponse({ stopped: true, error: err.message });
  12. return;
  13. }
  14. sendResponse({ error: err.message });
  15. });
  16. return true;
  17. }
  18. if (message.type === 'EXTRACT_DUCK_TOKEN') {
  19. extractDuckToken().then(result => {
  20. sendResponse(result);
  21. }).catch(err => {
  22. sendResponse({ error: err.message });
  23. });
  24. return true;
  25. }
  26. });
  27. async function extractDuckToken() {
  28. log('Duck Mail: Extracting access token...');
  29. // The DuckDuckGo email app stores userData in a React state.
  30. // We can intercept it by reading the __NEXT_DATA__ or by calling their API
  31. // with cookies that the browser already has.
  32. // Strategy: call the dashboard API endpoint which returns user info including token.
  33. // Strategy 1: Try to find token in page's fetch calls by hooking into the app state
  34. // The app calls quack.duckduckgo.com/api/email/dashboard with credentials
  35. try {
  36. const resp = await fetch('https://quack.duckduckgo.com/api/email/dashboard', {
  37. credentials: 'include',
  38. });
  39. if (resp.ok) {
  40. const data = await resp.json();
  41. // Response has { user: { access_token, username, ... } }
  42. const token = data?.user?.access_token;
  43. const username = data?.user?.username;
  44. if (token) {
  45. log(`Duck Mail: Token extracted for ${username}`, 'ok');
  46. return { token, username };
  47. }
  48. }
  49. } catch (e) {
  50. log(`Duck Mail: Dashboard API failed: ${e.message}`, 'warn');
  51. }
  52. throw new Error('Could not extract DuckDuckGo access token. Make sure you are logged in.');
  53. }
  54. async function fetchDuckEmail(payload = {}) {
  55. const { generateNew = true } = payload;
  56. log(`Duck Mail: ${generateNew ? 'Generating' : 'Reading'} private address...`);
  57. await waitForElement(
  58. 'input.AutofillSettingsPanel__PrivateDuckAddressValue, button.AutofillSettingsPanel__GeneratorButton',
  59. 15000
  60. );
  61. const getAddressInput = () => document.querySelector('input.AutofillSettingsPanel__PrivateDuckAddressValue');
  62. const getGeneratorButton = () => document.querySelector('button.AutofillSettingsPanel__GeneratorButton')
  63. || Array.from(document.querySelectorAll('button')).find(btn => /generate private duck address/i.test(btn.textContent || ''));
  64. const readEmail = () => {
  65. const value = getAddressInput()?.value?.trim() || '';
  66. return value.includes('@duck.com') ? value : '';
  67. };
  68. const waitForEmailValue = async (previousValue = '') => {
  69. for (let i = 0; i < 100; i++) {
  70. const nextValue = readEmail();
  71. if (nextValue && nextValue !== previousValue) {
  72. return nextValue;
  73. }
  74. await sleep(150);
  75. }
  76. throw new Error('Timed out waiting for Duck address to appear.');
  77. };
  78. const currentEmail = readEmail();
  79. if (currentEmail && !generateNew) {
  80. log(`Duck Mail: Found existing address ${currentEmail}`);
  81. return { email: currentEmail, generated: false };
  82. }
  83. await humanPause(500, 1300);
  84. const generatorButton = getGeneratorButton();
  85. if (!generatorButton) {
  86. if (currentEmail) {
  87. log(`Duck Mail: Reusing existing address ${currentEmail}`, 'warn');
  88. return { email: currentEmail, generated: false };
  89. }
  90. throw new Error('Could not find "Generate Private Duck Address" button.');
  91. }
  92. generatorButton.click();
  93. log('Duck Mail: Clicked "Generate Private Duck Address"');
  94. const nextEmail = await waitForEmailValue(currentEmail);
  95. log(`Duck Mail: Ready address ${nextEmail}`, 'ok');
  96. return { email: nextEmail, generated: true };
  97. }