generated-email-helpers.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. (function attachGeneratedEmailHelpers(root, factory) {
  2. root.MultiPageGeneratedEmailHelpers = factory();
  3. })(typeof self !== 'undefined' ? self : globalThis, function createGeneratedEmailHelpersModule() {
  4. function createGeneratedEmailHelpers(deps = {}) {
  5. const {
  6. addLog,
  7. buildCloudflareTempEmailHeaders,
  8. CLOUDFLARE_TEMP_EMAIL_GENERATOR,
  9. DUCK_AUTOFILL_URL,
  10. fetch,
  11. fetchIcloudHideMyEmail,
  12. getCloudflareTempEmailAddressFromResponse,
  13. getCloudflareTempEmailConfig,
  14. getState,
  15. joinCloudflareTempEmailUrl,
  16. normalizeCloudflareDomain,
  17. normalizeCloudflareTempEmailAddress,
  18. normalizeEmailGenerator,
  19. reuseOrCreateTab,
  20. sendToContentScript,
  21. setEmailState,
  22. throwIfStopped,
  23. } = deps;
  24. function generateCloudflareAliasLocalPart() {
  25. const letters = 'abcdefghijklmnopqrstuvwxyz';
  26. const digits = '0123456789';
  27. const chars = [];
  28. for (let i = 0; i < 6; i++) {
  29. chars.push(letters[Math.floor(Math.random() * letters.length)]);
  30. }
  31. for (let i = 0; i < 4; i++) {
  32. chars.push(digits[Math.floor(Math.random() * digits.length)]);
  33. }
  34. for (let i = chars.length - 1; i > 0; i--) {
  35. const j = Math.floor(Math.random() * (i + 1));
  36. [chars[i], chars[j]] = [chars[j], chars[i]];
  37. }
  38. return chars.join('');
  39. }
  40. async function fetchCloudflareEmail(state, options = {}) {
  41. throwIfStopped();
  42. const latestState = state || await getState();
  43. const domain = normalizeCloudflareDomain(latestState.cloudflareDomain);
  44. if (!domain) {
  45. throw new Error('Cloudflare 域名为空或格式无效。');
  46. }
  47. const localPart = String(options.localPart || '').trim().toLowerCase() || generateCloudflareAliasLocalPart();
  48. const aliasEmail = `${localPart}@${domain}`;
  49. await setEmailState(aliasEmail);
  50. await addLog(`Cloudflare 邮箱:已生成 ${aliasEmail}`, 'ok');
  51. return aliasEmail;
  52. }
  53. function ensureCloudflareTempEmailConfig(state, options = {}) {
  54. const {
  55. requireAdminAuth = false,
  56. requireDomain = false,
  57. } = options;
  58. const config = getCloudflareTempEmailConfig(state);
  59. if (!config.baseUrl) {
  60. throw new Error('Cloudflare Temp Email 服务地址为空或格式无效。');
  61. }
  62. if (requireAdminAuth && !config.adminAuth) {
  63. throw new Error('Cloudflare Temp Email 缺少 Admin Auth。');
  64. }
  65. if (requireDomain && !config.domain) {
  66. throw new Error('Cloudflare Temp Email 域名为空或格式无效。');
  67. }
  68. return config;
  69. }
  70. async function requestCloudflareTempEmailJson(config, path, options = {}) {
  71. const {
  72. method = 'GET',
  73. payload,
  74. searchParams,
  75. timeoutMs = 20000,
  76. } = options;
  77. const url = new URL(joinCloudflareTempEmailUrl(config.baseUrl, path));
  78. if (searchParams && typeof searchParams === 'object') {
  79. for (const [key, value] of Object.entries(searchParams)) {
  80. if (value === undefined || value === null || value === '') continue;
  81. url.searchParams.set(key, String(value));
  82. }
  83. }
  84. const controller = new AbortController();
  85. const timeoutId = setTimeout(() => controller.abort(new Error('timeout')), timeoutMs);
  86. let response;
  87. try {
  88. response = await fetch(url.toString(), {
  89. method,
  90. headers: buildCloudflareTempEmailHeaders(config, {
  91. json: payload !== undefined,
  92. }),
  93. body: payload !== undefined ? JSON.stringify(payload) : undefined,
  94. signal: controller.signal,
  95. });
  96. } catch (err) {
  97. const errorMessage = err?.name === 'AbortError'
  98. ? `Cloudflare Temp Email 请求超时(>${Math.round(timeoutMs / 1000)} 秒)`
  99. : `Cloudflare Temp Email 请求失败:${err.message}`;
  100. throw new Error(errorMessage);
  101. } finally {
  102. clearTimeout(timeoutId);
  103. }
  104. const text = await response.text();
  105. let parsed;
  106. try {
  107. parsed = text ? JSON.parse(text) : {};
  108. } catch {
  109. parsed = text;
  110. }
  111. if (!response.ok) {
  112. const payloadError = typeof parsed === 'object' && parsed
  113. ? (parsed.message || parsed.error || parsed.msg)
  114. : '';
  115. throw new Error(`Cloudflare Temp Email 请求失败:${payloadError || text || `HTTP ${response.status}`}`);
  116. }
  117. return parsed;
  118. }
  119. async function fetchCloudflareTempEmailAddress(state, options = {}) {
  120. throwIfStopped();
  121. const latestState = state || await getState();
  122. const config = ensureCloudflareTempEmailConfig(latestState, {
  123. requireAdminAuth: true,
  124. requireDomain: true,
  125. });
  126. const requestedName = String(options.localPart || options.name || '').trim().toLowerCase() || generateCloudflareAliasLocalPart();
  127. const payload = {
  128. enablePrefix: true,
  129. name: requestedName,
  130. domain: config.domain,
  131. };
  132. const result = await requestCloudflareTempEmailJson(config, '/admin/new_address', {
  133. method: 'POST',
  134. payload,
  135. });
  136. const address = normalizeCloudflareTempEmailAddress(getCloudflareTempEmailAddressFromResponse(result));
  137. if (!address) {
  138. throw new Error('Cloudflare Temp Email 未返回可用邮箱地址。');
  139. }
  140. await setEmailState(address);
  141. await addLog(`Cloudflare Temp Email:已生成 ${address}`, 'ok');
  142. return address;
  143. }
  144. async function fetchDuckEmail(options = {}) {
  145. throwIfStopped();
  146. const { generateNew = true } = options;
  147. await addLog(`Duck 邮箱:正在打开自动填充设置(${generateNew ? '生成新地址' : '复用当前地址'})...`);
  148. await reuseOrCreateTab('duck-mail', DUCK_AUTOFILL_URL);
  149. const result = await sendToContentScript('duck-mail', {
  150. type: 'FETCH_DUCK_EMAIL',
  151. source: 'background',
  152. payload: { generateNew },
  153. });
  154. if (result?.error) {
  155. throw new Error(result.error);
  156. }
  157. if (!result?.email) {
  158. throw new Error('未返回 Duck 邮箱地址。');
  159. }
  160. await setEmailState(result.email);
  161. await addLog(`Duck 邮箱:${result.generated ? '已生成' : '已读取'} ${result.email}`, 'ok');
  162. return result.email;
  163. }
  164. async function fetchGeneratedEmail(state, options = {}) {
  165. const currentState = state || await getState();
  166. const generator = normalizeEmailGenerator(options.generator ?? currentState.emailGenerator);
  167. if (generator === 'custom') {
  168. throw new Error('当前邮箱生成方式为自定义邮箱,请直接填写注册邮箱。');
  169. }
  170. if (generator === 'icloud') {
  171. return fetchIcloudHideMyEmail();
  172. }
  173. if (generator === 'cloudflare') {
  174. return fetchCloudflareEmail(currentState, options);
  175. }
  176. if (generator === CLOUDFLARE_TEMP_EMAIL_GENERATOR) {
  177. return fetchCloudflareTempEmailAddress(currentState, options);
  178. }
  179. return fetchDuckEmail(options);
  180. }
  181. return {
  182. ensureCloudflareTempEmailConfig,
  183. fetchCloudflareEmail,
  184. fetchCloudflareTempEmailAddress,
  185. fetchDuckEmail,
  186. fetchGeneratedEmail,
  187. generateCloudflareAliasLocalPart,
  188. requestCloudflareTempEmailJson,
  189. };
  190. }
  191. return {
  192. createGeneratedEmailHelpers,
  193. };
  194. });