@z_06/relay-temp-mail 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -2
- package/README.zh-CN.md +52 -2
- package/dist/index.cjs +190 -108
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +79 -4
- package/dist/index.d.ts +79 -4
- package/dist/index.js +188 -108
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
A modular TypeScript/JavaScript package for managing email aliases and retrieving temporary emails through pluggable providers.
|
|
6
6
|
|
|
7
|
-
Built on a provider architecture — combine any **alias provider** with any **mail provider** to fit your workflow. Currently ships with Firefox Relay and CloudFlare Temp Mail support
|
|
7
|
+
Built on a provider architecture — combine any **alias provider** with any **mail provider** to fit your workflow. Currently ships with Firefox Relay, DuckDuckGo Email Protection, and CloudFlare Temp Mail support.
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
11
|
- **Provider-based architecture** — mix and match alias + mail providers
|
|
12
12
|
- **Firefox Relay** — create, list, and delete email aliases
|
|
13
|
+
- **DuckDuckGo Email Protection** — create email aliases with local storage
|
|
13
14
|
- **CloudFlare Temp Mail** — retrieve and parse emails via API
|
|
14
15
|
- **TypeScript support** — full type definitions for all APIs, including provider interfaces
|
|
15
16
|
- **ESM + CommonJS support** — works with both module systems
|
|
@@ -60,7 +61,7 @@ The library uses two types of providers that can be combined independently:
|
|
|
60
61
|
|
|
61
62
|
| Provider Type | Interface | Current Implementations |
|
|
62
63
|
|---|---|---|
|
|
63
|
-
| **Alias Provider** | `AliasProvider` | `firefox-relay` |
|
|
64
|
+
| **Alias Provider** | `AliasProvider` | `firefox-relay`, `duckduckgo-email` |
|
|
64
65
|
| **Mail Provider** | `MailProvider` | `cf-temp-mail` |
|
|
65
66
|
|
|
66
67
|
### Alias Providers
|
|
@@ -87,6 +88,53 @@ Manages email aliases through [Firefox Relay](https://relay.firefox.com).
|
|
|
87
88
|
4. Find Cookies for `relay.firefox.com`
|
|
88
89
|
5. Copy the values for `csrftoken` and `sessionid`
|
|
89
90
|
|
|
91
|
+
#### `duckduckgo-email`
|
|
92
|
+
|
|
93
|
+
Manages email aliases through [DuckDuckGo Email Protection](https://duckduckgo.com/email/).
|
|
94
|
+
|
|
95
|
+
Since the DuckDuckGo API does not provide endpoints for listing or deleting aliases, this provider uses a local store. A default in-memory store is included; implement `DuckDuckGoAliasStore` for custom persistence (e.g., file-based, database).
|
|
96
|
+
|
|
97
|
+
**Configuration:**
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
{
|
|
101
|
+
type: 'duckduckgo-email',
|
|
102
|
+
jwtToken: string; // JWT token from DuckDuckGo Email Protection
|
|
103
|
+
store?: DuckDuckGoAliasStore; // Optional custom store (default: in-memory)
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Getting your JWT token:**
|
|
108
|
+
|
|
109
|
+
1. Visit [duckduckgo.com/email](https://duckduckgo.com/email/) and register an account
|
|
110
|
+
2. Open your browser's developer tools (F12)
|
|
111
|
+
3. Click "Generate New Address" in the DuckDuckGo Email UI
|
|
112
|
+
4. In the Network tab, find the request to `quack.duckduckgo.com`
|
|
113
|
+
5. Copy the Bearer token from the `Authorization` header
|
|
114
|
+
|
|
115
|
+
**Custom persistence:**
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import type { DuckDuckGoAliasStore, RelayAlias } from '@z_06/relay-temp-mail';
|
|
119
|
+
|
|
120
|
+
class MyFileStore implements DuckDuckGoAliasStore {
|
|
121
|
+
getAll(): RelayAlias[] { /* read from file */ }
|
|
122
|
+
add(alias: RelayAlias): void { /* append to file */ }
|
|
123
|
+
remove(id: number): void { /* remove from file */ }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const client = new TempMailClient({
|
|
127
|
+
aliasProvider: {
|
|
128
|
+
type: 'duckduckgo-email',
|
|
129
|
+
jwtToken: 'your-jwt-token',
|
|
130
|
+
store: new MyFileStore(),
|
|
131
|
+
},
|
|
132
|
+
mailProvider: { /* ... */ },
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Duplicate detection:** The DuckDuckGo API may occasionally return a previously-generated address with a 201 response. The provider detects this and throws a `RelayTempMailError` with code `DUPLICATE_ALIAS`.
|
|
137
|
+
|
|
90
138
|
### Mail Providers
|
|
91
139
|
|
|
92
140
|
#### `cf-temp-mail`
|
|
@@ -255,6 +303,8 @@ import type {
|
|
|
255
303
|
MailProvider,
|
|
256
304
|
TempMailConfig,
|
|
257
305
|
FirefoxRelayConfig,
|
|
306
|
+
DuckDuckGoEmailConfig,
|
|
307
|
+
DuckDuckGoAliasStore,
|
|
258
308
|
CFTempMailConfig,
|
|
259
309
|
RelayAlias,
|
|
260
310
|
Email,
|
package/README.zh-CN.md
CHANGED
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
一个模块化的 TypeScript/JavaScript 包,通过可插拔的 Provider 管理邮箱别名和接收临时邮件。
|
|
6
6
|
|
|
7
|
-
基于 Provider 架构 — 自由组合**别名提供商**与**邮件提供商**。当前内置 Firefox Relay
|
|
7
|
+
基于 Provider 架构 — 自由组合**别名提供商**与**邮件提供商**。当前内置 Firefox Relay、DuckDuckGo 邮件保护和 CloudFlare 临时邮箱支持。
|
|
8
8
|
|
|
9
9
|
## 功能特性
|
|
10
10
|
|
|
11
11
|
- **Provider 架构** — 自由组合别名与邮件提供商
|
|
12
12
|
- **Firefox Relay** — 创建、列出、删除邮箱别名
|
|
13
|
+
- **DuckDuckGo 邮件保护** — 创建邮箱别名,支持本地存储
|
|
13
14
|
- **CloudFlare 临时邮箱** — 通过 API 获取并解析邮件
|
|
14
15
|
- **TypeScript 支持** — 所有 API 均有完整类型定义,包括 Provider 接口
|
|
15
16
|
- **ESM + CommonJS 支持** — 兼容两种模块系统
|
|
@@ -60,7 +61,7 @@ const emails = await client.getEmails(alias.fullAddress, { limit: 10 });
|
|
|
60
61
|
|
|
61
62
|
| Provider 类型 | 接口 | 当前实现 |
|
|
62
63
|
|---|---|---|
|
|
63
|
-
| **别名提供商** | `AliasProvider` | `firefox-relay` |
|
|
64
|
+
| **别名提供商** | `AliasProvider` | `firefox-relay`, `duckduckgo-email` |
|
|
64
65
|
| **邮件提供商** | `MailProvider` | `cf-temp-mail` |
|
|
65
66
|
|
|
66
67
|
### 别名提供商
|
|
@@ -87,6 +88,53 @@ const emails = await client.getEmails(alias.fullAddress, { limit: 10 });
|
|
|
87
88
|
4. 找到 `relay.firefox.com` 的 Cookies
|
|
88
89
|
5. 复制 `csrftoken` 和 `sessionid` 的值
|
|
89
90
|
|
|
91
|
+
#### `duckduckgo-email`
|
|
92
|
+
|
|
93
|
+
通过 [DuckDuckGo 邮件保护](https://duckduckgo.com/email/) 管理邮箱别名。
|
|
94
|
+
|
|
95
|
+
由于 DuckDuckGo API 不提供列出或删除别名的接口,该提供商使用本地存储。内置内存存储;可通过实现 `DuckDuckGoAliasStore` 接口自定义持久化(如文件、数据库)。
|
|
96
|
+
|
|
97
|
+
**配置:**
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
{
|
|
101
|
+
type: 'duckduckgo-email',
|
|
102
|
+
jwtToken: string; // DuckDuckGo 邮件保护的 JWT token
|
|
103
|
+
store?: DuckDuckGoAliasStore; // 可选自定义存储(默认: 内存存储)
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**获取 JWT Token:**
|
|
108
|
+
|
|
109
|
+
1. 访问 [duckduckgo.com/email](https://duckduckgo.com/email/) 并注册账户
|
|
110
|
+
2. 打开浏览器开发者工具(F12)
|
|
111
|
+
3. 在 DuckDuckGo 邮件界面点击"生成新地址"
|
|
112
|
+
4. 在网络请求栏中找到发往 `quack.duckduckgo.com` 的请求
|
|
113
|
+
5. 从 `Authorization` 请求头中复制 Bearer token
|
|
114
|
+
|
|
115
|
+
**自定义持久化:**
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import type { DuckDuckGoAliasStore, RelayAlias } from '@z_06/relay-temp-mail';
|
|
119
|
+
|
|
120
|
+
class MyFileStore implements DuckDuckGoAliasStore {
|
|
121
|
+
getAll(): RelayAlias[] { /* 从文件读取 */ }
|
|
122
|
+
add(alias: RelayAlias): void { /* 追加到文件 */ }
|
|
123
|
+
remove(id: number): void { /* 从文件中删除 */ }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const client = new TempMailClient({
|
|
127
|
+
aliasProvider: {
|
|
128
|
+
type: 'duckduckgo-email',
|
|
129
|
+
jwtToken: 'your-jwt-token',
|
|
130
|
+
store: new MyFileStore(),
|
|
131
|
+
},
|
|
132
|
+
mailProvider: { /* ... */ },
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**重复检测:** DuckDuckGo API 偶尔会返回之前已生成过的地址(仍返回 201)。该提供商会检测这种情况并抛出 `RelayTempMailError`,错误代码为 `DUPLICATE_ALIAS`。
|
|
137
|
+
|
|
90
138
|
### 邮件提供商
|
|
91
139
|
|
|
92
140
|
#### `cf-temp-mail`
|
|
@@ -255,6 +303,8 @@ import type {
|
|
|
255
303
|
MailProvider,
|
|
256
304
|
TempMailConfig,
|
|
257
305
|
FirefoxRelayConfig,
|
|
306
|
+
DuckDuckGoEmailConfig,
|
|
307
|
+
DuckDuckGoAliasStore,
|
|
258
308
|
CFTempMailConfig,
|
|
259
309
|
RelayAlias,
|
|
260
310
|
Email,
|
package/dist/index.cjs
CHANGED
|
@@ -23,7 +23,9 @@ __export(index_exports, {
|
|
|
23
23
|
AuthError: () => AuthError,
|
|
24
24
|
CFEmailClient: () => CFEmailClient,
|
|
25
25
|
CFTempMailProvider: () => CFTempMailProvider,
|
|
26
|
+
DuckDuckGoEmailProvider: () => DuckDuckGoEmailProvider,
|
|
26
27
|
FirefoxRelayProvider: () => FirefoxRelayProvider,
|
|
28
|
+
InMemoryDuckDuckGoAliasStore: () => InMemoryDuckDuckGoAliasStore,
|
|
27
29
|
NetworkError: () => NetworkError,
|
|
28
30
|
NotFoundError: () => NotFoundError,
|
|
29
31
|
ParseError: () => ParseError,
|
|
@@ -167,114 +169,6 @@ var DefaultHttpClient = class {
|
|
|
167
169
|
}
|
|
168
170
|
};
|
|
169
171
|
|
|
170
|
-
// src/parser.ts
|
|
171
|
-
var EMAIL_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
|
|
172
|
-
var MOZMAIL_SUFFIX_PATTERN = /@mozmail\.com$/i;
|
|
173
|
-
var ENCODED_WORD_PATTERN = /=\?([^?]+)\?([BbQq])\?([^?]*)\?=/g;
|
|
174
|
-
var EmailParser = class {
|
|
175
|
-
parseEmail(raw) {
|
|
176
|
-
const headers = this.parseHeaders(raw);
|
|
177
|
-
const toHeader = headers.get("to") || "";
|
|
178
|
-
const fromHeader = headers.get("from") || "";
|
|
179
|
-
const messageIdHeader = headers.get("message-id") || "";
|
|
180
|
-
const relayAlias = this.extractRelayAlias(raw);
|
|
181
|
-
return {
|
|
182
|
-
id: 0,
|
|
183
|
-
messageId: this.extractMessageId(messageIdHeader),
|
|
184
|
-
source: this.extractEmailAddress(fromHeader),
|
|
185
|
-
address: this.extractEmailAddress(toHeader),
|
|
186
|
-
raw,
|
|
187
|
-
metadata: null,
|
|
188
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
189
|
-
relayAlias: relayAlias || void 0
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
extractRelayAlias(raw) {
|
|
193
|
-
try {
|
|
194
|
-
const headers = this.parseHeaders(raw);
|
|
195
|
-
const fromAddresses = this.extractHeaderEmails(headers.get("from"));
|
|
196
|
-
const toAddresses = this.extractHeaderEmails(headers.get("to"));
|
|
197
|
-
const allAddresses = [...fromAddresses, ...toAddresses];
|
|
198
|
-
const mozmailAddress = allAddresses.find(
|
|
199
|
-
(address) => MOZMAIL_SUFFIX_PATTERN.test(address)
|
|
200
|
-
);
|
|
201
|
-
if (mozmailAddress) return mozmailAddress;
|
|
202
|
-
return toAddresses[0] ?? null;
|
|
203
|
-
} catch {
|
|
204
|
-
return null;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
parseHeaders(raw) {
|
|
208
|
-
const headers = /* @__PURE__ */ new Map();
|
|
209
|
-
const headerEnd = raw.indexOf("\r\n\r\n");
|
|
210
|
-
const headerSection = headerEnd === -1 ? raw : raw.substring(0, headerEnd);
|
|
211
|
-
const lines = headerSection.split(/\r?\n/);
|
|
212
|
-
let currentHeader = null;
|
|
213
|
-
let currentValue = "";
|
|
214
|
-
for (const line of lines) {
|
|
215
|
-
if (/^\s/.test(line)) {
|
|
216
|
-
if (currentHeader) currentValue += " " + line.trim();
|
|
217
|
-
} else {
|
|
218
|
-
if (currentHeader) headers.set(currentHeader, currentValue);
|
|
219
|
-
const colonIndex = line.indexOf(":");
|
|
220
|
-
if (colonIndex > 0) {
|
|
221
|
-
currentHeader = line.substring(0, colonIndex).toLowerCase().trim();
|
|
222
|
-
currentValue = line.substring(colonIndex + 1).trim();
|
|
223
|
-
} else {
|
|
224
|
-
currentHeader = null;
|
|
225
|
-
currentValue = "";
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
if (currentHeader) headers.set(currentHeader, currentValue);
|
|
230
|
-
return headers;
|
|
231
|
-
}
|
|
232
|
-
decodeHeader(value) {
|
|
233
|
-
return value.replace(ENCODED_WORD_PATTERN, (_, charset, encoding, encoded) => {
|
|
234
|
-
try {
|
|
235
|
-
if (encoding.toUpperCase() === "Q") {
|
|
236
|
-
return this.decodeQuotedPrintable(encoded);
|
|
237
|
-
} else if (encoding.toUpperCase() === "B") {
|
|
238
|
-
return this.decodeBase64(encoded);
|
|
239
|
-
}
|
|
240
|
-
return encoded;
|
|
241
|
-
} catch {
|
|
242
|
-
return encoded;
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
decodeQuotedPrintable(encoded) {
|
|
247
|
-
let decoded = encoded.replace(/_/g, " ");
|
|
248
|
-
decoded = decoded.replace(
|
|
249
|
-
/=([0-9A-Fa-f]{2})/g,
|
|
250
|
-
(_, hex) => String.fromCharCode(parseInt(hex, 16))
|
|
251
|
-
);
|
|
252
|
-
return decoded;
|
|
253
|
-
}
|
|
254
|
-
decodeBase64(encoded) {
|
|
255
|
-
return Buffer.from(encoded, "base64").toString("utf-8");
|
|
256
|
-
}
|
|
257
|
-
extractEmailAddress(headerValue) {
|
|
258
|
-
if (!headerValue) return "";
|
|
259
|
-
const decoded = this.decodeHeader(headerValue);
|
|
260
|
-
const bracketMatch = decoded.match(/<([^>]+)>/);
|
|
261
|
-
if (bracketMatch) return bracketMatch[1].trim().toLowerCase();
|
|
262
|
-
const emailMatch = decoded.match(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/);
|
|
263
|
-
return emailMatch ? emailMatch[0].toLowerCase() : decoded.trim().toLowerCase();
|
|
264
|
-
}
|
|
265
|
-
extractHeaderEmails(headerValue) {
|
|
266
|
-
if (!headerValue) return [];
|
|
267
|
-
const decoded = this.decodeHeader(headerValue);
|
|
268
|
-
const matches = decoded.match(EMAIL_PATTERN);
|
|
269
|
-
return matches ? matches.map((match) => match.toLowerCase()) : [];
|
|
270
|
-
}
|
|
271
|
-
extractMessageId(headerValue) {
|
|
272
|
-
if (!headerValue) return `<generated-${Date.now()}@relay-temp-mail>`;
|
|
273
|
-
const cleaned = headerValue.replace(/[<>]/g, "").trim();
|
|
274
|
-
return `<${cleaned}>`;
|
|
275
|
-
}
|
|
276
|
-
};
|
|
277
|
-
|
|
278
172
|
// src/http.ts
|
|
279
173
|
var HttpClient = class {
|
|
280
174
|
/**
|
|
@@ -431,6 +325,187 @@ var HttpClient = class {
|
|
|
431
325
|
}
|
|
432
326
|
};
|
|
433
327
|
|
|
328
|
+
// src/duckduckgo-api.ts
|
|
329
|
+
var InMemoryDuckDuckGoAliasStore = class {
|
|
330
|
+
constructor() {
|
|
331
|
+
this.aliases = [];
|
|
332
|
+
}
|
|
333
|
+
getAll() {
|
|
334
|
+
return [...this.aliases];
|
|
335
|
+
}
|
|
336
|
+
add(alias) {
|
|
337
|
+
this.aliases.push(alias);
|
|
338
|
+
}
|
|
339
|
+
remove(id) {
|
|
340
|
+
this.aliases = this.aliases.filter((a) => a.id !== id);
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
var DuckDuckGoEmailProvider = class {
|
|
344
|
+
constructor(jwtToken, store, httpClient) {
|
|
345
|
+
this.jwtToken = jwtToken;
|
|
346
|
+
this.store = store ?? new InMemoryDuckDuckGoAliasStore();
|
|
347
|
+
this.httpClient = httpClient ?? new HttpClient("https://quack.duckduckgo.com");
|
|
348
|
+
this.nextId = 1;
|
|
349
|
+
}
|
|
350
|
+
async listAliases() {
|
|
351
|
+
return this.store.getAll();
|
|
352
|
+
}
|
|
353
|
+
async createAlias() {
|
|
354
|
+
const response = await this.httpClient.request(
|
|
355
|
+
"POST",
|
|
356
|
+
"/api/email/addresses",
|
|
357
|
+
{
|
|
358
|
+
headers: {
|
|
359
|
+
Authorization: `Bearer ${this.jwtToken}`
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
);
|
|
363
|
+
const fullAddress = `${response.address}@duck.com`;
|
|
364
|
+
const existing = await this.store.getAll();
|
|
365
|
+
if (existing.some((a) => a.fullAddress === fullAddress)) {
|
|
366
|
+
throw new RelayTempMailError(
|
|
367
|
+
`DuckDuckGo returned a duplicate alias: ${fullAddress}`,
|
|
368
|
+
"DUPLICATE_ALIAS",
|
|
369
|
+
201
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
const alias = {
|
|
373
|
+
id: this.nextId++,
|
|
374
|
+
address: response.address,
|
|
375
|
+
fullAddress,
|
|
376
|
+
enabled: true,
|
|
377
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
378
|
+
domain: 3,
|
|
379
|
+
maskType: "random"
|
|
380
|
+
};
|
|
381
|
+
await this.store.add(alias);
|
|
382
|
+
return alias;
|
|
383
|
+
}
|
|
384
|
+
async deleteAlias(id) {
|
|
385
|
+
await this.store.remove(id);
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// src/parser.ts
|
|
390
|
+
var EMAIL_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
|
|
391
|
+
var MOZMAIL_SUFFIX_PATTERN = /@mozmail\.com$/i;
|
|
392
|
+
var DUCK_SUFFIX_PATTERN = /@duck\.com$/i;
|
|
393
|
+
var ENCODED_WORD_PATTERN = /=\?([^?]+)\?([BbQq])\?([^?]*)\?=/g;
|
|
394
|
+
var EmailParser = class {
|
|
395
|
+
parseEmail(raw) {
|
|
396
|
+
const headers = this.parseHeaders(raw);
|
|
397
|
+
const toHeader = headers.get("to") || "";
|
|
398
|
+
const fromHeader = headers.get("from") || "";
|
|
399
|
+
const messageIdHeader = headers.get("message-id") || "";
|
|
400
|
+
const relayAlias = this.extractRelayAlias(raw);
|
|
401
|
+
return {
|
|
402
|
+
id: 0,
|
|
403
|
+
messageId: this.extractMessageId(messageIdHeader),
|
|
404
|
+
source: this.extractEmailAddress(fromHeader),
|
|
405
|
+
address: this.extractEmailAddress(toHeader),
|
|
406
|
+
raw,
|
|
407
|
+
metadata: null,
|
|
408
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
409
|
+
relayAlias: relayAlias || void 0
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
extractRelayAlias(raw) {
|
|
413
|
+
try {
|
|
414
|
+
const headers = this.parseHeaders(raw);
|
|
415
|
+
const duckOriginalTo = headers.get("duck-original-to");
|
|
416
|
+
if (duckOriginalTo) {
|
|
417
|
+
const duckAddress2 = this.extractEmailAddress(duckOriginalTo);
|
|
418
|
+
if (duckAddress2 && DUCK_SUFFIX_PATTERN.test(duckAddress2)) {
|
|
419
|
+
return duckAddress2;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
const fromAddresses = this.extractHeaderEmails(headers.get("from"));
|
|
423
|
+
const toAddresses = this.extractHeaderEmails(headers.get("to"));
|
|
424
|
+
const allAddresses = [...fromAddresses, ...toAddresses];
|
|
425
|
+
const mozmailAddress = allAddresses.find(
|
|
426
|
+
(address) => MOZMAIL_SUFFIX_PATTERN.test(address)
|
|
427
|
+
);
|
|
428
|
+
if (mozmailAddress) return mozmailAddress;
|
|
429
|
+
const duckAddress = allAddresses.find(
|
|
430
|
+
(address) => DUCK_SUFFIX_PATTERN.test(address)
|
|
431
|
+
);
|
|
432
|
+
if (duckAddress) return duckAddress;
|
|
433
|
+
return toAddresses[0] ?? null;
|
|
434
|
+
} catch {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
parseHeaders(raw) {
|
|
439
|
+
const headers = /* @__PURE__ */ new Map();
|
|
440
|
+
const headerEnd = raw.indexOf("\r\n\r\n");
|
|
441
|
+
const headerSection = headerEnd === -1 ? raw : raw.substring(0, headerEnd);
|
|
442
|
+
const lines = headerSection.split(/\r?\n/);
|
|
443
|
+
let currentHeader = null;
|
|
444
|
+
let currentValue = "";
|
|
445
|
+
for (const line of lines) {
|
|
446
|
+
if (/^\s/.test(line)) {
|
|
447
|
+
if (currentHeader) currentValue += " " + line.trim();
|
|
448
|
+
} else {
|
|
449
|
+
if (currentHeader) headers.set(currentHeader, currentValue);
|
|
450
|
+
const colonIndex = line.indexOf(":");
|
|
451
|
+
if (colonIndex > 0) {
|
|
452
|
+
currentHeader = line.substring(0, colonIndex).toLowerCase().trim();
|
|
453
|
+
currentValue = line.substring(colonIndex + 1).trim();
|
|
454
|
+
} else {
|
|
455
|
+
currentHeader = null;
|
|
456
|
+
currentValue = "";
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (currentHeader) headers.set(currentHeader, currentValue);
|
|
461
|
+
return headers;
|
|
462
|
+
}
|
|
463
|
+
decodeHeader(value) {
|
|
464
|
+
return value.replace(ENCODED_WORD_PATTERN, (_, charset, encoding, encoded) => {
|
|
465
|
+
try {
|
|
466
|
+
if (encoding.toUpperCase() === "Q") {
|
|
467
|
+
return this.decodeQuotedPrintable(encoded);
|
|
468
|
+
} else if (encoding.toUpperCase() === "B") {
|
|
469
|
+
return this.decodeBase64(encoded);
|
|
470
|
+
}
|
|
471
|
+
return encoded;
|
|
472
|
+
} catch {
|
|
473
|
+
return encoded;
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
decodeQuotedPrintable(encoded) {
|
|
478
|
+
let decoded = encoded.replace(/_/g, " ");
|
|
479
|
+
decoded = decoded.replace(
|
|
480
|
+
/=([0-9A-Fa-f]{2})/g,
|
|
481
|
+
(_, hex) => String.fromCharCode(parseInt(hex, 16))
|
|
482
|
+
);
|
|
483
|
+
return decoded;
|
|
484
|
+
}
|
|
485
|
+
decodeBase64(encoded) {
|
|
486
|
+
return Buffer.from(encoded, "base64").toString("utf-8");
|
|
487
|
+
}
|
|
488
|
+
extractEmailAddress(headerValue) {
|
|
489
|
+
if (!headerValue) return "";
|
|
490
|
+
const decoded = this.decodeHeader(headerValue);
|
|
491
|
+
const bracketMatch = decoded.match(/<([^>]+)>/);
|
|
492
|
+
if (bracketMatch) return bracketMatch[1].trim().toLowerCase();
|
|
493
|
+
const emailMatch = decoded.match(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/);
|
|
494
|
+
return emailMatch ? emailMatch[0].toLowerCase() : decoded.trim().toLowerCase();
|
|
495
|
+
}
|
|
496
|
+
extractHeaderEmails(headerValue) {
|
|
497
|
+
if (!headerValue) return [];
|
|
498
|
+
const decoded = this.decodeHeader(headerValue);
|
|
499
|
+
const matches = decoded.match(EMAIL_PATTERN);
|
|
500
|
+
return matches ? matches.map((match) => match.toLowerCase()) : [];
|
|
501
|
+
}
|
|
502
|
+
extractMessageId(headerValue) {
|
|
503
|
+
if (!headerValue) return `<generated-${Date.now()}@relay-temp-mail>`;
|
|
504
|
+
const cleaned = headerValue.replace(/[<>]/g, "").trim();
|
|
505
|
+
return `<${cleaned}>`;
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
|
|
434
509
|
// src/relay-api.ts
|
|
435
510
|
var FirefoxRelayProvider = class {
|
|
436
511
|
constructor(csrfToken, sessionId, httpClient) {
|
|
@@ -508,6 +583,11 @@ function createAliasProvider(config, httpClient) {
|
|
|
508
583
|
aliasConfig.sessionId,
|
|
509
584
|
httpClient
|
|
510
585
|
);
|
|
586
|
+
case "duckduckgo-email":
|
|
587
|
+
return new DuckDuckGoEmailProvider(
|
|
588
|
+
aliasConfig.jwtToken,
|
|
589
|
+
aliasConfig.store
|
|
590
|
+
);
|
|
511
591
|
}
|
|
512
592
|
}
|
|
513
593
|
function createMailProvider(config) {
|
|
@@ -568,7 +648,9 @@ var RelayClient = TempMailClient;
|
|
|
568
648
|
AuthError,
|
|
569
649
|
CFEmailClient,
|
|
570
650
|
CFTempMailProvider,
|
|
651
|
+
DuckDuckGoEmailProvider,
|
|
571
652
|
FirefoxRelayProvider,
|
|
653
|
+
InMemoryDuckDuckGoAliasStore,
|
|
572
654
|
NetworkError,
|
|
573
655
|
NotFoundError,
|
|
574
656
|
ParseError,
|