tempmail-sdk 1.1.2 → 1.1.4

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.
Files changed (47) hide show
  1. package/README.md +43 -38
  2. package/demo/poll-emails.ts +290 -28
  3. package/dist/config.d.ts +16 -0
  4. package/dist/config.js +4 -1
  5. package/dist/index.d.ts +4 -1
  6. package/dist/index.js +33 -16
  7. package/dist/providers/awamail.js +4 -3
  8. package/dist/providers/chatgpt-org-uk.d.ts +1 -1
  9. package/dist/providers/chatgpt-org-uk.js +188 -20
  10. package/dist/providers/dropmail.js +135 -3
  11. package/dist/providers/guerrillamail.js +4 -3
  12. package/dist/providers/linshi-email.d.ts +1 -1
  13. package/dist/providers/linshi-email.js +19 -7
  14. package/dist/providers/linshi-token.d.ts +25 -0
  15. package/dist/providers/linshi-token.js +69 -0
  16. package/dist/providers/mail-tm.js +43 -25
  17. package/dist/providers/maildrop.js +3 -2
  18. package/dist/providers/smail-pw.d.ts +9 -0
  19. package/dist/providers/smail-pw.js +356 -0
  20. package/dist/providers/temp-mail-io.js +5 -4
  21. package/dist/providers/tempmail-lol.js +4 -3
  22. package/dist/providers/tempmail.js +4 -3
  23. package/dist/retry.d.ts +2 -10
  24. package/dist/retry.js +41 -10
  25. package/dist/types.d.ts +6 -1
  26. package/dist/types.js +1 -1
  27. package/package.json +1 -1
  28. package/src/config.ts +16 -0
  29. package/src/index.ts +31 -14
  30. package/src/providers/awamail.ts +3 -2
  31. package/src/providers/chatgpt-org-uk.ts +213 -22
  32. package/src/providers/dropmail.ts +162 -2
  33. package/src/providers/guerrillamail.ts +3 -2
  34. package/src/providers/linshi-email.ts +24 -7
  35. package/src/providers/linshi-token.ts +86 -0
  36. package/src/providers/mail-tm.ts +43 -24
  37. package/src/providers/maildrop.ts +2 -1
  38. package/src/providers/smail-pw.ts +382 -0
  39. package/src/providers/temp-mail-io.ts +4 -3
  40. package/src/providers/tempmail-lol.ts +3 -2
  41. package/src/providers/tempmail.ts +3 -2
  42. package/src/retry.ts +42 -9
  43. package/src/types.ts +6 -1
  44. package/test/example.ts +183 -4
  45. package/dist/providers/tempmail-la.d.ts +0 -15
  46. package/dist/providers/tempmail-la.js +0 -89
  47. package/src/providers/tempmail-la.ts +0 -99
@@ -1,14 +1,43 @@
1
1
  import { InternalEmailInfo, Email, Channel } from '../types';
2
2
  import { normalizeEmail } from '../normalize';
3
+ import { fetchWithTimeout } from '../retry';
3
4
 
4
5
  const CHANNEL: Channel = 'mail-tm';
5
6
  const BASE_URL = 'https://api.mail.tm';
6
7
 
8
+ /**
9
+ * 与 Internxt 临时邮箱页(https://internxt.com/temporary-email)前端一致:
10
+ * 同源 cross-site 请求 api.mail.tm 时常带 Origin/Referer;domains 可为空 Bearer。
11
+ */
7
12
  const DEFAULT_HEADERS: Record<string, string> = {
8
- 'Content-Type': 'application/json',
9
13
  'Accept': 'application/json',
14
+ 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
15
+ 'Cache-Control': 'no-cache',
16
+ 'Pragma': 'no-cache',
17
+ 'Origin': 'https://internxt.com',
18
+ 'Referer': 'https://internxt.com/',
19
+ 'User-Agent':
20
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0',
10
21
  };
11
22
 
23
+ function jsonHeaders(extra?: Record<string, string>): Record<string, string> {
24
+ return { ...DEFAULT_HEADERS, 'Content-Type': 'application/json', ...extra };
25
+ }
26
+
27
+ function bearerHeaders(token: string, extra?: Record<string, string>): Record<string, string> {
28
+ return { ...jsonHeaders(extra), Authorization: `Bearer ${token}` };
29
+ }
30
+
31
+ function parseHydraMember(data: unknown): any[] {
32
+ if (Array.isArray(data)) return data;
33
+ if (data && typeof data === 'object') {
34
+ const o = data as Record<string, unknown>;
35
+ if (Array.isArray(o['hydra:member'])) return o['hydra:member'];
36
+ if (Array.isArray(o['data'])) return o['data'];
37
+ }
38
+ return [];
39
+ }
40
+
12
41
  /**
13
42
  * 生成随机字符串
14
43
  */
@@ -26,7 +55,7 @@ function randomString(length: number): string {
26
55
  * API: GET /domains
27
56
  */
28
57
  async function getDomains(): Promise<string[]> {
29
- const response = await fetch(`${BASE_URL}/domains`, {
58
+ const response = await fetchWithTimeout(`${BASE_URL}/domains?page=1`, {
30
59
  method: 'GET',
31
60
  headers: DEFAULT_HEADERS,
32
61
  });
@@ -36,14 +65,11 @@ async function getDomains(): Promise<string[]> {
36
65
  }
37
66
 
38
67
  const data = await response.json();
39
- /* 兼容两种响应格式:
40
- * - Accept: application/ld+json → Hydra 格式 { "hydra:member": [...] }
41
- * - Accept: application/json → 纯数组 [...]
42
- */
43
- const members = Array.isArray(data) ? data : (data['hydra:member'] || []);
68
+ const members = parseHydraMember(data);
44
69
  return members
45
- .filter((d: any) => d.isActive)
46
- .map((d: any) => d.domain);
70
+ .filter((d: any) => d && d.isActive !== false)
71
+ .map((d: any) => d.domain)
72
+ .filter(Boolean);
47
73
  }
48
74
 
49
75
  /**
@@ -51,7 +77,7 @@ async function getDomains(): Promise<string[]> {
51
77
  * API: POST /accounts
52
78
  */
53
79
  async function createAccount(address: string, password: string): Promise<any> {
54
- const response = await fetch(`${BASE_URL}/accounts`, {
80
+ const response = await fetchWithTimeout(`${BASE_URL}/accounts`, {
55
81
  method: 'POST',
56
82
  headers: { ...DEFAULT_HEADERS, 'Content-Type': 'application/ld+json' },
57
83
  body: JSON.stringify({ address, password }),
@@ -70,9 +96,9 @@ async function createAccount(address: string, password: string): Promise<any> {
70
96
  * API: POST /token
71
97
  */
72
98
  async function getToken(address: string, password: string): Promise<string> {
73
- const response = await fetch(`${BASE_URL}/token`, {
99
+ const response = await fetchWithTimeout(`${BASE_URL}/token`, {
74
100
  method: 'POST',
75
- headers: DEFAULT_HEADERS,
101
+ headers: jsonHeaders(),
76
102
  body: JSON.stringify({ address, password }),
77
103
  });
78
104
 
@@ -144,12 +170,9 @@ function flattenMessage(msg: any, recipientEmail: string): any {
144
170
  */
145
171
  export async function getEmails(token: string, email: string): Promise<Email[]> {
146
172
  // 1. 获取邮件列表
147
- const listResponse = await fetch(`${BASE_URL}/messages`, {
173
+ const listResponse = await fetchWithTimeout(`${BASE_URL}/messages?page=1`, {
148
174
  method: 'GET',
149
- headers: {
150
- ...DEFAULT_HEADERS,
151
- 'Authorization': `Bearer ${token}`,
152
- },
175
+ headers: bearerHeaders(token),
153
176
  });
154
177
 
155
178
  if (!listResponse.ok) {
@@ -157,8 +180,7 @@ export async function getEmails(token: string, email: string): Promise<Email[]>
157
180
  }
158
181
 
159
182
  const listData = await listResponse.json();
160
- /* 兼容 Hydra 格式和纯数组格式 */
161
- const messages = Array.isArray(listData) ? listData : (listData['hydra:member'] || []);
183
+ const messages = parseHydraMember(listData);
162
184
 
163
185
  if (messages.length === 0) {
164
186
  return [];
@@ -167,12 +189,9 @@ export async function getEmails(token: string, email: string): Promise<Email[]>
167
189
  // 2. 并行获取每封邮件的详情(含 text/html/attachments)
168
190
  const detailPromises = messages.map(async (msg: any) => {
169
191
  try {
170
- const detailResponse = await fetch(`${BASE_URL}/messages/${msg.id}`, {
192
+ const detailResponse = await fetchWithTimeout(`${BASE_URL}/messages/${msg.id}`, {
171
193
  method: 'GET',
172
- headers: {
173
- ...DEFAULT_HEADERS,
174
- 'Authorization': `Bearer ${token}`,
175
- },
194
+ headers: bearerHeaders(token),
176
195
  });
177
196
 
178
197
  if (!detailResponse.ok) {
@@ -10,6 +10,7 @@
10
10
  */
11
11
 
12
12
  import { InternalEmailInfo, Email, Channel } from '../types';
13
+ import { fetchWithTimeout } from '../retry';
13
14
 
14
15
  const CHANNEL: Channel = 'maildrop';
15
16
  const GRAPHQL_URL = 'https://api.maildrop.cc/graphql';
@@ -113,7 +114,7 @@ async function graphqlRequest(
113
114
  query: string,
114
115
  variables: Record<string, string> = {},
115
116
  ): Promise<any> {
116
- const response = await fetch(GRAPHQL_URL, {
117
+ const response = await fetchWithTimeout(GRAPHQL_URL, {
117
118
  method: 'POST',
118
119
  headers: {
119
120
  'Content-Type': 'application/json',
@@ -0,0 +1,382 @@
1
+ import { InternalEmailInfo, Email, Channel } from '../types';
2
+ import { normalizeEmail } from '../normalize';
3
+ import { fetchWithTimeout } from '../retry';
4
+
5
+ const CHANNEL: Channel = 'smail-pw';
6
+ const ROOT_DATA_URL = 'https://smail.pw/_root.data';
7
+
8
+ const DEFAULT_HEADERS: Record<string, string> = {
9
+ Accept: '*/*',
10
+ 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
11
+ 'cache-control': 'no-cache',
12
+ dnt: '1',
13
+ origin: 'https://smail.pw',
14
+ pragma: 'no-cache',
15
+ referer: 'https://smail.pw/',
16
+ 'sec-ch-ua':
17
+ '"Chromium";v="146", "Not-A.Brand";v="24", "Microsoft Edge";v="146"',
18
+ 'sec-ch-ua-mobile': '?0',
19
+ 'sec-ch-ua-platform': '"Windows"',
20
+ 'sec-fetch-dest': 'empty',
21
+ 'sec-fetch-mode': 'cors',
22
+ 'sec-fetch-site': 'same-origin',
23
+ 'User-Agent':
24
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0',
25
+ };
26
+
27
+ /** Flight/RSC 根为数组时,按出现顺序展开嵌套数组,保留标量与对象(不展开对象键),用于解析交错键值 */
28
+ function flightLinearLeaves(node: unknown): unknown[] {
29
+ if (!Array.isArray(node)) {
30
+ return [];
31
+ }
32
+ const out: unknown[] = [];
33
+ for (const el of node) {
34
+ if (Array.isArray(el)) {
35
+ out.push(...flightLinearLeaves(el));
36
+ } else {
37
+ out.push(el);
38
+ }
39
+ }
40
+ return out;
41
+ }
42
+
43
+ const MAIL_KV_KEYS = new Set(['id', 'to_address', 'from_name', 'from_address']);
44
+
45
+ /**
46
+ * 在线性序列中查找 ... "subject", <str>, "time", <num>,再向前成对读取已知字段。
47
+ * 比纯正则更耐受 emails 与 id 之间的 {"_23":4,...} 引用块。
48
+ */
49
+ function parseSmailEmailsFromLinear(
50
+ leaves: unknown[],
51
+ recipientEmail: string,
52
+ ): any[] {
53
+ const mails: any[] = [];
54
+ for (let i = 0; i + 3 < leaves.length; i++) {
55
+ if (leaves[i] !== 'subject') {
56
+ continue;
57
+ }
58
+ const subj = leaves[i + 1];
59
+ if (typeof subj !== 'string') {
60
+ continue;
61
+ }
62
+ if (leaves[i + 2] !== 'time') {
63
+ continue;
64
+ }
65
+ const timeRaw = leaves[i + 3];
66
+ const timeMs =
67
+ typeof timeRaw === 'number'
68
+ ? timeRaw
69
+ : typeof timeRaw === 'string' && /^\d+$/.test(timeRaw)
70
+ ? parseInt(timeRaw, 10)
71
+ : NaN;
72
+ if (!Number.isFinite(timeMs)) {
73
+ continue;
74
+ }
75
+
76
+ const fields: Record<string, string> = {};
77
+ for (let k = i - 2; k >= 1; k -= 2) {
78
+ const key = leaves[k];
79
+ const val = leaves[k + 1];
80
+ if (typeof key !== 'string' || typeof val !== 'string') {
81
+ break;
82
+ }
83
+ if (MAIL_KV_KEYS.has(key)) {
84
+ fields[key] = val;
85
+ }
86
+ /* 未知键值对跳过(RSC 可能在 subject 前插入其它字符串字段) */
87
+ }
88
+
89
+ mails.push({
90
+ id: fields.id || '',
91
+ to_address: fields.to_address || recipientEmail,
92
+ from_name: fields.from_name || '',
93
+ from_address: fields.from_address || '',
94
+ subject: subj,
95
+ date: timeMs,
96
+ text: '',
97
+ html: '',
98
+ attachments: [],
99
+ });
100
+ }
101
+ return mails;
102
+ }
103
+
104
+ function extractSessionCookie(response: Response): string {
105
+ const h = response.headers as Headers & { getSetCookie?: () => string[] };
106
+ if (typeof h.getSetCookie === 'function') {
107
+ for (const line of h.getSetCookie()) {
108
+ const m = line.match(/^__session=([^;]+)/);
109
+ if (m) return `__session=${m[1]}`;
110
+ }
111
+ }
112
+ const single = response.headers.get('set-cookie');
113
+ if (single) {
114
+ const m = single.match(/__session=([^;]+)/);
115
+ if (m) return `__session=${m[1]}`;
116
+ }
117
+ return '';
118
+ }
119
+
120
+ /** RSC/Flight 风格 JSON 文本中提取 @smail.pw 收件地址 */
121
+ function extractInboxEmail(text: string): string | null {
122
+ const quoted = text.match(/"([a-z0-9][a-z0-9.-]*@smail\.pw)"/i);
123
+ if (quoted) return quoted[1];
124
+ const plain = text.match(/\b([a-z0-9][a-z0-9.-]*@smail\.pw)\b/i);
125
+ return plain ? plain[1] : null;
126
+ }
127
+
128
+ /** 从响应体解析邮件条目(正则路径,与早期 curl 样本一致) */
129
+ function parseSmailEmailsRegex(text: string, recipientEmail: string): any[] {
130
+ const out: any[] = [];
131
+ const re =
132
+ /"id","([^"]+)","to_address","([^"]*)","from_name","([^"]*)","from_address","([^"]*)","subject","([^"]*)","time",(\d+)/g;
133
+ let m: RegExpExecArray | null;
134
+ while ((m = re.exec(text)) !== null) {
135
+ out.push({
136
+ id: m[1],
137
+ to_address: m[2] || recipientEmail,
138
+ from_name: m[3],
139
+ from_address: m[4],
140
+ subject: m[5],
141
+ date: parseInt(m[6], 10),
142
+ text: '',
143
+ html: '',
144
+ attachments: [],
145
+ });
146
+ }
147
+ if (out.length > 0) {
148
+ return out;
149
+ }
150
+
151
+ const re2 =
152
+ /"id","([^"]+)","from_name","([^"]*)","from_address","([^"]*)","subject","([^"]*)","time",(\d+)/g;
153
+ while ((m = re2.exec(text)) !== null) {
154
+ out.push({
155
+ id: m[1],
156
+ to_address: recipientEmail,
157
+ from_name: m[2],
158
+ from_address: m[3],
159
+ subject: m[4],
160
+ date: parseInt(m[5], 10),
161
+ text: '',
162
+ html: '',
163
+ attachments: [],
164
+ });
165
+ }
166
+ return out;
167
+ }
168
+
169
+ function mergeMailsById(lists: any[][]): any[] {
170
+ const map = new Map<string, any>();
171
+ let anon = 0;
172
+ for (const list of lists) {
173
+ for (const mail of list) {
174
+ let id = String(mail?.id || '');
175
+ if (!id) {
176
+ id = `__smail_${anon++}_${mail?.date ?? ''}_${String(mail?.subject ?? '').slice(0, 48)}`;
177
+ mail.id = id;
178
+ }
179
+ if (!map.has(id)) {
180
+ map.set(id, mail);
181
+ }
182
+ }
183
+ }
184
+ return [...map.values()];
185
+ }
186
+
187
+ /**
188
+ * 官方 loader 返回 D1 行:{ id, to_address, from_name, from_address, subject, time }(见 akazwz/smail app/types/email.ts)。
189
+ * Flight 序列化后多为普通 JSON 对象,而非 "id","…" 交错字符串元组,须在整棵树递归收集。
190
+ */
191
+ function collectPlainRowEmails(root: unknown, recipientEmail: string): any[] {
192
+ const mails: any[] = [];
193
+ const seen = new WeakSet<object>();
194
+
195
+ function walk(node: unknown): void {
196
+ if (node === null || node === undefined) {
197
+ return;
198
+ }
199
+ if (typeof node !== 'object') {
200
+ return;
201
+ }
202
+ if (seen.has(node as object)) {
203
+ return;
204
+ }
205
+ seen.add(node as object);
206
+
207
+ if (Array.isArray(node)) {
208
+ for (const el of node) {
209
+ walk(el);
210
+ }
211
+ return;
212
+ }
213
+
214
+ const o = node as Record<string, unknown>;
215
+ if (typeof o.subject === 'string') {
216
+ const tr = o.time;
217
+ const timeMs =
218
+ typeof tr === 'number' && Number.isFinite(tr)
219
+ ? tr
220
+ : typeof tr === 'string' && /^\d+$/.test(tr)
221
+ ? parseInt(tr, 10)
222
+ : NaN;
223
+ if (Number.isFinite(timeMs)) {
224
+ mails.push({
225
+ id: String(o.id ?? ''),
226
+ to_address: String(o.to_address ?? recipientEmail),
227
+ from_name: String(o.from_name ?? ''),
228
+ from_address: String(o.from_address ?? ''),
229
+ subject: o.subject,
230
+ date: timeMs,
231
+ text: '',
232
+ html: '',
233
+ attachments: [],
234
+ });
235
+ }
236
+ }
237
+
238
+ for (const v of Object.values(o)) {
239
+ if (v !== null && typeof v === 'object') {
240
+ walk(v);
241
+ }
242
+ }
243
+ }
244
+
245
+ walk(root);
246
+ return mails;
247
+ }
248
+
249
+ /**
250
+ * Flight 风格 payload:根为数组,"addresses",[i] 表示 root[i] 为邮箱字符串;
251
+ * "emails",[a,b,...] 中每项为下标,指向 root[idx] 的邮件行(常为嵌套数组,内含 id/to_address/subject/time 键值序列)。
252
+ */
253
+ function resolveFlightSlot(root: unknown[], idx: number, visited: Set<number>): unknown {
254
+ if (idx < 0 || idx >= root.length || visited.has(idx)) {
255
+ return undefined;
256
+ }
257
+ visited.add(idx);
258
+ const val = root[idx];
259
+ if (typeof val === 'number' && Number.isInteger(val) && val >= 0 && val < root.length) {
260
+ return resolveFlightSlot(root, val, visited);
261
+ }
262
+ return val;
263
+ }
264
+
265
+ function flattenFlightIndices(refs: unknown[]): number[] {
266
+ const out: number[] = [];
267
+ for (const r of refs) {
268
+ if (typeof r === 'number' && Number.isInteger(r)) {
269
+ out.push(r);
270
+ } else if (typeof r === 'string' && /^\d+$/.test(r)) {
271
+ out.push(parseInt(r, 10));
272
+ } else if (Array.isArray(r)) {
273
+ for (const x of r) {
274
+ if (typeof x === 'number' && Number.isInteger(x)) {
275
+ out.push(x);
276
+ } else if (typeof x === 'string' && /^\d+$/.test(x)) {
277
+ out.push(parseInt(x, 10));
278
+ }
279
+ }
280
+ }
281
+ }
282
+ return out;
283
+ }
284
+
285
+ function parseSmailEmailsFromFlightRoot(root: unknown[], recipientEmail: string): any[] {
286
+ const mails: any[] = [];
287
+ for (let i = 0; i < root.length - 1; i++) {
288
+ if (root[i] !== 'emails') {
289
+ continue;
290
+ }
291
+ const refs = root[i + 1];
292
+ if (!Array.isArray(refs)) {
293
+ break;
294
+ }
295
+ for (const r of flattenFlightIndices(refs)) {
296
+ const node = resolveFlightSlot(root, r, new Set());
297
+ if (Array.isArray(node)) {
298
+ const leaves = flightLinearLeaves(node);
299
+ mails.push(...parseSmailEmailsFromLinear(leaves, recipientEmail));
300
+ } else if (node !== null && typeof node === 'object') {
301
+ mails.push(...collectPlainRowEmails(node, recipientEmail));
302
+ }
303
+ }
304
+ break;
305
+ }
306
+ return mails;
307
+ }
308
+
309
+ function parseSmailEmailsFromPayload(text: string, recipientEmail: string): any[] {
310
+ const regexMails = parseSmailEmailsRegex(text, recipientEmail);
311
+ let linearMails: any[] = [];
312
+ let flightMails: any[] = [];
313
+ let plainMails: any[] = [];
314
+ try {
315
+ const root = JSON.parse(text) as unknown;
316
+ plainMails = collectPlainRowEmails(root, recipientEmail);
317
+ if (Array.isArray(root)) {
318
+ flightMails = parseSmailEmailsFromFlightRoot(root, recipientEmail);
319
+ const leaves = flightLinearLeaves(root);
320
+ linearMails = parseSmailEmailsFromLinear(leaves, recipientEmail);
321
+ }
322
+ } catch {
323
+ /* 非 JSON 或结构异常时仅用正则 */
324
+ }
325
+ return mergeMailsById([regexMails, linearMails, flightMails, plainMails]);
326
+ }
327
+
328
+ /**
329
+ * POST intent=generate,保存 Set-Cookie __session,从 RSC 响应中解析邮箱
330
+ */
331
+ export async function generateEmail(): Promise<InternalEmailInfo> {
332
+ const response = await fetchWithTimeout(ROOT_DATA_URL, {
333
+ method: 'POST',
334
+ headers: {
335
+ ...DEFAULT_HEADERS,
336
+ 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
337
+ },
338
+ body: 'intent=generate',
339
+ });
340
+
341
+ if (!response.ok) {
342
+ throw new Error(`smail.pw generate failed: ${response.status}`);
343
+ }
344
+
345
+ const cookie = extractSessionCookie(response);
346
+ if (!cookie) {
347
+ throw new Error('Failed to extract __session cookie');
348
+ }
349
+
350
+ const text = await response.text();
351
+ const email = extractInboxEmail(text);
352
+ if (!email) {
353
+ throw new Error('Failed to parse inbox address from smail.pw response');
354
+ }
355
+
356
+ return {
357
+ channel: CHANNEL,
358
+ email,
359
+ token: cookie,
360
+ };
361
+ }
362
+
363
+ /**
364
+ * GET _root.data,携带 __session Cookie,解析邮件列表
365
+ */
366
+ export async function getEmails(token: string, email: string): Promise<Email[]> {
367
+ const response = await fetchWithTimeout(ROOT_DATA_URL, {
368
+ method: 'GET',
369
+ headers: {
370
+ ...DEFAULT_HEADERS,
371
+ Cookie: token,
372
+ },
373
+ });
374
+
375
+ if (!response.ok) {
376
+ throw new Error(`smail.pw get emails failed: ${response.status}`);
377
+ }
378
+
379
+ const text = await response.text();
380
+ const rawList = parseSmailEmailsFromPayload(text, email);
381
+ return rawList.map((raw) => normalizeEmail(raw, email));
382
+ }
@@ -1,5 +1,6 @@
1
1
  import { InternalEmailInfo, Email, Channel } from '../types';
2
2
  import { normalizeEmail } from '../normalize';
3
+ import { fetchWithTimeout } from '../retry';
3
4
 
4
5
  const CHANNEL: Channel = 'temp-mail-io';
5
6
  const BASE_URL = 'https://api.internal.temp-mail.io/api/v3';
@@ -18,7 +19,7 @@ async function fetchCorsHeader(): Promise<string> {
18
19
  if (cachedCorsHeader) return cachedCorsHeader;
19
20
 
20
21
  try {
21
- const response = await fetch(PAGE_URL, {
22
+ const response = await fetchWithTimeout(PAGE_URL, {
22
23
  headers: {
23
24
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36',
24
25
  },
@@ -61,7 +62,7 @@ async function getApiHeaders(): Promise<Record<string, string>> {
61
62
  */
62
63
  export async function generateEmail(): Promise<InternalEmailInfo> {
63
64
  const headers = await getApiHeaders();
64
- const response = await fetch(`${BASE_URL}/email/new`, {
65
+ const response = await fetchWithTimeout(`${BASE_URL}/email/new`, {
65
66
  method: 'POST',
66
67
  headers,
67
68
  body: JSON.stringify({ min_name_length: 10, max_name_length: 10 }),
@@ -91,7 +92,7 @@ export async function generateEmail(): Promise<InternalEmailInfo> {
91
92
  */
92
93
  export async function getEmails(email: string): Promise<Email[]> {
93
94
  const headers = await getApiHeaders();
94
- const response = await fetch(`${BASE_URL}/email/${email}/messages`, {
95
+ const response = await fetchWithTimeout(`${BASE_URL}/email/${email}/messages`, {
95
96
  method: 'GET',
96
97
  headers,
97
98
  });
@@ -1,5 +1,6 @@
1
1
  import { InternalEmailInfo, Email, Channel } from '../types';
2
2
  import { normalizeEmail } from '../normalize';
3
+ import { fetchWithTimeout } from '../retry';
3
4
 
4
5
  const CHANNEL: Channel = 'tempmail-lol';
5
6
  const BASE_URL = 'https://api.tempmail.lol/v2';
@@ -15,7 +16,7 @@ const DEFAULT_HEADERS = {
15
16
  };
16
17
 
17
18
  export async function generateEmail(domain: string | null = null): Promise<InternalEmailInfo> {
18
- const response = await fetch(`${BASE_URL}/inbox/create`, {
19
+ const response = await fetchWithTimeout(`${BASE_URL}/inbox/create`, {
19
20
  method: 'POST',
20
21
  headers: DEFAULT_HEADERS,
21
22
  body: JSON.stringify({ domain, captcha: null }),
@@ -39,7 +40,7 @@ export async function generateEmail(domain: string | null = null): Promise<Inter
39
40
  }
40
41
 
41
42
  export async function getEmails(token: string, recipientEmail: string = ''): Promise<Email[]> {
42
- const response = await fetch(`${BASE_URL}/inbox?token=${encodeURIComponent(token)}`, {
43
+ const response = await fetchWithTimeout(`${BASE_URL}/inbox?token=${encodeURIComponent(token)}`, {
43
44
  method: 'GET',
44
45
  headers: DEFAULT_HEADERS,
45
46
  });
@@ -1,5 +1,6 @@
1
1
  import { InternalEmailInfo, Email, Channel } from '../types';
2
2
  import { normalizeEmail } from '../normalize';
3
+ import { fetchWithTimeout } from '../retry';
3
4
 
4
5
  const CHANNEL: Channel = 'tempmail';
5
6
  const BASE_URL = 'https://api.tempmail.ing/api';
@@ -15,7 +16,7 @@ const DEFAULT_HEADERS = {
15
16
  };
16
17
 
17
18
  export async function generateEmail(duration: number = 30): Promise<InternalEmailInfo> {
18
- const response = await fetch(`${BASE_URL}/generate`, {
19
+ const response = await fetchWithTimeout(`${BASE_URL}/generate`, {
19
20
  method: 'POST',
20
21
  headers: DEFAULT_HEADERS,
21
22
  body: JSON.stringify({ duration }),
@@ -41,7 +42,7 @@ export async function generateEmail(duration: number = 30): Promise<InternalEmai
41
42
 
42
43
  export async function getEmails(email: string): Promise<Email[]> {
43
44
  const encodedEmail = encodeURIComponent(email);
44
- const response = await fetch(`${BASE_URL}/emails/${encodedEmail}`, {
45
+ const response = await fetchWithTimeout(`${BASE_URL}/emails/${encodedEmail}`, {
45
46
  method: 'GET',
46
47
  headers: DEFAULT_HEADERS,
47
48
  });