@openemail/sdk 0.0.1
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 +183 -0
- package/index.cjs +1776 -0
- package/index.d.cts +1531 -0
- package/index.d.ts +1531 -0
- package/index.js +1744 -0
- package/package.json +60 -0
package/index.js
ADDED
|
@@ -0,0 +1,1744 @@
|
|
|
1
|
+
var version = "0.0.1";
|
|
2
|
+
|
|
3
|
+
const BUILD_BASE_URL = 'https://api.openemail.uk';
|
|
4
|
+
|
|
5
|
+
const DEFAULTS = {
|
|
6
|
+
BASE_URL: BUILD_BASE_URL,
|
|
7
|
+
MAX_RETRIES: 2,
|
|
8
|
+
TIMEOUT_MS: 30_000,
|
|
9
|
+
USER_AGENT: `openemail-sdk/${version}`
|
|
10
|
+
};
|
|
11
|
+
const BROWSER_REFUSAL = [
|
|
12
|
+
'OpenEmail refused to start in a browser.',
|
|
13
|
+
'',
|
|
14
|
+
' This client carries a workspace API key that can send mail and read the mailbox. Anything',
|
|
15
|
+
' you ship to a browser is readable by anyone who opens devtools.',
|
|
16
|
+
'',
|
|
17
|
+
' Call it from a server, a serverless function or a script instead. Disposable inboxes are',
|
|
18
|
+
' the exception: createTempMail() needs no API key and is safe to use in a browser.',
|
|
19
|
+
'',
|
|
20
|
+
' If you are certain this environment is not a real browser, for example a test harness that',
|
|
21
|
+
' defines window and document, pass { dangerouslyAllowBrowser: true }.'
|
|
22
|
+
].join('\n');
|
|
23
|
+
const ENV_VARS = {
|
|
24
|
+
API_KEY: 'OPENEMAIL_API_KEY',
|
|
25
|
+
BASE_URL: 'OPENEMAIL_BASE_URL'
|
|
26
|
+
};
|
|
27
|
+
const API_KEY_PREFIXES = {
|
|
28
|
+
LIVE: 'oe_live_',
|
|
29
|
+
TEST: 'oe_test_'
|
|
30
|
+
};
|
|
31
|
+
const API_KEY_MODES = {
|
|
32
|
+
LIVE: 'live',
|
|
33
|
+
TEST: 'test'
|
|
34
|
+
};
|
|
35
|
+
const CLIENT_MESSAGES = {
|
|
36
|
+
API_KEY_REQUIRED: `An OpenEmail API key is required. Pass { apiKey } or set ${ENV_VARS.API_KEY}. Create one in OpenEmail under Settings, API keys.`,
|
|
37
|
+
API_KEY_SHAPE: 'is not an OpenEmail API key. The API accepts only keys beginning "oe_live_" or "oe_test_", so a session cookie, a session token or a key for another service is refused.',
|
|
38
|
+
BASE_URL_SHAPE: 'is not a usable base URL. Pass an origin such as "https://api.openemail.uk", or the http origin of a server you run yourself.',
|
|
39
|
+
NO_FETCH: 'No global fetch is available. Pass one as { fetch }, or run on Node 20+, Bun, Deno, Cloudflare Workers or a browser.',
|
|
40
|
+
EMPTY_SEGMENT: 'An id must not be empty.',
|
|
41
|
+
DOT_SEGMENT: 'is not a usable id: a path segment made only of dots is removed by URL parsers, so the request would reach a different endpoint.'
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const MESSAGE_ENCRYPTION_FORMATS = [
|
|
45
|
+
'pgp-mime',
|
|
46
|
+
'pgp-signed',
|
|
47
|
+
'pgp-inline',
|
|
48
|
+
'smime-encrypted',
|
|
49
|
+
'smime-signed'
|
|
50
|
+
];
|
|
51
|
+
const SEALED_ENCRYPTION_FORMATS = ['pgp-mime', 'pgp-inline', 'smime-encrypted'];
|
|
52
|
+
const BASE64_CHUNK_SIZE = 0x8000;
|
|
53
|
+
|
|
54
|
+
const ERROR_TYPES = [
|
|
55
|
+
'invalid_request_error',
|
|
56
|
+
'authentication_error',
|
|
57
|
+
'permission_error',
|
|
58
|
+
'not_found_error',
|
|
59
|
+
'conflict_error',
|
|
60
|
+
'validation_error',
|
|
61
|
+
'rate_limit_error',
|
|
62
|
+
'api_error'
|
|
63
|
+
];
|
|
64
|
+
const ERROR_TYPE = {
|
|
65
|
+
INVALID_REQUEST: 'invalid_request_error',
|
|
66
|
+
AUTHENTICATION: 'authentication_error',
|
|
67
|
+
PERMISSION: 'permission_error',
|
|
68
|
+
NOT_FOUND: 'not_found_error',
|
|
69
|
+
CONFLICT: 'conflict_error',
|
|
70
|
+
VALIDATION: 'validation_error',
|
|
71
|
+
RATE_LIMIT: 'rate_limit_error',
|
|
72
|
+
API: 'api_error'
|
|
73
|
+
};
|
|
74
|
+
const ERROR_TYPE_BY_STATUS = {
|
|
75
|
+
401: ERROR_TYPE.AUTHENTICATION,
|
|
76
|
+
403: ERROR_TYPE.PERMISSION,
|
|
77
|
+
404: ERROR_TYPE.NOT_FOUND,
|
|
78
|
+
409: ERROR_TYPE.CONFLICT,
|
|
79
|
+
422: ERROR_TYPE.VALIDATION,
|
|
80
|
+
429: ERROR_TYPE.RATE_LIMIT
|
|
81
|
+
};
|
|
82
|
+
const ERROR_CODES = {
|
|
83
|
+
INSUFFICIENT_SCOPE: 'insufficient_scope',
|
|
84
|
+
UNRECOGNISED_RESPONSE: 'unrecognised_response'
|
|
85
|
+
};
|
|
86
|
+
const ERROR_NAMES = {
|
|
87
|
+
API: 'OpenEmailApiError',
|
|
88
|
+
NETWORK: 'OpenEmailNetworkError',
|
|
89
|
+
TIMEOUT: 'TimeoutError'};
|
|
90
|
+
|
|
91
|
+
const HTTP_METHODS = {
|
|
92
|
+
GET: 'GET',
|
|
93
|
+
POST: 'POST',
|
|
94
|
+
PATCH: 'PATCH',
|
|
95
|
+
DELETE: 'DELETE'
|
|
96
|
+
};
|
|
97
|
+
const HEADER_KEYS = {
|
|
98
|
+
AUTHORIZATION: 'Authorization',
|
|
99
|
+
ACCEPT: 'Accept',
|
|
100
|
+
CONTENT_TYPE: 'Content-Type',
|
|
101
|
+
USER_AGENT: 'User-Agent',
|
|
102
|
+
IDEMPOTENCY_KEY: 'Idempotency-Key',
|
|
103
|
+
RETRY_AFTER: 'retry-after',
|
|
104
|
+
REQUEST_ID: 'x-request-id'
|
|
105
|
+
};
|
|
106
|
+
const CONTENT_TYPES = {
|
|
107
|
+
JSON: 'application/json',
|
|
108
|
+
CALENDAR: 'text/calendar'
|
|
109
|
+
};
|
|
110
|
+
const RETRY = {
|
|
111
|
+
STATUSES: [408, 429, 500, 502, 503, 504],
|
|
112
|
+
METHODS: ['GET'],
|
|
113
|
+
BASE_BACKOFF_MS: 500,
|
|
114
|
+
MAX_BACKOFF_MS: 8_000,
|
|
115
|
+
MAX_HONOURED_RETRY_AFTER_MS: 60_000,
|
|
116
|
+
RATE_LIMIT_STATUS: 429
|
|
117
|
+
};
|
|
118
|
+
const IDEMPOTENCY_KEY_PREFIX = 'oe-';
|
|
119
|
+
|
|
120
|
+
const LANGUAGES = [
|
|
121
|
+
{ code: 'en', label: 'English', native: 'English', flag: '🇬🇧', rtl: false },
|
|
122
|
+
{ code: 'es', label: 'Spanish', native: 'Español', flag: '🇪🇸', rtl: false },
|
|
123
|
+
{ code: 'zh-Hans', label: 'Simplified Chinese', native: '简体中文', flag: '🇨🇳', rtl: false },
|
|
124
|
+
{ code: 'zh-Hant', label: 'Traditional Chinese', native: '繁體中文', flag: '🇹🇼', rtl: false },
|
|
125
|
+
{ code: 'hi', label: 'Hindi', native: 'हिन्दी', flag: '🇮🇳', rtl: false },
|
|
126
|
+
{ code: 'ar', label: 'Arabic', native: 'العربية', flag: '🇸🇦', rtl: true },
|
|
127
|
+
{ code: 'pt-BR', label: 'Brazilian Portuguese', native: 'Português (Brasil)', flag: '🇧🇷', rtl: false },
|
|
128
|
+
{ code: 'pt', label: 'Portuguese', native: 'Português', flag: '🇵🇹', rtl: false },
|
|
129
|
+
{ code: 'fr', label: 'French', native: 'Français', flag: '🇫🇷', rtl: false },
|
|
130
|
+
{ code: 'de', label: 'German', native: 'Deutsch', flag: '🇩🇪', rtl: false },
|
|
131
|
+
{ code: 'ru', label: 'Russian', native: 'Русский', flag: '🇷🇺', rtl: false },
|
|
132
|
+
{ code: 'ja', label: 'Japanese', native: '日本語', flag: '🇯🇵', rtl: false },
|
|
133
|
+
{ code: 'ko', label: 'Korean', native: '한국어', flag: '🇰🇷', rtl: false },
|
|
134
|
+
{ code: 'it', label: 'Italian', native: 'Italiano', flag: '🇮🇹', rtl: false },
|
|
135
|
+
{ code: 'nl', label: 'Dutch', native: 'Nederlands', flag: '🇳🇱', rtl: false },
|
|
136
|
+
{ code: 'tr', label: 'Turkish', native: 'Türkçe', flag: '🇹🇷', rtl: false },
|
|
137
|
+
{ code: 'pl', label: 'Polish', native: 'Polski', flag: '🇵🇱', rtl: false },
|
|
138
|
+
{ code: 'uk', label: 'Ukrainian', native: 'Українська', flag: '🇺🇦', rtl: false },
|
|
139
|
+
{ code: 'vi', label: 'Vietnamese', native: 'Tiếng Việt', flag: '🇻🇳', rtl: false },
|
|
140
|
+
{ code: 'id', label: 'Indonesian', native: 'Bahasa Indonesia', flag: '🇮🇩', rtl: false },
|
|
141
|
+
{ code: 'th', label: 'Thai', native: 'ไทย', flag: '🇹🇭', rtl: false },
|
|
142
|
+
{ code: 'sv', label: 'Swedish', native: 'Svenska', flag: '🇸🇪', rtl: false },
|
|
143
|
+
{ code: 'da', label: 'Danish', native: 'Dansk', flag: '🇩🇰', rtl: false },
|
|
144
|
+
{ code: 'no', label: 'Norwegian', native: 'Norsk', flag: '🇳🇴', rtl: false },
|
|
145
|
+
{ code: 'fi', label: 'Finnish', native: 'Suomi', flag: '🇫🇮', rtl: false },
|
|
146
|
+
{ code: 'cs', label: 'Czech', native: 'Čeština', flag: '🇨🇿', rtl: false },
|
|
147
|
+
{ code: 'el', label: 'Greek', native: 'Ελληνικά', flag: '🇬🇷', rtl: false },
|
|
148
|
+
{ code: 'he', label: 'Hebrew', native: 'עברית', flag: '🇮🇱', rtl: true },
|
|
149
|
+
{ code: 'ro', label: 'Romanian', native: 'Română', flag: '🇷🇴', rtl: false },
|
|
150
|
+
{ code: 'hu', label: 'Hungarian', native: 'Magyar', flag: '🇭🇺', rtl: false },
|
|
151
|
+
{ code: 'bn', label: 'Bengali', native: 'বাংলা', flag: '🇧🇩', rtl: false },
|
|
152
|
+
{ code: 'ur', label: 'Urdu', native: 'اردو', flag: '🇵🇰', rtl: true },
|
|
153
|
+
{ code: 'fa', label: 'Persian', native: 'فارسی', flag: '🇮🇷', rtl: true },
|
|
154
|
+
{ code: 'ms', label: 'Malay', native: 'Bahasa Melayu', flag: '🇲🇾', rtl: false },
|
|
155
|
+
{ code: 'ta', label: 'Tamil', native: 'தமிழ்', flag: '🇮🇳', rtl: false },
|
|
156
|
+
{ code: 'ca', label: 'Catalan', native: 'Català', flag: '🇦🇩', rtl: false },
|
|
157
|
+
{ code: 'en-GB', label: 'British English', native: 'English (United Kingdom)', flag: '🇬🇧', rtl: false },
|
|
158
|
+
{ code: 'en-US', label: 'American English', native: 'English (United States)', flag: '🇺🇸', rtl: false },
|
|
159
|
+
{ code: 'es-419', label: 'Latin American Spanish', native: 'Español (Latinoamérica)', flag: '🇲🇽', rtl: false },
|
|
160
|
+
{ code: 'fr-CA', label: 'Canadian French', native: 'Français (Canada)', flag: '🇨🇦', rtl: false },
|
|
161
|
+
{ code: 'de-AT', label: 'Austrian German', native: 'Deutsch (Österreich)', flag: '🇦🇹', rtl: false },
|
|
162
|
+
{ code: 'de-CH', label: 'Swiss Standard German', native: 'Deutsch (Schweiz)', flag: '🇨🇭', rtl: false },
|
|
163
|
+
{ code: 'nl-BE', label: 'Flemish', native: 'Vlaams', flag: '🇧🇪', rtl: false },
|
|
164
|
+
{ code: 'zh-HK', label: 'Hong Kong Chinese', native: '繁體中文(香港)', flag: '🇭🇰', rtl: false },
|
|
165
|
+
{ code: 'yue', label: 'Cantonese', native: '粵語', flag: '🇭🇰', rtl: false },
|
|
166
|
+
{ code: 'nb', label: 'Norwegian Bokmål', native: 'Norsk bokmål', flag: '🇳🇴', rtl: false },
|
|
167
|
+
{ code: 'nn', label: 'Norwegian Nynorsk', native: 'Norsk nynorsk', flag: '🇳🇴', rtl: false },
|
|
168
|
+
{ code: 'sr', label: 'Serbian', native: 'Српски', flag: '🇷🇸', rtl: false },
|
|
169
|
+
{ code: 'sr-Latn', label: 'Serbian (Latin)', native: 'Srpski (latinica)', flag: '🇷🇸', rtl: false },
|
|
170
|
+
{ code: 'ar-EG', label: 'Egyptian Arabic', native: 'العربية (مصر)', flag: '🇪🇬', rtl: true },
|
|
171
|
+
{ code: 'ar-MA', label: 'Moroccan Arabic', native: 'الدارجة المغربية', flag: '🇲🇦', rtl: true },
|
|
172
|
+
{ code: 'sk', label: 'Slovak', native: 'Slovenčina', flag: '🇸🇰', rtl: false },
|
|
173
|
+
{ code: 'sl', label: 'Slovenian', native: 'Slovenščina', flag: '🇸🇮', rtl: false },
|
|
174
|
+
{ code: 'hr', label: 'Croatian', native: 'Hrvatski', flag: '🇭🇷', rtl: false },
|
|
175
|
+
{ code: 'bs', label: 'Bosnian', native: 'Bosanski', flag: '🇧🇦', rtl: false },
|
|
176
|
+
{ code: 'cnr', label: 'Montenegrin', native: 'Crnogorski', flag: '🇲🇪', rtl: false },
|
|
177
|
+
{ code: 'mk', label: 'Macedonian', native: 'Македонски', flag: '🇲🇰', rtl: false },
|
|
178
|
+
{ code: 'bg', label: 'Bulgarian', native: 'Български', flag: '🇧🇬', rtl: false },
|
|
179
|
+
{ code: 'be', label: 'Belarusian', native: 'Беларуская', flag: '🇧🇾', rtl: false },
|
|
180
|
+
{ code: 'hsb', label: 'Upper Sorbian', native: 'Hornjoserbšćina', flag: '🇩🇪', rtl: false },
|
|
181
|
+
{ code: 'rue', label: 'Rusyn', native: 'Русиньскый', flag: '🇸🇰', rtl: false },
|
|
182
|
+
{ code: 'lt', label: 'Lithuanian', native: 'Lietuvių', flag: '🇱🇹', rtl: false },
|
|
183
|
+
{ code: 'lv', label: 'Latvian', native: 'Latviešu', flag: '🇱🇻', rtl: false },
|
|
184
|
+
{ code: 'et', label: 'Estonian', native: 'Eesti', flag: '🇪🇪', rtl: false },
|
|
185
|
+
{ code: 'se', label: 'Northern Sami', native: 'Davvisámegiella', flag: '🇳🇴', rtl: false },
|
|
186
|
+
{ code: 'is', label: 'Icelandic', native: 'Íslenska', flag: '🇮🇸', rtl: false },
|
|
187
|
+
{ code: 'fo', label: 'Faroese', native: 'Føroyskt', flag: '🇫🇴', rtl: false },
|
|
188
|
+
{ code: 'kl', label: 'Greenlandic', native: 'Kalaallisut', flag: '🇬🇱', rtl: false },
|
|
189
|
+
{ code: 'ga', label: 'Irish', native: 'Gaeilge', flag: '🇮🇪', rtl: false },
|
|
190
|
+
{ code: 'gd', label: 'Scottish Gaelic', native: 'Gàidhlig', flag: '🇬🇧', rtl: false },
|
|
191
|
+
{ code: 'cy', label: 'Welsh', native: 'Cymraeg', flag: '🇬🇧', rtl: false },
|
|
192
|
+
{ code: 'kw', label: 'Cornish', native: 'Kernewek', flag: '🇬🇧', rtl: false },
|
|
193
|
+
{ code: 'gv', label: 'Manx', native: 'Gaelg', flag: '🇮🇲', rtl: false },
|
|
194
|
+
{ code: 'br', label: 'Breton', native: 'Brezhoneg', flag: '🇫🇷', rtl: false },
|
|
195
|
+
{ code: 'eu', label: 'Basque', native: 'Euskara', flag: '🇪🇸', rtl: false },
|
|
196
|
+
{ code: 'gl', label: 'Galician', native: 'Galego', flag: '🇪🇸', rtl: false },
|
|
197
|
+
{ code: 'ast', label: 'Asturian', native: 'Asturianu', flag: '🇪🇸', rtl: false },
|
|
198
|
+
{ code: 'an', label: 'Aragonese', native: 'Aragonés', flag: '🇪🇸', rtl: false },
|
|
199
|
+
{ code: 'oc', label: 'Occitan', native: 'Occitan', flag: '🇫🇷', rtl: false },
|
|
200
|
+
{ code: 'co', label: 'Corsican', native: 'Corsu', flag: '🇫🇷', rtl: false },
|
|
201
|
+
{ code: 'sc', label: 'Sardinian', native: 'Sardu', flag: '🇮🇹', rtl: false },
|
|
202
|
+
{ code: 'fur', label: 'Friulian', native: 'Furlan', flag: '🇮🇹', rtl: false },
|
|
203
|
+
{ code: 'rm', label: 'Romansh', native: 'Rumantsch', flag: '🇨🇭', rtl: false },
|
|
204
|
+
{ code: 'gsw', label: 'Alemannic German', native: 'Schwiizerdütsch', flag: '🇨🇭', rtl: false },
|
|
205
|
+
{ code: 'lb', label: 'Luxembourgish', native: 'Lëtzebuergesch', flag: '🇱🇺', rtl: false },
|
|
206
|
+
{ code: 'nds', label: 'Low German', native: 'Plattdüütsch', flag: '🇩🇪', rtl: false },
|
|
207
|
+
{ code: 'bar', label: 'Bavarian', native: 'Boarisch', flag: '🇩🇪', rtl: false },
|
|
208
|
+
{ code: 'fy', label: 'Western Frisian', native: 'Frysk', flag: '🇳🇱', rtl: false },
|
|
209
|
+
{ code: 'li', label: 'Limburgish', native: 'Limburgs', flag: '🇳🇱', rtl: false },
|
|
210
|
+
{ code: 'wa', label: 'Walloon', native: 'Walon', flag: '🇧🇪', rtl: false },
|
|
211
|
+
{ code: 'mt', label: 'Maltese', native: 'Malti', flag: '🇲🇹', rtl: false },
|
|
212
|
+
{ code: 'sq', label: 'Albanian', native: 'Shqip', flag: '🇦🇱', rtl: false },
|
|
213
|
+
{ code: 'la', label: 'Latin', native: 'Latina', flag: '🇻🇦', rtl: false },
|
|
214
|
+
{ code: 'eo', label: 'Esperanto', native: 'Esperanto', flag: '🇪🇺', rtl: false },
|
|
215
|
+
{ code: 'yi', label: 'Yiddish', native: 'ייִדיש', flag: '🇮🇱', rtl: true },
|
|
216
|
+
{ code: 'hy', label: 'Armenian', native: 'Հայերեն', flag: '🇦🇲', rtl: false },
|
|
217
|
+
{ code: 'ka', label: 'Georgian', native: 'ქართული', flag: '🇬🇪', rtl: false },
|
|
218
|
+
{ code: 'os', label: 'Ossetian', native: 'Ирон æвзаг', flag: '🇬🇪', rtl: false },
|
|
219
|
+
{ code: 'ab', label: 'Abkhaz', native: 'Аԥсшәа', flag: '🇬🇪', rtl: false },
|
|
220
|
+
{ code: 'ce', label: 'Chechen', native: 'Нохчийн мотт', flag: '🇷🇺', rtl: false },
|
|
221
|
+
{ code: 'az', label: 'Azerbaijani', native: 'Azərbaycan dili', flag: '🇦🇿', rtl: false },
|
|
222
|
+
{ code: 'kk', label: 'Kazakh', native: 'Қазақ тілі', flag: '🇰🇿', rtl: false },
|
|
223
|
+
{ code: 'ky', label: 'Kyrgyz', native: 'Кыргызча', flag: '🇰🇬', rtl: false },
|
|
224
|
+
{ code: 'uz', label: 'Uzbek', native: 'Oʻzbekcha', flag: '🇺🇿', rtl: false },
|
|
225
|
+
{ code: 'tk', label: 'Turkmen', native: 'Türkmençe', flag: '🇹🇲', rtl: false },
|
|
226
|
+
{ code: 'tt', label: 'Tatar', native: 'Татар теле', flag: '🇷🇺', rtl: false },
|
|
227
|
+
{ code: 'ba', label: 'Bashkir', native: 'Башҡортса', flag: '🇷🇺', rtl: false },
|
|
228
|
+
{ code: 'cv', label: 'Chuvash', native: 'Чӑвашла', flag: '🇷🇺', rtl: false },
|
|
229
|
+
{ code: 'sah', label: 'Yakut', native: 'Саха тыла', flag: '🇷🇺', rtl: false },
|
|
230
|
+
{ code: 'ug', label: 'Uyghur', native: 'ئۇيغۇرچە', flag: '🇨🇳', rtl: true },
|
|
231
|
+
{ code: 'mn', label: 'Mongolian', native: 'Монгол хэл', flag: '🇲🇳', rtl: false },
|
|
232
|
+
{ code: 'bo', label: 'Tibetan', native: 'བོད་སྐད་', flag: '🇨🇳', rtl: false },
|
|
233
|
+
{ code: 'dz', label: 'Dzongkha', native: 'རྫོང་ཁ', flag: '🇧🇹', rtl: false },
|
|
234
|
+
{ code: 'fa-AF', label: 'Dari', native: 'دری', flag: '🇦🇫', rtl: true },
|
|
235
|
+
{ code: 'ps', label: 'Pashto', native: 'پښتو', flag: '🇦🇫', rtl: true },
|
|
236
|
+
{ code: 'ku', label: 'Kurmanji Kurdish', native: 'Kurmancî', flag: '🇹🇷', rtl: false },
|
|
237
|
+
{ code: 'ckb', label: 'Sorani Kurdish', native: 'کوردیی ناوەندی', flag: '🇮🇶', rtl: true },
|
|
238
|
+
{ code: 'syr', label: 'Syriac', native: 'ܣܘܪܝܝܐ', flag: '🇮🇶', rtl: true },
|
|
239
|
+
{ code: 'te', label: 'Telugu', native: 'తెలుగు', flag: '🇮🇳', rtl: false },
|
|
240
|
+
{ code: 'mr', label: 'Marathi', native: 'मराठी', flag: '🇮🇳', rtl: false },
|
|
241
|
+
{ code: 'gu', label: 'Gujarati', native: 'ગુજરાતી', flag: '🇮🇳', rtl: false },
|
|
242
|
+
{ code: 'kn', label: 'Kannada', native: 'ಕನ್ನಡ', flag: '🇮🇳', rtl: false },
|
|
243
|
+
{ code: 'ml', label: 'Malayalam', native: 'മലയാളം', flag: '🇮🇳', rtl: false },
|
|
244
|
+
{ code: 'or', label: 'Odia', native: 'ଓଡ଼ିଆ', flag: '🇮🇳', rtl: false },
|
|
245
|
+
{ code: 'pa', label: 'Punjabi', native: 'ਪੰਜਾਬੀ', flag: '🇮🇳', rtl: false },
|
|
246
|
+
{ code: 'pa-Arab', label: 'Western Punjabi', native: 'پنجابی', flag: '🇵🇰', rtl: true },
|
|
247
|
+
{ code: 'as', label: 'Assamese', native: 'অসমীয়া', flag: '🇮🇳', rtl: false },
|
|
248
|
+
{ code: 'mai', label: 'Maithili', native: 'मैथिली', flag: '🇮🇳', rtl: false },
|
|
249
|
+
{ code: 'bho', label: 'Bhojpuri', native: 'भोजपुरी', flag: '🇮🇳', rtl: false },
|
|
250
|
+
{ code: 'sat', label: 'Santali', native: 'ᱥᱟᱱᱛᱟᱲᱤ', flag: '🇮🇳', rtl: false },
|
|
251
|
+
{ code: 'ks', label: 'Kashmiri', native: 'کٲشُر', flag: '🇮🇳', rtl: true },
|
|
252
|
+
{ code: 'sa', label: 'Sanskrit', native: 'संस्कृतम्', flag: '🇮🇳', rtl: false },
|
|
253
|
+
{ code: 'ne', label: 'Nepali', native: 'नेपाली', flag: '🇳🇵', rtl: false },
|
|
254
|
+
{ code: 'si', label: 'Sinhala', native: 'සිංහල', flag: '🇱🇰', rtl: false },
|
|
255
|
+
{ code: 'sd', label: 'Sindhi', native: 'سنڌي', flag: '🇵🇰', rtl: true },
|
|
256
|
+
{ code: 'dv', label: 'Divehi', native: 'ދިވެހި', flag: '🇲🇻', rtl: true },
|
|
257
|
+
{ code: 'km', label: 'Khmer', native: 'ភាសាខ្មែរ', flag: '🇰🇭', rtl: false },
|
|
258
|
+
{ code: 'lo', label: 'Lao', native: 'ລາວ', flag: '🇱🇦', rtl: false },
|
|
259
|
+
{ code: 'my', label: 'Burmese', native: 'မြန်မာဘာသာ', flag: '🇲🇲', rtl: false },
|
|
260
|
+
{ code: 'jv', label: 'Javanese', native: 'Basa Jawa', flag: '🇮🇩', rtl: false },
|
|
261
|
+
{ code: 'su', label: 'Sundanese', native: 'Basa Sunda', flag: '🇮🇩', rtl: false },
|
|
262
|
+
{ code: 'fil', label: 'Filipino', native: 'Filipino', flag: '🇵🇭', rtl: false },
|
|
263
|
+
{ code: 'tl', label: 'Tagalog', native: 'Tagalog', flag: '🇵🇭', rtl: false },
|
|
264
|
+
{ code: 'ceb', label: 'Cebuano', native: 'Binisaya', flag: '🇵🇭', rtl: false },
|
|
265
|
+
{ code: 'ilo', label: 'Ilocano', native: 'Ilokano', flag: '🇵🇭', rtl: false },
|
|
266
|
+
{ code: 'war', label: 'Waray', native: 'Winaray', flag: '🇵🇭', rtl: false },
|
|
267
|
+
{ code: 'sw', label: 'Swahili', native: 'Kiswahili', flag: '🇰🇪', rtl: false },
|
|
268
|
+
{ code: 'am', label: 'Amharic', native: 'አማርኛ', flag: '🇪🇹', rtl: false },
|
|
269
|
+
{ code: 'ti', label: 'Tigrinya', native: 'ትግርኛ', flag: '🇪🇷', rtl: false },
|
|
270
|
+
{ code: 'om', label: 'Oromo', native: 'Afaan Oromoo', flag: '🇪🇹', rtl: false },
|
|
271
|
+
{ code: 'so', label: 'Somali', native: 'Soomaali', flag: '🇸🇴', rtl: false },
|
|
272
|
+
{ code: 'ha', label: 'Hausa', native: 'Hausa', flag: '🇳🇬', rtl: false },
|
|
273
|
+
{ code: 'yo', label: 'Yoruba', native: 'Yorùbá', flag: '🇳🇬', rtl: false },
|
|
274
|
+
{ code: 'ig', label: 'Igbo', native: 'Igbo', flag: '🇳🇬', rtl: false },
|
|
275
|
+
{ code: 'af', label: 'Afrikaans', native: 'Afrikaans', flag: '🇿🇦', rtl: false },
|
|
276
|
+
{ code: 'zu', label: 'Zulu', native: 'isiZulu', flag: '🇿🇦', rtl: false },
|
|
277
|
+
{ code: 'xh', label: 'Xhosa', native: 'isiXhosa', flag: '🇿🇦', rtl: false },
|
|
278
|
+
{ code: 'st', label: 'Sesotho', native: 'Sesotho', flag: '🇱🇸', rtl: false },
|
|
279
|
+
{ code: 'nso', label: 'Sepedi', native: 'Sepedi', flag: '🇿🇦', rtl: false },
|
|
280
|
+
{ code: 'tn', label: 'Setswana', native: 'Setswana', flag: '🇧🇼', rtl: false },
|
|
281
|
+
{ code: 'ts', label: 'Xitsonga', native: 'Xitsonga', flag: '🇿🇦', rtl: false },
|
|
282
|
+
{ code: 've', label: 'Tshivenda', native: 'Tshivenḓa', flag: '🇿🇦', rtl: false },
|
|
283
|
+
{ code: 'ss', label: 'Swati', native: 'siSwati', flag: '🇸🇿', rtl: false },
|
|
284
|
+
{ code: 'nr', label: 'Southern Ndebele', native: 'isiNdebele', flag: '🇿🇦', rtl: false },
|
|
285
|
+
{ code: 'sn', label: 'Shona', native: 'chiShona', flag: '🇿🇼', rtl: false },
|
|
286
|
+
{ code: 'ny', label: 'Chichewa', native: 'Chichewa', flag: '🇲🇼', rtl: false },
|
|
287
|
+
{ code: 'rw', label: 'Kinyarwanda', native: 'Ikinyarwanda', flag: '🇷🇼', rtl: false },
|
|
288
|
+
{ code: 'rn', label: 'Kirundi', native: 'Ikirundi', flag: '🇧🇮', rtl: false },
|
|
289
|
+
{ code: 'lg', label: 'Luganda', native: 'Luganda', flag: '🇺🇬', rtl: false },
|
|
290
|
+
{ code: 'ln', label: 'Lingala', native: 'Lingála', flag: '🇨🇩', rtl: false },
|
|
291
|
+
{ code: 'kg', label: 'Kikongo', native: 'Kikongo', flag: '🇨🇩', rtl: false },
|
|
292
|
+
{ code: 'wo', label: 'Wolof', native: 'Wolof', flag: '🇸🇳', rtl: false },
|
|
293
|
+
{ code: 'ff', label: 'Fula', native: 'Pulaar', flag: '🇸🇳', rtl: false },
|
|
294
|
+
{ code: 'bm', label: 'Bambara', native: 'Bamanankan', flag: '🇲🇱', rtl: false },
|
|
295
|
+
{ code: 'ak', label: 'Akan', native: 'Akan', flag: '🇬🇭', rtl: false },
|
|
296
|
+
{ code: 'tw', label: 'Twi', native: 'Twi', flag: '🇬🇭', rtl: false },
|
|
297
|
+
{ code: 'ee', label: 'Ewe', native: 'Eʋegbe', flag: '🇬🇭', rtl: false },
|
|
298
|
+
{ code: 'mg', label: 'Malagasy', native: 'Malagasy', flag: '🇲🇬', rtl: false },
|
|
299
|
+
{ code: 'kab', label: 'Kabyle', native: 'Taqbaylit', flag: '🇩🇿', rtl: false },
|
|
300
|
+
{ code: 'tzm', label: 'Tamazight', native: 'ⵜⴰⵎⴰⵣⵉⵖⵜ', flag: '🇲🇦', rtl: false },
|
|
301
|
+
{ code: 'sg', label: 'Sango', native: 'Sängö', flag: '🇨🇫', rtl: false },
|
|
302
|
+
{ code: 'ht', label: 'Haitian Creole', native: 'Kreyòl ayisyen', flag: '🇭🇹', rtl: false },
|
|
303
|
+
{ code: 'pap', label: 'Papiamento', native: 'Papiamentu', flag: '🇦🇼', rtl: false },
|
|
304
|
+
{ code: 'srn', label: 'Sranan Tongo', native: 'Sranantongo', flag: '🇸🇷', rtl: false },
|
|
305
|
+
{ code: 'qu', label: 'Quechua', native: 'Runa Simi', flag: '🇵🇪', rtl: false },
|
|
306
|
+
{ code: 'gn', label: 'Guarani', native: 'Avañe\'ẽ', flag: '🇵🇾', rtl: false },
|
|
307
|
+
{ code: 'ay', label: 'Aymara', native: 'Aymar aru', flag: '🇧🇴', rtl: false },
|
|
308
|
+
{ code: 'nah', label: 'Nahuatl', native: 'Nāhuatl', flag: '🇲🇽', rtl: false },
|
|
309
|
+
{ code: 'chr', label: 'Cherokee', native: 'ᏣᎳᎩ', flag: '🇺🇸', rtl: false },
|
|
310
|
+
{ code: 'nv', label: 'Navajo', native: 'Diné bizaad', flag: '🇺🇸', rtl: false },
|
|
311
|
+
{ code: 'iu', label: 'Inuktitut', native: 'ᐃᓄᒃᑎᑐᑦ', flag: '🇨🇦', rtl: false },
|
|
312
|
+
{ code: 'mi', label: 'Maori', native: 'Te reo Māori', flag: '🇳🇿', rtl: false },
|
|
313
|
+
{ code: 'sm', label: 'Samoan', native: 'Gagana Sāmoa', flag: '🇼🇸', rtl: false },
|
|
314
|
+
{ code: 'to', label: 'Tongan', native: 'Lea faka-Tonga', flag: '🇹🇴', rtl: false },
|
|
315
|
+
{ code: 'fj', label: 'Fijian', native: 'Na Vosa Vakaviti', flag: '🇫🇯', rtl: false },
|
|
316
|
+
{ code: 'haw', label: 'Hawaiian', native: 'ʻŌlelo Hawaiʻi', flag: '🇺🇸', rtl: false },
|
|
317
|
+
{ code: 'ty', label: 'Tahitian', native: 'Reo Tahiti', flag: '🇵🇫', rtl: false },
|
|
318
|
+
{ code: 'ch', label: 'Chamorro', native: 'Chamoru', flag: '🇬🇺', rtl: false },
|
|
319
|
+
{ code: 'mh', label: 'Marshallese', native: 'Kajin M̧ajeļ', flag: '🇲🇭', rtl: false },
|
|
320
|
+
{ code: 'tpi', label: 'Tok Pisin', native: 'Tok Pisin', flag: '🇵🇬', rtl: false }
|
|
321
|
+
];
|
|
322
|
+
const LANGUAGE_ALIASES = [
|
|
323
|
+
['zh', 'zh-Hans'],
|
|
324
|
+
['zh-cn', 'zh-Hans'],
|
|
325
|
+
['zh-sg', 'zh-Hans'],
|
|
326
|
+
['cmn', 'zh-Hans'],
|
|
327
|
+
['zh-tw', 'zh-Hant'],
|
|
328
|
+
['zh-mo', 'zh-HK'],
|
|
329
|
+
['iw', 'he'],
|
|
330
|
+
['in', 'id'],
|
|
331
|
+
['ji', 'yi'],
|
|
332
|
+
['mo', 'ro'],
|
|
333
|
+
['sh', 'sr-Latn'],
|
|
334
|
+
['sr-latn-rs', 'sr-Latn'],
|
|
335
|
+
['prs', 'fa-AF'],
|
|
336
|
+
['fa-af', 'fa-AF'],
|
|
337
|
+
['kmr', 'ku'],
|
|
338
|
+
['pt-pt', 'pt'],
|
|
339
|
+
['es-mx', 'es-419'],
|
|
340
|
+
['es-ar', 'es-419'],
|
|
341
|
+
['es-co', 'es-419'],
|
|
342
|
+
['es-cl', 'es-419']
|
|
343
|
+
];
|
|
344
|
+
const LANGUAGE_SUBTAG_PATTERN = /[-_]/;
|
|
345
|
+
|
|
346
|
+
const PAGINATION = {
|
|
347
|
+
MAX_LIMIT: 100,
|
|
348
|
+
DEFAULT_LIMIT: 25
|
|
349
|
+
};
|
|
350
|
+
const CURSOR_STYLES = {
|
|
351
|
+
CURSOR: { PARAM: 'cursor', FIELD: 'nextCursor' },
|
|
352
|
+
PAGE_TOKEN: { PARAM: 'pageToken', FIELD: 'nextPageToken' }
|
|
353
|
+
};
|
|
354
|
+
const QUERY_KEYS = {
|
|
355
|
+
LIMIT: 'limit',
|
|
356
|
+
STATUS: 'status',
|
|
357
|
+
FROM: 'from',
|
|
358
|
+
TO: 'to',
|
|
359
|
+
TIMEZONE: 'timezone',
|
|
360
|
+
ENABLED: 'enabled',
|
|
361
|
+
RULE_ID: 'ruleId',
|
|
362
|
+
THREAD_ID: 'threadId',
|
|
363
|
+
FOLDER: 'folder',
|
|
364
|
+
QUERY: 'query',
|
|
365
|
+
LABEL_IDS: 'labelIds',
|
|
366
|
+
OPENED: 'opened',
|
|
367
|
+
CLICKED: 'clicked',
|
|
368
|
+
DAYS: 'days',
|
|
369
|
+
MINUTES: 'minutes',
|
|
370
|
+
GRAIN: 'grain',
|
|
371
|
+
OFFSET_MINUTES: 'offsetMinutes',
|
|
372
|
+
INCLUDE_MACHINE: 'includeMachine',
|
|
373
|
+
REASSIGN_TO: 'reassignTo'
|
|
374
|
+
};
|
|
375
|
+
const LIST_SEPARATOR = ',';
|
|
376
|
+
|
|
377
|
+
const PATHS = {
|
|
378
|
+
PING: '/ping',
|
|
379
|
+
KEYS_SELF: '/keys/self',
|
|
380
|
+
ADDRESSES: '/addresses',
|
|
381
|
+
LANGUAGES: '/languages',
|
|
382
|
+
EMAILS: '/emails',
|
|
383
|
+
EMAILS_BATCH: '/emails/batch',
|
|
384
|
+
EMAILS_TRANSLATE: '/emails/translate',
|
|
385
|
+
THREADS: '/threads',
|
|
386
|
+
DRAFTS: '/drafts',
|
|
387
|
+
LABELS: '/labels',
|
|
388
|
+
CONTACTS: '/contacts',
|
|
389
|
+
DOMAINS: '/domains',
|
|
390
|
+
WEBHOOKS: '/webhooks',
|
|
391
|
+
TEMPLATES: '/templates',
|
|
392
|
+
RULES: '/rules',
|
|
393
|
+
RULES_RUNS: '/rules/runs',
|
|
394
|
+
RULES_REORDER: '/rules/reorder',
|
|
395
|
+
TRACKING: '/tracking',
|
|
396
|
+
TRACKING_STATS: '/tracking/stats',
|
|
397
|
+
CALENDAR_EVENTS: '/calendar/events',
|
|
398
|
+
SETTINGS: '/settings',
|
|
399
|
+
ROLES: '/roles',
|
|
400
|
+
ROLES_PERMISSIONS: '/roles/permissions',
|
|
401
|
+
MEMBERS: '/members',
|
|
402
|
+
TEMP_MAIL_DOMAINS: '/temp-mail/domains',
|
|
403
|
+
TEMP_MAIL_INBOXES: '/temp-mail/inboxes'
|
|
404
|
+
};
|
|
405
|
+
const SEGMENTS = {
|
|
406
|
+
ADDRESSES: 'addresses',
|
|
407
|
+
ATTACHMENTS: 'attachments',
|
|
408
|
+
CANCEL: 'cancel',
|
|
409
|
+
CLICKS: 'clicks',
|
|
410
|
+
DELIVERIES: 'deliveries',
|
|
411
|
+
EVENTS: 'events',
|
|
412
|
+
EXTEND: 'extend',
|
|
413
|
+
ICS: 'ics',
|
|
414
|
+
MESSAGES: 'messages',
|
|
415
|
+
OPENS: 'opens',
|
|
416
|
+
PREVIEW: 'preview',
|
|
417
|
+
ROTATE_SECRET: 'rotate-secret',
|
|
418
|
+
SEND: 'send',
|
|
419
|
+
SNOOZE: 'snooze',
|
|
420
|
+
TEST: 'test',
|
|
421
|
+
TRACKING: 'tracking',
|
|
422
|
+
TRASH: 'trash',
|
|
423
|
+
UNSNOOZE: 'unsnooze',
|
|
424
|
+
VERSIONS: 'versions'
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
const VERSION = version;
|
|
428
|
+
const NPM_REGISTRY_URL = 'https://registry.npmjs.org/@openemail/sdk';
|
|
429
|
+
const UPDATE_NOTICE = {
|
|
430
|
+
ENV_DISABLE: 'OPENEMAIL_DISABLE_UPDATE_NOTICE',
|
|
431
|
+
TIMEOUT_MS: 2_000,
|
|
432
|
+
PACKAGE: '@openemail/sdk',
|
|
433
|
+
CHANGELOG_URL: 'https://www.npmjs.com/package/@openemail/sdk?activeTab=versions',
|
|
434
|
+
ICON: 'ℹ'
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
const RULE_FIELDS = [
|
|
438
|
+
'from',
|
|
439
|
+
'from_domain',
|
|
440
|
+
'envelope_from',
|
|
441
|
+
'to',
|
|
442
|
+
'cc',
|
|
443
|
+
'bcc',
|
|
444
|
+
'recipient',
|
|
445
|
+
'reply_to',
|
|
446
|
+
'delivered_to',
|
|
447
|
+
'subject',
|
|
448
|
+
'body',
|
|
449
|
+
'header',
|
|
450
|
+
'list_id',
|
|
451
|
+
'attachment_name',
|
|
452
|
+
'attachment_type',
|
|
453
|
+
'has_attachment',
|
|
454
|
+
'attachment_size',
|
|
455
|
+
'message_size',
|
|
456
|
+
'spam',
|
|
457
|
+
'hour',
|
|
458
|
+
'weekday'
|
|
459
|
+
];
|
|
460
|
+
const RULE_OPERATORS = ['matches', 'contains', 'equals', 'starts_with', 'ends_with', 'gt', 'lt'];
|
|
461
|
+
const RULE_ACTIONS = [
|
|
462
|
+
'label',
|
|
463
|
+
'remove_label',
|
|
464
|
+
'archive',
|
|
465
|
+
'mark_read',
|
|
466
|
+
'star',
|
|
467
|
+
'spam',
|
|
468
|
+
'trash',
|
|
469
|
+
'forward',
|
|
470
|
+
'reply',
|
|
471
|
+
'block_sender',
|
|
472
|
+
'reject'
|
|
473
|
+
];
|
|
474
|
+
|
|
475
|
+
const API_SCOPES = [
|
|
476
|
+
'emails:send',
|
|
477
|
+
'emails:read',
|
|
478
|
+
'drafts:read',
|
|
479
|
+
'drafts:write',
|
|
480
|
+
'threads:read',
|
|
481
|
+
'threads:write',
|
|
482
|
+
'labels:read',
|
|
483
|
+
'labels:write',
|
|
484
|
+
'contacts:read',
|
|
485
|
+
'calendar:read',
|
|
486
|
+
'calendar:write',
|
|
487
|
+
'templates:read',
|
|
488
|
+
'templates:write',
|
|
489
|
+
'domains:read',
|
|
490
|
+
'domains:write',
|
|
491
|
+
'webhooks:read',
|
|
492
|
+
'webhooks:write',
|
|
493
|
+
'rules:read',
|
|
494
|
+
'rules:write',
|
|
495
|
+
'connections:read',
|
|
496
|
+
'members:read',
|
|
497
|
+
'members:write',
|
|
498
|
+
'roles:read',
|
|
499
|
+
'roles:write',
|
|
500
|
+
'settings:read',
|
|
501
|
+
'settings:write'
|
|
502
|
+
];
|
|
503
|
+
|
|
504
|
+
const WEBHOOK_EVENTS = [
|
|
505
|
+
'email.received',
|
|
506
|
+
'email.sent',
|
|
507
|
+
'email.failed',
|
|
508
|
+
'email.cancelled',
|
|
509
|
+
'email.scheduled',
|
|
510
|
+
'email.queued',
|
|
511
|
+
'email.bounced',
|
|
512
|
+
'email.complained',
|
|
513
|
+
'email.opened',
|
|
514
|
+
'email.clicked'
|
|
515
|
+
];
|
|
516
|
+
const WEBHOOK_HEADERS = {
|
|
517
|
+
SIGNATURE: 'X-OpenEmail-Signature',
|
|
518
|
+
EVENT: 'X-OpenEmail-Event',
|
|
519
|
+
DELIVERY: 'X-OpenEmail-Delivery'
|
|
520
|
+
};
|
|
521
|
+
const WEBHOOK_SIGNATURE = {
|
|
522
|
+
VERSION: 'v1',
|
|
523
|
+
TIMESTAMP: 't',
|
|
524
|
+
PAIR_SEPARATOR: ',',
|
|
525
|
+
VALUE_SEPARATOR: '=',
|
|
526
|
+
TOLERANCE_SECONDS: 300
|
|
527
|
+
};
|
|
528
|
+
const WEBHOOK_MESSAGES = {
|
|
529
|
+
MISSING_HEADER: 'Missing the X-OpenEmail-Signature header.',
|
|
530
|
+
MALFORMED_HEADER: 'X-OpenEmail-Signature is not in the form t=<seconds>,v1=<hex>.',
|
|
531
|
+
OUT_OF_TOLERANCE: 'Webhook timestamp is {{drift}}s out of tolerance ({{tolerance}}s).',
|
|
532
|
+
MISMATCH: 'Webhook signature does not match the payload.',
|
|
533
|
+
NO_WEBCRYPTO: 'No WebCrypto is available. Verifying a webhook signature needs globalThis.crypto.subtle, which Node 20+, Bun, Deno, Cloudflare Workers and browsers over HTTPS all provide.'
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
const PAGE_LIMITS = PAGINATION;
|
|
537
|
+
const WEBHOOK_SIGNATURE_HEADERS = WEBHOOK_HEADERS;
|
|
538
|
+
|
|
539
|
+
const CLIENT_REGISTRY_KEY = Symbol.for('openemail.sdk.client.v1');
|
|
540
|
+
const CLIENT_REGISTRY = globalThis;
|
|
541
|
+
|
|
542
|
+
const LOCAL_HOSTS = ['localhost', '127.0.0.1', '0.0.0.0', '::1'];
|
|
543
|
+
const ENDPOINT_SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
544
|
+
|
|
545
|
+
const RETRYABLE_STATUSES = new Set(RETRY.STATUSES);
|
|
546
|
+
const RETRYABLE_METHODS = new Set(RETRY.METHODS);
|
|
547
|
+
|
|
548
|
+
const IsApiKey = (value) => typeof value === 'string' && (value.startsWith(API_KEY_PREFIXES.LIVE) || value.startsWith(API_KEY_PREFIXES.TEST));
|
|
549
|
+
const ModeOf = (apiKey) => apiKey.startsWith(API_KEY_PREFIXES.TEST) ? API_KEY_MODES.TEST : API_KEY_MODES.LIVE;
|
|
550
|
+
const AssertApiKey = (apiKey, where) => {
|
|
551
|
+
if (!apiKey)
|
|
552
|
+
throw new Error(CLIENT_MESSAGES.API_KEY_REQUIRED);
|
|
553
|
+
if (!IsApiKey(apiKey))
|
|
554
|
+
throw new Error(`${where} ${CLIENT_MESSAGES.API_KEY_SHAPE}`);
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
const IsBrowser = () => {
|
|
558
|
+
const scope = globalThis;
|
|
559
|
+
if (scope.Deno !== undefined)
|
|
560
|
+
return false;
|
|
561
|
+
return typeof scope.window !== 'undefined' && typeof scope.document !== 'undefined';
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
class OpenEmailApiError extends Error {
|
|
565
|
+
status;
|
|
566
|
+
type;
|
|
567
|
+
code;
|
|
568
|
+
param;
|
|
569
|
+
docUrl;
|
|
570
|
+
requestId;
|
|
571
|
+
retryAfterSeconds;
|
|
572
|
+
constructor(props) {
|
|
573
|
+
const { message, status, type, code, param, docUrl, requestId, retryAfterSeconds } = props;
|
|
574
|
+
super(message);
|
|
575
|
+
this.name = ERROR_NAMES.API;
|
|
576
|
+
this.status = status;
|
|
577
|
+
this.type = type;
|
|
578
|
+
this.code = code;
|
|
579
|
+
this.param = param;
|
|
580
|
+
this.docUrl = docUrl;
|
|
581
|
+
this.requestId = requestId;
|
|
582
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
583
|
+
}
|
|
584
|
+
get isAuth() {
|
|
585
|
+
return this.type === ERROR_TYPE.AUTHENTICATION;
|
|
586
|
+
}
|
|
587
|
+
get isPermission() {
|
|
588
|
+
return this.type === ERROR_TYPE.PERMISSION;
|
|
589
|
+
}
|
|
590
|
+
get isScopeMissing() {
|
|
591
|
+
return this.code === ERROR_CODES.INSUFFICIENT_SCOPE;
|
|
592
|
+
}
|
|
593
|
+
get isInvalidRequest() {
|
|
594
|
+
return this.type === ERROR_TYPE.INVALID_REQUEST;
|
|
595
|
+
}
|
|
596
|
+
get isValidation() {
|
|
597
|
+
return this.type === ERROR_TYPE.VALIDATION;
|
|
598
|
+
}
|
|
599
|
+
get isNotFound() {
|
|
600
|
+
return this.type === ERROR_TYPE.NOT_FOUND;
|
|
601
|
+
}
|
|
602
|
+
get isConflict() {
|
|
603
|
+
return this.type === ERROR_TYPE.CONFLICT;
|
|
604
|
+
}
|
|
605
|
+
get isRateLimited() {
|
|
606
|
+
return this.type === ERROR_TYPE.RATE_LIMIT;
|
|
607
|
+
}
|
|
608
|
+
get isServerError() {
|
|
609
|
+
return this.status !== null && this.status >= 500;
|
|
610
|
+
}
|
|
611
|
+
get isRetryable() {
|
|
612
|
+
return this.status !== null && RETRY.STATUSES.includes(this.status);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
class OpenEmailNetworkError extends Error {
|
|
616
|
+
cause;
|
|
617
|
+
isTimeout;
|
|
618
|
+
constructor(message, cause, isTimeout = false) {
|
|
619
|
+
super(message);
|
|
620
|
+
this.name = ERROR_NAMES.NETWORK;
|
|
621
|
+
this.cause = cause;
|
|
622
|
+
this.isTimeout = isTimeout;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
let checked = false;
|
|
627
|
+
const Parts = (value) => value
|
|
628
|
+
.trim()
|
|
629
|
+
.replace(/^v/, '')
|
|
630
|
+
.split('.')
|
|
631
|
+
.map(part => Number.parseInt(part, 10));
|
|
632
|
+
const IsNewer = (candidate, current) => {
|
|
633
|
+
const left = Parts(candidate);
|
|
634
|
+
const right = Parts(current);
|
|
635
|
+
if (left.length !== 3 || right.length !== 3)
|
|
636
|
+
return false;
|
|
637
|
+
if (left.some(Number.isNaN) || right.some(Number.isNaN))
|
|
638
|
+
return false;
|
|
639
|
+
for (let index = 0; index < 3; index++) {
|
|
640
|
+
if (left[index] > right[index])
|
|
641
|
+
return true;
|
|
642
|
+
if (left[index] < right[index])
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
return false;
|
|
646
|
+
};
|
|
647
|
+
const Disabled = () => {
|
|
648
|
+
if (typeof process === 'undefined')
|
|
649
|
+
return true;
|
|
650
|
+
const value = process.env?.[UPDATE_NOTICE.ENV_DISABLE];
|
|
651
|
+
if (value && value.length > 0)
|
|
652
|
+
return true;
|
|
653
|
+
return process.stdout?.isTTY !== true;
|
|
654
|
+
};
|
|
655
|
+
const LatestVersion = async (fetchImpl = globalThis.fetch) => {
|
|
656
|
+
const controller = new AbortController();
|
|
657
|
+
const timer = setTimeout(() => controller.abort(), UPDATE_NOTICE.TIMEOUT_MS);
|
|
658
|
+
timer.unref?.();
|
|
659
|
+
try {
|
|
660
|
+
const response = await fetchImpl(NPM_REGISTRY_URL, { signal: controller.signal });
|
|
661
|
+
if (!response.ok)
|
|
662
|
+
return null;
|
|
663
|
+
const payload = await response.json();
|
|
664
|
+
const latest = payload?.['dist-tags']?.latest;
|
|
665
|
+
return typeof latest === 'string' ? latest : null;
|
|
666
|
+
}
|
|
667
|
+
catch {
|
|
668
|
+
return null;
|
|
669
|
+
}
|
|
670
|
+
finally {
|
|
671
|
+
clearTimeout(timer);
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
const UpdateNotice = (fetchImpl) => {
|
|
675
|
+
if (checked || Disabled())
|
|
676
|
+
return;
|
|
677
|
+
checked = true;
|
|
678
|
+
void LatestVersion(fetchImpl)
|
|
679
|
+
.then(latest => {
|
|
680
|
+
if (!latest || !IsNewer(latest, VERSION))
|
|
681
|
+
return;
|
|
682
|
+
console.warn(`${UPDATE_NOTICE.ICON} ${UPDATE_NOTICE.PACKAGE} ${latest} is available, you are on ${VERSION}. ${UPDATE_NOTICE.CHANGELOG_URL}.`);
|
|
683
|
+
})
|
|
684
|
+
.catch(() => undefined);
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
const Backoff = (attempt) => {
|
|
688
|
+
const ceiling = Math.min(RETRY.MAX_BACKOFF_MS, RETRY.BASE_BACKOFF_MS * 2 ** attempt);
|
|
689
|
+
return Math.round(ceiling * (0.5 + Math.random() * 0.5));
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
const BuildUrl = (baseUrl, path, query) => {
|
|
693
|
+
const url = `${baseUrl.replace(/\/+$/, '')}${path}`;
|
|
694
|
+
if (!query)
|
|
695
|
+
return url;
|
|
696
|
+
const params = new URLSearchParams();
|
|
697
|
+
for (const [key, value] of Object.entries(query)) {
|
|
698
|
+
if (value === undefined || value === null || value === '')
|
|
699
|
+
continue;
|
|
700
|
+
params.append(key, String(value));
|
|
701
|
+
}
|
|
702
|
+
const serialized = params.toString();
|
|
703
|
+
return serialized ? `${url}?${serialized}` : url;
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
const Deadline = (timeoutMs, signal) => {
|
|
707
|
+
const controller = new AbortController();
|
|
708
|
+
let expired = false;
|
|
709
|
+
const timer = timeoutMs > 0
|
|
710
|
+
? setTimeout(() => {
|
|
711
|
+
const reason = new Error(`Request timed out after ${timeoutMs}ms.`);
|
|
712
|
+
reason.name = ERROR_NAMES.TIMEOUT;
|
|
713
|
+
expired = true;
|
|
714
|
+
controller.abort(reason);
|
|
715
|
+
}, timeoutMs)
|
|
716
|
+
: undefined;
|
|
717
|
+
const OnAbort = () => controller.abort(signal?.reason);
|
|
718
|
+
if (signal?.aborted)
|
|
719
|
+
controller.abort(signal.reason);
|
|
720
|
+
else
|
|
721
|
+
signal?.addEventListener('abort', OnAbort, { once: true });
|
|
722
|
+
return {
|
|
723
|
+
signal: controller.signal,
|
|
724
|
+
timedOut: () => expired,
|
|
725
|
+
release: () => {
|
|
726
|
+
if (timer !== undefined)
|
|
727
|
+
clearTimeout(timer);
|
|
728
|
+
signal?.removeEventListener('abort', OnAbort);
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
const IdempotencyKey = () => {
|
|
734
|
+
const source = globalThis.crypto;
|
|
735
|
+
if (typeof source?.randomUUID === 'function')
|
|
736
|
+
return `${IDEMPOTENCY_KEY_PREFIX}${source.randomUUID()}`;
|
|
737
|
+
if (typeof source?.getRandomValues === 'function') {
|
|
738
|
+
const bytes = source.getRandomValues(new Uint8Array(16));
|
|
739
|
+
return `${IDEMPOTENCY_KEY_PREFIX}${Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('')}`;
|
|
740
|
+
}
|
|
741
|
+
return `${IDEMPOTENCY_KEY_PREFIX}${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 14)}`;
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
const RetryAfter = (value) => {
|
|
745
|
+
if (!value)
|
|
746
|
+
return undefined;
|
|
747
|
+
const seconds = Number(value);
|
|
748
|
+
if (Number.isFinite(seconds) && seconds >= 0)
|
|
749
|
+
return seconds;
|
|
750
|
+
const at = Date.parse(value);
|
|
751
|
+
if (!Number.isNaN(at))
|
|
752
|
+
return Math.max(0, Math.ceil((at - Date.now()) / 1000));
|
|
753
|
+
return undefined;
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
const Sleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
757
|
+
if (signal?.aborted) {
|
|
758
|
+
reject(new OpenEmailNetworkError('Request aborted by the caller.', signal.reason));
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
const onAbort = () => {
|
|
762
|
+
clearTimeout(timer);
|
|
763
|
+
reject(new OpenEmailNetworkError('Request aborted by the caller.', signal?.reason));
|
|
764
|
+
};
|
|
765
|
+
const timer = setTimeout(() => {
|
|
766
|
+
signal?.removeEventListener('abort', onAbort);
|
|
767
|
+
resolve();
|
|
768
|
+
}, ms);
|
|
769
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
const IsErrorType = (value) => typeof value === 'string' && ERROR_TYPES.includes(value);
|
|
773
|
+
const TypeForStatus = (status) => {
|
|
774
|
+
if (status >= 500)
|
|
775
|
+
return ERROR_TYPE.API;
|
|
776
|
+
const mapped = ERROR_TYPE_BY_STATUS[status];
|
|
777
|
+
return IsErrorType(mapped) ? mapped : ERROR_TYPE.INVALID_REQUEST;
|
|
778
|
+
};
|
|
779
|
+
const ReadPayload = (body) => {
|
|
780
|
+
if (typeof body !== 'object' || body === null || !('error' in body))
|
|
781
|
+
return {};
|
|
782
|
+
const error = body.error;
|
|
783
|
+
return typeof error === 'object' && error !== null ? error : {};
|
|
784
|
+
};
|
|
785
|
+
const Text = (value) => typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
786
|
+
const ToErrorProps = (response, body, rawText, retryAfterSeconds) => {
|
|
787
|
+
const payload = ReadPayload(body);
|
|
788
|
+
return {
|
|
789
|
+
message: Text(payload.message)
|
|
790
|
+
?? (rawText ? rawText.slice(0, 300) : `The API responded ${response.status} with a body this client did not recognise.`),
|
|
791
|
+
status: response.status,
|
|
792
|
+
type: IsErrorType(payload.type) ? payload.type : TypeForStatus(response.status),
|
|
793
|
+
code: Text(payload.code) ?? ERROR_CODES.UNRECOGNISED_RESPONSE,
|
|
794
|
+
param: Text(payload.param),
|
|
795
|
+
docUrl: Text(payload.docUrl),
|
|
796
|
+
requestId: Text(payload.requestId) ?? response.headers.get(HEADER_KEYS.REQUEST_ID) ?? undefined,
|
|
797
|
+
retryAfterSeconds
|
|
798
|
+
};
|
|
799
|
+
};
|
|
800
|
+
|
|
801
|
+
const Describe = (error) => error instanceof Error ? error.message : String(error);
|
|
802
|
+
const ParseJson = (text) => {
|
|
803
|
+
if (!text)
|
|
804
|
+
return undefined;
|
|
805
|
+
try {
|
|
806
|
+
return JSON.parse(text);
|
|
807
|
+
}
|
|
808
|
+
catch {
|
|
809
|
+
return undefined;
|
|
810
|
+
}
|
|
811
|
+
};
|
|
812
|
+
const ValidBaseUrl = (value) => {
|
|
813
|
+
const trimmed = value.trim().replace(/\/+$/, '');
|
|
814
|
+
try {
|
|
815
|
+
const parsed = new URL(trimmed);
|
|
816
|
+
if (parsed.protocol === 'https:' || parsed.protocol === 'http:')
|
|
817
|
+
return trimmed;
|
|
818
|
+
}
|
|
819
|
+
catch { }
|
|
820
|
+
throw new Error(`${JSON.stringify(value)} ${CLIENT_MESSAGES.BASE_URL_SHAPE}`);
|
|
821
|
+
};
|
|
822
|
+
class Transport {
|
|
823
|
+
#apiKey;
|
|
824
|
+
#inboxToken;
|
|
825
|
+
baseUrl;
|
|
826
|
+
fetchImpl;
|
|
827
|
+
maxRetries;
|
|
828
|
+
timeoutMs;
|
|
829
|
+
userAgent;
|
|
830
|
+
headers;
|
|
831
|
+
constructor(options) {
|
|
832
|
+
if (options.apiKey !== undefined)
|
|
833
|
+
AssertApiKey(options.apiKey, '{ apiKey }');
|
|
834
|
+
const fetchImpl = options.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
835
|
+
if (!fetchImpl)
|
|
836
|
+
throw new Error(CLIENT_MESSAGES.NO_FETCH);
|
|
837
|
+
this.#apiKey = options.apiKey;
|
|
838
|
+
this.#inboxToken = options.inboxToken;
|
|
839
|
+
this.baseUrl = ValidBaseUrl(options.baseUrl ?? DEFAULTS.BASE_URL);
|
|
840
|
+
this.fetchImpl = fetchImpl;
|
|
841
|
+
this.maxRetries = Math.max(0, options.maxRetries ?? DEFAULTS.MAX_RETRIES);
|
|
842
|
+
this.timeoutMs = options.timeoutMs ?? DEFAULTS.TIMEOUT_MS;
|
|
843
|
+
this.userAgent = options.userAgent ?? DEFAULTS.USER_AGENT;
|
|
844
|
+
this.headers = { ...options.headers };
|
|
845
|
+
if (options.disableUpdateNotice !== true)
|
|
846
|
+
UpdateNotice(this.fetchImpl);
|
|
847
|
+
}
|
|
848
|
+
async request(path, options = {}) {
|
|
849
|
+
const method = (options.method ?? HTTP_METHODS.GET).toUpperCase();
|
|
850
|
+
const url = BuildUrl(this.baseUrl, path, options.query);
|
|
851
|
+
const headers = this.headersFor(options);
|
|
852
|
+
const body = options.body === undefined ? undefined : JSON.stringify(options.body);
|
|
853
|
+
const repeatable = options.repeatable ?? RETRYABLE_METHODS.has(method);
|
|
854
|
+
let attempt = 0;
|
|
855
|
+
for (;;) {
|
|
856
|
+
const outcome = await this.attempt(url, method, headers, body, options.signal);
|
|
857
|
+
if (outcome instanceof OpenEmailNetworkError) {
|
|
858
|
+
if (options.signal?.aborted || !repeatable || attempt >= this.maxRetries)
|
|
859
|
+
throw outcome;
|
|
860
|
+
await Sleep(Backoff(attempt), options.signal);
|
|
861
|
+
attempt += 1;
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
const { response, text } = outcome;
|
|
865
|
+
if (response.ok)
|
|
866
|
+
return this.parse(response, text, options.accept);
|
|
867
|
+
const retryAfter = RetryAfter(response.headers.get(HEADER_KEYS.RETRY_AFTER));
|
|
868
|
+
const honoured = retryAfter === undefined
|
|
869
|
+
? response.status !== RETRY.RATE_LIMIT_STATUS
|
|
870
|
+
: retryAfter * 1000 <= RETRY.MAX_HONOURED_RETRY_AFTER_MS;
|
|
871
|
+
if (repeatable && honoured && attempt < this.maxRetries && RETRYABLE_STATUSES.has(response.status)) {
|
|
872
|
+
await Sleep(retryAfter === undefined ? Backoff(attempt) : retryAfter * 1000, options.signal);
|
|
873
|
+
attempt += 1;
|
|
874
|
+
continue;
|
|
875
|
+
}
|
|
876
|
+
throw new OpenEmailApiError(ToErrorProps(response, ParseJson(text), text, retryAfter));
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
headersFor(options) {
|
|
880
|
+
const headers = {
|
|
881
|
+
...this.headers,
|
|
882
|
+
[HEADER_KEYS.ACCEPT]: options.accept ?? CONTENT_TYPES.JSON
|
|
883
|
+
};
|
|
884
|
+
if (!IsBrowser())
|
|
885
|
+
headers[HEADER_KEYS.USER_AGENT] = this.userAgent;
|
|
886
|
+
if (options.body !== undefined)
|
|
887
|
+
headers[HEADER_KEYS.CONTENT_TYPE] = CONTENT_TYPES.JSON;
|
|
888
|
+
if (options.anonymous !== true) {
|
|
889
|
+
if (options.apiKey !== undefined)
|
|
890
|
+
AssertApiKey(options.apiKey, '{ apiKey } on this call');
|
|
891
|
+
const credential = options.inboxToken ?? options.apiKey ?? this.#inboxToken ?? this.#apiKey;
|
|
892
|
+
if (credential)
|
|
893
|
+
headers[HEADER_KEYS.AUTHORIZATION] = `Bearer ${credential}`;
|
|
894
|
+
}
|
|
895
|
+
if (options.idempotent === true)
|
|
896
|
+
headers[HEADER_KEYS.IDEMPOTENCY_KEY] = options.idempotencyKey ?? IdempotencyKey();
|
|
897
|
+
return headers;
|
|
898
|
+
}
|
|
899
|
+
async attempt(url, method, headers, body, signal) {
|
|
900
|
+
const deadline = Deadline(this.timeoutMs, signal);
|
|
901
|
+
const Failure = (error, fallback) => {
|
|
902
|
+
if (signal?.aborted)
|
|
903
|
+
return new OpenEmailNetworkError('Request aborted by the caller.', error);
|
|
904
|
+
if (deadline.timedOut())
|
|
905
|
+
return new OpenEmailNetworkError(`Request timed out after ${this.timeoutMs}ms.`, error, true);
|
|
906
|
+
return new OpenEmailNetworkError(`${fallback}: ${Describe(error)}`, error);
|
|
907
|
+
};
|
|
908
|
+
try {
|
|
909
|
+
let response;
|
|
910
|
+
try {
|
|
911
|
+
response = await this.fetchImpl(url, { method, headers, body, signal: deadline.signal });
|
|
912
|
+
}
|
|
913
|
+
catch (error) {
|
|
914
|
+
return Failure(error, `Could not reach ${this.baseUrl}`);
|
|
915
|
+
}
|
|
916
|
+
try {
|
|
917
|
+
return { response, text: await response.text() };
|
|
918
|
+
}
|
|
919
|
+
catch (error) {
|
|
920
|
+
if (response.ok || signal?.aborted || deadline.timedOut())
|
|
921
|
+
return Failure(error, 'The response body could not be read');
|
|
922
|
+
return { response, text: '' };
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
finally {
|
|
926
|
+
deadline.release();
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
parse(response, text, accept) {
|
|
930
|
+
if (accept && accept !== CONTENT_TYPES.JSON)
|
|
931
|
+
return text;
|
|
932
|
+
if (!text)
|
|
933
|
+
return undefined;
|
|
934
|
+
const parsed = ParseJson(text);
|
|
935
|
+
if (parsed !== undefined)
|
|
936
|
+
return parsed;
|
|
937
|
+
throw new OpenEmailApiError({
|
|
938
|
+
message: `The API answered ${response.status} with a body that is not JSON.`,
|
|
939
|
+
status: response.status,
|
|
940
|
+
type: ERROR_TYPE.API,
|
|
941
|
+
code: ERROR_CODES.UNRECOGNISED_RESPONSE,
|
|
942
|
+
requestId: response.headers.get(HEADER_KEYS.REQUEST_ID) ?? undefined
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
const Addresses = (transport) => ({
|
|
948
|
+
list: async (options = {}) => {
|
|
949
|
+
const envelope = await transport.request(PATHS.ADDRESSES, { signal: options.signal, apiKey: options.apiKey });
|
|
950
|
+
return {
|
|
951
|
+
unrestricted: envelope.unrestricted,
|
|
952
|
+
addresses: envelope.data ?? [],
|
|
953
|
+
domains: envelope.domains ?? []
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
const FetchPage = async (transport, path, style, options = {}, query = {}) => {
|
|
959
|
+
const payload = await transport.request(path, {
|
|
960
|
+
query: { ...query, [QUERY_KEYS.LIMIT]: options.limit, [style.PARAM]: options.cursor },
|
|
961
|
+
signal: options.signal,
|
|
962
|
+
apiKey: options.apiKey
|
|
963
|
+
});
|
|
964
|
+
const next = payload?.[style.FIELD];
|
|
965
|
+
const nextCursor = typeof next === 'string' && next.length > 0 ? next : null;
|
|
966
|
+
return {
|
|
967
|
+
items: (payload?.data ?? []),
|
|
968
|
+
hasMore: typeof payload?.hasMore === 'boolean' ? payload.hasMore : nextCursor !== null,
|
|
969
|
+
nextCursor
|
|
970
|
+
};
|
|
971
|
+
};
|
|
972
|
+
const IteratePages = async function* (transport, path, style, options = {}, query = {}) {
|
|
973
|
+
let cursor = options.cursor;
|
|
974
|
+
for (;;) {
|
|
975
|
+
const page = await FetchPage(transport, path, style, { ...options, cursor }, query);
|
|
976
|
+
for (const item of page.items)
|
|
977
|
+
yield item;
|
|
978
|
+
if (!page.hasMore || !page.nextCursor || page.nextCursor === cursor)
|
|
979
|
+
return;
|
|
980
|
+
cursor = page.nextCursor;
|
|
981
|
+
}
|
|
982
|
+
};
|
|
983
|
+
const CollectAll = async (transport, path, style, options = {}, query = {}) => {
|
|
984
|
+
const all = [];
|
|
985
|
+
for await (const item of IteratePages(transport, path, style, options, query))
|
|
986
|
+
all.push(item);
|
|
987
|
+
return all;
|
|
988
|
+
};
|
|
989
|
+
const FetchList = async (transport, path, options = {}, query) => {
|
|
990
|
+
const payload = await transport.request(path, {
|
|
991
|
+
query,
|
|
992
|
+
signal: options.signal,
|
|
993
|
+
apiKey: options.apiKey
|
|
994
|
+
});
|
|
995
|
+
return (payload?.data ?? []);
|
|
996
|
+
};
|
|
997
|
+
|
|
998
|
+
const Segment = (value) => {
|
|
999
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
1000
|
+
throw new Error(CLIENT_MESSAGES.EMPTY_SEGMENT);
|
|
1001
|
+
if (/^\.+$/.test(value))
|
|
1002
|
+
throw new Error(`${JSON.stringify(value)} ${CLIENT_MESSAGES.DOT_SEGMENT}`);
|
|
1003
|
+
return encodeURIComponent(value);
|
|
1004
|
+
};
|
|
1005
|
+
const EmailPath = (id) => `${PATHS.EMAILS}/${Segment(id)}`;
|
|
1006
|
+
const EmailChildPath = (id, segment) => `${EmailPath(id)}/${segment}`;
|
|
1007
|
+
const ThreadPath = (id) => `${PATHS.THREADS}/${Segment(id)}`;
|
|
1008
|
+
const ThreadChildPath = (id, segment) => `${ThreadPath(id)}/${segment}`;
|
|
1009
|
+
const ThreadMessageAttachmentsPath = (id, messageId) => `${ThreadPath(id)}/${SEGMENTS.MESSAGES}/${Segment(messageId)}/${SEGMENTS.ATTACHMENTS}`;
|
|
1010
|
+
const DraftPath = (id) => `${PATHS.DRAFTS}/${Segment(id)}`;
|
|
1011
|
+
const LabelPath = (id) => `${PATHS.LABELS}/${Segment(id)}`;
|
|
1012
|
+
const ContactPath = (email) => `${PATHS.CONTACTS}/${Segment(email)}`;
|
|
1013
|
+
const DomainPath = (id) => `${PATHS.DOMAINS}/${Segment(id)}`;
|
|
1014
|
+
const WebhookPath = (id) => `${PATHS.WEBHOOKS}/${Segment(id)}`;
|
|
1015
|
+
const WebhookChildPath = (id, segment) => `${WebhookPath(id)}/${segment}`;
|
|
1016
|
+
const TemplatePath = (idOrSlug) => `${PATHS.TEMPLATES}/${Segment(idOrSlug)}`;
|
|
1017
|
+
const TemplateChildPath = (idOrSlug, segment) => `${TemplatePath(idOrSlug)}/${segment}`;
|
|
1018
|
+
const RulePath = (id) => `${PATHS.RULES}/${Segment(id)}`;
|
|
1019
|
+
const RuleChildPath = (id, segment) => `${RulePath(id)}/${segment}`;
|
|
1020
|
+
const TrackingPath = (id) => `${PATHS.TRACKING}/${Segment(id)}`;
|
|
1021
|
+
const TrackingChildPath = (id, segment) => `${TrackingPath(id)}/${segment}`;
|
|
1022
|
+
const CalendarEventPath = (id) => `${PATHS.CALENDAR_EVENTS}/${Segment(id)}`;
|
|
1023
|
+
const CalendarEventIcsPath = (id) => `${CalendarEventPath(id)}/${SEGMENTS.ICS}`;
|
|
1024
|
+
const RolePath = (id) => `${PATHS.ROLES}/${Segment(id)}`;
|
|
1025
|
+
const MemberPath = (userId) => `${PATHS.MEMBERS}/${Segment(userId)}`;
|
|
1026
|
+
const MemberAddressesPath = (userId) => `${MemberPath(userId)}/${SEGMENTS.ADDRESSES}`;
|
|
1027
|
+
const MemberAddressPath = (userId, addressId) => `${MemberAddressesPath(userId)}/${Segment(addressId)}`;
|
|
1028
|
+
const TempInboxPath = (id) => `${PATHS.TEMP_MAIL_INBOXES}/${Segment(id)}`;
|
|
1029
|
+
const TempInboxExtendPath = (id) => `${TempInboxPath(id)}/${SEGMENTS.EXTEND}`;
|
|
1030
|
+
const TempMessagesPath = (id) => `${TempInboxPath(id)}/${SEGMENTS.MESSAGES}`;
|
|
1031
|
+
const TempMessagePath = (id, messageId) => `${TempMessagesPath(id)}/${Segment(messageId)}`;
|
|
1032
|
+
const TempMessageAttachmentsPath = (id, messageId) => `${TempMessagePath(id, messageId)}/${SEGMENTS.ATTACHMENTS}`;
|
|
1033
|
+
|
|
1034
|
+
const ToBase64 = (bytes) => {
|
|
1035
|
+
const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
1036
|
+
let binary = '';
|
|
1037
|
+
for (let offset = 0; offset < view.length; offset += BASE64_CHUNK_SIZE) {
|
|
1038
|
+
binary += String.fromCharCode(...view.subarray(offset, offset + BASE64_CHUNK_SIZE));
|
|
1039
|
+
}
|
|
1040
|
+
return btoa(binary);
|
|
1041
|
+
};
|
|
1042
|
+
|
|
1043
|
+
const Many = (value) => {
|
|
1044
|
+
if (value === undefined)
|
|
1045
|
+
return undefined;
|
|
1046
|
+
return Array.isArray(value) ? value : [value];
|
|
1047
|
+
};
|
|
1048
|
+
const Instant = (value) => value instanceof Date ? value.toISOString() : value;
|
|
1049
|
+
const Attachment = (attachment) => ({
|
|
1050
|
+
filename: attachment.filename,
|
|
1051
|
+
content: typeof attachment.content === 'string' ? attachment.content : ToBase64(attachment.content),
|
|
1052
|
+
...(attachment.contentType === undefined ? {} : { contentType: attachment.contentType })
|
|
1053
|
+
});
|
|
1054
|
+
const EmailToWire = (body) => ({
|
|
1055
|
+
...body,
|
|
1056
|
+
to: Many(body.to),
|
|
1057
|
+
cc: Many(body.cc),
|
|
1058
|
+
bcc: Many(body.bcc),
|
|
1059
|
+
attachments: body.attachments?.map(Attachment),
|
|
1060
|
+
scheduledAt: Instant(body.scheduledAt)
|
|
1061
|
+
});
|
|
1062
|
+
const TemplateSendToWire = (body) => ({
|
|
1063
|
+
...body,
|
|
1064
|
+
to: Many(body.to),
|
|
1065
|
+
cc: Many(body.cc),
|
|
1066
|
+
bcc: Many(body.bcc),
|
|
1067
|
+
scheduledAt: Instant(body.scheduledAt)
|
|
1068
|
+
});
|
|
1069
|
+
const ToInstant = Instant;
|
|
1070
|
+
|
|
1071
|
+
const Calendar = (transport) => ({
|
|
1072
|
+
listEvents: (options) => FetchList(transport, PATHS.CALENDAR_EVENTS, options, {
|
|
1073
|
+
[QUERY_KEYS.FROM]: ToInstant(options.from),
|
|
1074
|
+
[QUERY_KEYS.TO]: ToInstant(options.to),
|
|
1075
|
+
[QUERY_KEYS.TIMEZONE]: options.timezone
|
|
1076
|
+
}),
|
|
1077
|
+
getEvent: (id, options = {}) => transport.request(CalendarEventPath(id), { signal: options.signal, apiKey: options.apiKey }),
|
|
1078
|
+
getEventIcs: (id, options = {}) => transport.request(CalendarEventIcsPath(id), {
|
|
1079
|
+
accept: CONTENT_TYPES.CALENDAR,
|
|
1080
|
+
signal: options.signal,
|
|
1081
|
+
apiKey: options.apiKey
|
|
1082
|
+
})
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
const Contacts = (transport) => ({
|
|
1086
|
+
list: (options = {}) => FetchList(transport, PATHS.CONTACTS, options, { [QUERY_KEYS.LIMIT]: options.limit }),
|
|
1087
|
+
get: (email, options = {}) => transport.request(ContactPath(email), { signal: options.signal, apiKey: options.apiKey })
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
const Domains = (transport) => ({
|
|
1091
|
+
list: (options = {}) => FetchList(transport, PATHS.DOMAINS, options),
|
|
1092
|
+
get: (id, options = {}) => transport.request(DomainPath(id), { signal: options.signal, apiKey: options.apiKey })
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
const ListQuery$4 = (options) => ({ [QUERY_KEYS.QUERY]: options.query });
|
|
1096
|
+
const Drafts = (transport) => ({
|
|
1097
|
+
list: (options = {}) => FetchPage(transport, PATHS.DRAFTS, CURSOR_STYLES.PAGE_TOKEN, options, ListQuery$4(options)),
|
|
1098
|
+
listAll: (options = {}) => CollectAll(transport, PATHS.DRAFTS, CURSOR_STYLES.PAGE_TOKEN, options, ListQuery$4(options)),
|
|
1099
|
+
iterate: (options = {}) => IteratePages(transport, PATHS.DRAFTS, CURSOR_STYLES.PAGE_TOKEN, options, ListQuery$4(options)),
|
|
1100
|
+
get: (id, options = {}) => transport.request(DraftPath(id), { signal: options.signal, apiKey: options.apiKey }),
|
|
1101
|
+
create: (body, options = {}) => transport.request(PATHS.DRAFTS, {
|
|
1102
|
+
method: HTTP_METHODS.POST,
|
|
1103
|
+
body,
|
|
1104
|
+
signal: options.signal,
|
|
1105
|
+
apiKey: options.apiKey
|
|
1106
|
+
}),
|
|
1107
|
+
update: (id, body, options = {}) => transport.request(DraftPath(id), {
|
|
1108
|
+
method: HTTP_METHODS.PATCH,
|
|
1109
|
+
body,
|
|
1110
|
+
signal: options.signal,
|
|
1111
|
+
apiKey: options.apiKey
|
|
1112
|
+
}),
|
|
1113
|
+
delete: (id, options = {}) => transport.request(DraftPath(id), {
|
|
1114
|
+
method: HTTP_METHODS.DELETE,
|
|
1115
|
+
signal: options.signal,
|
|
1116
|
+
apiKey: options.apiKey
|
|
1117
|
+
})
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
const ListQuery$3 = (options) => ({
|
|
1121
|
+
[QUERY_KEYS.STATUS]: Array.isArray(options.status) ? options.status.join(LIST_SEPARATOR) : options.status,
|
|
1122
|
+
[QUERY_KEYS.FROM]: options.from
|
|
1123
|
+
});
|
|
1124
|
+
const Emails = (transport) => ({
|
|
1125
|
+
send: (body, options = {}) => transport.request(PATHS.EMAILS, {
|
|
1126
|
+
method: HTTP_METHODS.POST,
|
|
1127
|
+
body: EmailToWire(body),
|
|
1128
|
+
idempotent: true,
|
|
1129
|
+
idempotencyKey: options.idempotencyKey,
|
|
1130
|
+
repeatable: true,
|
|
1131
|
+
signal: options.signal,
|
|
1132
|
+
apiKey: options.apiKey
|
|
1133
|
+
}),
|
|
1134
|
+
sendBatch: async (emails, options = {}) => {
|
|
1135
|
+
const result = await transport.request(PATHS.EMAILS_BATCH, {
|
|
1136
|
+
method: HTTP_METHODS.POST,
|
|
1137
|
+
body: { emails: emails.map(EmailToWire) },
|
|
1138
|
+
idempotent: true,
|
|
1139
|
+
idempotencyKey: options.idempotencyKey,
|
|
1140
|
+
repeatable: true,
|
|
1141
|
+
signal: options.signal,
|
|
1142
|
+
apiKey: options.apiKey
|
|
1143
|
+
});
|
|
1144
|
+
return { items: result.data, sent: result.sent, failed: result.failed };
|
|
1145
|
+
},
|
|
1146
|
+
translate: (body, options = {}) => transport.request(PATHS.EMAILS_TRANSLATE, {
|
|
1147
|
+
method: HTTP_METHODS.POST,
|
|
1148
|
+
body,
|
|
1149
|
+
signal: options.signal,
|
|
1150
|
+
apiKey: options.apiKey
|
|
1151
|
+
}),
|
|
1152
|
+
list: (options = {}) => FetchPage(transport, PATHS.EMAILS, CURSOR_STYLES.CURSOR, options, ListQuery$3(options)),
|
|
1153
|
+
listAll: (options = {}) => CollectAll(transport, PATHS.EMAILS, CURSOR_STYLES.CURSOR, options, ListQuery$3(options)),
|
|
1154
|
+
iterate: (options = {}) => IteratePages(transport, PATHS.EMAILS, CURSOR_STYLES.CURSOR, options, ListQuery$3(options)),
|
|
1155
|
+
get: (id, options = {}) => transport.request(EmailPath(id), { signal: options.signal, apiKey: options.apiKey }),
|
|
1156
|
+
listEvents: (id, options = {}) => FetchList(transport, EmailChildPath(id, SEGMENTS.EVENTS), options),
|
|
1157
|
+
getTracking: (id, options = {}) => transport.request(EmailChildPath(id, SEGMENTS.TRACKING), { signal: options.signal, apiKey: options.apiKey }),
|
|
1158
|
+
cancel: (id, options = {}) => transport.request(EmailChildPath(id, SEGMENTS.CANCEL), {
|
|
1159
|
+
method: HTTP_METHODS.POST,
|
|
1160
|
+
repeatable: true,
|
|
1161
|
+
signal: options.signal,
|
|
1162
|
+
apiKey: options.apiKey
|
|
1163
|
+
}),
|
|
1164
|
+
reschedule: (id, scheduledAt, options = {}) => transport.request(EmailPath(id), {
|
|
1165
|
+
method: HTTP_METHODS.PATCH,
|
|
1166
|
+
body: { scheduledAt: ToInstant(scheduledAt) },
|
|
1167
|
+
repeatable: true,
|
|
1168
|
+
signal: options.signal,
|
|
1169
|
+
apiKey: options.apiKey
|
|
1170
|
+
})
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
const Labels = (transport) => ({
|
|
1174
|
+
list: (options = {}) => FetchList(transport, PATHS.LABELS, options),
|
|
1175
|
+
get: (id, options = {}) => transport.request(LabelPath(id), { signal: options.signal, apiKey: options.apiKey }),
|
|
1176
|
+
create: (body, options = {}) => transport.request(PATHS.LABELS, {
|
|
1177
|
+
method: HTTP_METHODS.POST,
|
|
1178
|
+
body,
|
|
1179
|
+
signal: options.signal,
|
|
1180
|
+
apiKey: options.apiKey
|
|
1181
|
+
}),
|
|
1182
|
+
update: (id, body, options = {}) => transport.request(LabelPath(id), {
|
|
1183
|
+
method: HTTP_METHODS.PATCH,
|
|
1184
|
+
body,
|
|
1185
|
+
repeatable: true,
|
|
1186
|
+
signal: options.signal,
|
|
1187
|
+
apiKey: options.apiKey
|
|
1188
|
+
}),
|
|
1189
|
+
delete: (id, options = {}) => transport.request(LabelPath(id), {
|
|
1190
|
+
method: HTTP_METHODS.DELETE,
|
|
1191
|
+
signal: options.signal,
|
|
1192
|
+
apiKey: options.apiKey
|
|
1193
|
+
})
|
|
1194
|
+
});
|
|
1195
|
+
|
|
1196
|
+
const Languages = (transport) => ({
|
|
1197
|
+
list: (options = {}) => FetchList(transport, PATHS.LANGUAGES, options)
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1200
|
+
const Me = (transport) => ({
|
|
1201
|
+
get: (options = {}) => transport.request(PATHS.KEYS_SELF, { signal: options.signal, apiKey: options.apiKey }),
|
|
1202
|
+
ping: (options = {}) => transport.request(PATHS.PING, { signal: options.signal, apiKey: options.apiKey })
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
const Members = (transport) => ({
|
|
1206
|
+
list: (options = {}) => FetchList(transport, PATHS.MEMBERS, options),
|
|
1207
|
+
get: (userId, options = {}) => transport.request(MemberPath(userId), { signal: options.signal, apiKey: options.apiKey }),
|
|
1208
|
+
add: (body, options = {}) => transport.request(PATHS.MEMBERS, {
|
|
1209
|
+
method: HTTP_METHODS.POST,
|
|
1210
|
+
body,
|
|
1211
|
+
signal: options.signal,
|
|
1212
|
+
apiKey: options.apiKey
|
|
1213
|
+
}),
|
|
1214
|
+
update: (userId, patch, options = {}) => transport.request(MemberPath(userId), {
|
|
1215
|
+
method: HTTP_METHODS.PATCH,
|
|
1216
|
+
body: patch,
|
|
1217
|
+
repeatable: true,
|
|
1218
|
+
signal: options.signal,
|
|
1219
|
+
apiKey: options.apiKey
|
|
1220
|
+
}),
|
|
1221
|
+
remove: (userId, options = {}) => transport.request(MemberPath(userId), {
|
|
1222
|
+
method: HTTP_METHODS.DELETE,
|
|
1223
|
+
signal: options.signal,
|
|
1224
|
+
apiKey: options.apiKey
|
|
1225
|
+
}),
|
|
1226
|
+
grantAddress: (userId, body, options = {}) => transport.request(MemberAddressesPath(userId), {
|
|
1227
|
+
method: HTTP_METHODS.POST,
|
|
1228
|
+
body,
|
|
1229
|
+
repeatable: true,
|
|
1230
|
+
signal: options.signal,
|
|
1231
|
+
apiKey: options.apiKey
|
|
1232
|
+
}),
|
|
1233
|
+
revokeAddress: (userId, addressId, options = {}) => transport.request(MemberAddressPath(userId, addressId), {
|
|
1234
|
+
method: HTTP_METHODS.DELETE,
|
|
1235
|
+
signal: options.signal,
|
|
1236
|
+
apiKey: options.apiKey
|
|
1237
|
+
})
|
|
1238
|
+
});
|
|
1239
|
+
|
|
1240
|
+
const Roles = (transport) => ({
|
|
1241
|
+
list: (options = {}) => FetchList(transport, PATHS.ROLES, options),
|
|
1242
|
+
get: (id, options = {}) => transport.request(RolePath(id), { signal: options.signal, apiKey: options.apiKey }),
|
|
1243
|
+
create: (body, options = {}) => transport.request(PATHS.ROLES, {
|
|
1244
|
+
method: HTTP_METHODS.POST,
|
|
1245
|
+
body,
|
|
1246
|
+
signal: options.signal,
|
|
1247
|
+
apiKey: options.apiKey
|
|
1248
|
+
}),
|
|
1249
|
+
update: (id, patch, options = {}) => transport.request(RolePath(id), {
|
|
1250
|
+
method: HTTP_METHODS.PATCH,
|
|
1251
|
+
body: patch,
|
|
1252
|
+
repeatable: true,
|
|
1253
|
+
signal: options.signal,
|
|
1254
|
+
apiKey: options.apiKey
|
|
1255
|
+
}),
|
|
1256
|
+
delete: (id, options = {}) => transport.request(RolePath(id), {
|
|
1257
|
+
method: HTTP_METHODS.DELETE,
|
|
1258
|
+
query: { [QUERY_KEYS.REASSIGN_TO]: options.reassignTo },
|
|
1259
|
+
signal: options.signal,
|
|
1260
|
+
apiKey: options.apiKey
|
|
1261
|
+
}),
|
|
1262
|
+
listPermissions: (options = {}) => FetchList(transport, PATHS.ROLES_PERMISSIONS, options)
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
const ListQuery$2 = (options) => ({
|
|
1266
|
+
[QUERY_KEYS.ENABLED]: options.enabled === undefined ? undefined : String(options.enabled)
|
|
1267
|
+
});
|
|
1268
|
+
const RunsQuery = (options) => ({
|
|
1269
|
+
[QUERY_KEYS.RULE_ID]: options.ruleId,
|
|
1270
|
+
[QUERY_KEYS.THREAD_ID]: options.threadId
|
|
1271
|
+
});
|
|
1272
|
+
const Rules = (transport) => ({
|
|
1273
|
+
list: (options = {}) => FetchPage(transport, PATHS.RULES, CURSOR_STYLES.CURSOR, options, ListQuery$2(options)),
|
|
1274
|
+
listAll: (options = {}) => CollectAll(transport, PATHS.RULES, CURSOR_STYLES.CURSOR, options, ListQuery$2(options)),
|
|
1275
|
+
iterate: (options = {}) => IteratePages(transport, PATHS.RULES, CURSOR_STYLES.CURSOR, options, ListQuery$2(options)),
|
|
1276
|
+
get: (id, options = {}) => transport.request(RulePath(id), { signal: options.signal, apiKey: options.apiKey }),
|
|
1277
|
+
create: (body, options = {}) => transport.request(PATHS.RULES, {
|
|
1278
|
+
method: HTTP_METHODS.POST,
|
|
1279
|
+
body,
|
|
1280
|
+
signal: options.signal,
|
|
1281
|
+
apiKey: options.apiKey
|
|
1282
|
+
}),
|
|
1283
|
+
update: (id, patch, options = {}) => transport.request(RulePath(id), {
|
|
1284
|
+
method: HTTP_METHODS.PATCH,
|
|
1285
|
+
body: patch,
|
|
1286
|
+
signal: options.signal,
|
|
1287
|
+
apiKey: options.apiKey
|
|
1288
|
+
}),
|
|
1289
|
+
delete: (id, options = {}) => transport.request(RulePath(id), {
|
|
1290
|
+
method: HTTP_METHODS.DELETE,
|
|
1291
|
+
signal: options.signal,
|
|
1292
|
+
apiKey: options.apiKey
|
|
1293
|
+
}),
|
|
1294
|
+
reorder: async (ruleIds, options = {}) => {
|
|
1295
|
+
const envelope = await transport.request(PATHS.RULES_REORDER, {
|
|
1296
|
+
method: HTTP_METHODS.POST,
|
|
1297
|
+
body: { ruleIds },
|
|
1298
|
+
repeatable: true,
|
|
1299
|
+
signal: options.signal,
|
|
1300
|
+
apiKey: options.apiKey
|
|
1301
|
+
});
|
|
1302
|
+
return envelope?.data ?? [];
|
|
1303
|
+
},
|
|
1304
|
+
test: (id, body = {}, options = {}) => transport.request(RuleChildPath(id, SEGMENTS.TEST), {
|
|
1305
|
+
method: HTTP_METHODS.POST,
|
|
1306
|
+
body,
|
|
1307
|
+
repeatable: true,
|
|
1308
|
+
signal: options.signal,
|
|
1309
|
+
apiKey: options.apiKey
|
|
1310
|
+
}),
|
|
1311
|
+
listRuns: (options = {}) => FetchPage(transport, PATHS.RULES_RUNS, CURSOR_STYLES.CURSOR, options, RunsQuery(options)),
|
|
1312
|
+
listAllRuns: (options = {}) => CollectAll(transport, PATHS.RULES_RUNS, CURSOR_STYLES.CURSOR, options, RunsQuery(options)),
|
|
1313
|
+
iterateRuns: (options = {}) => IteratePages(transport, PATHS.RULES_RUNS, CURSOR_STYLES.CURSOR, options, RunsQuery(options))
|
|
1314
|
+
});
|
|
1315
|
+
|
|
1316
|
+
const Settings = (transport) => ({
|
|
1317
|
+
get: (options = {}) => transport.request(PATHS.SETTINGS, { signal: options.signal, apiKey: options.apiKey }),
|
|
1318
|
+
update: (patch, options = {}) => transport.request(PATHS.SETTINGS, {
|
|
1319
|
+
method: HTTP_METHODS.PATCH,
|
|
1320
|
+
body: patch,
|
|
1321
|
+
repeatable: true,
|
|
1322
|
+
signal: options.signal,
|
|
1323
|
+
apiKey: options.apiKey
|
|
1324
|
+
})
|
|
1325
|
+
});
|
|
1326
|
+
|
|
1327
|
+
const TempMail = (transport) => ({
|
|
1328
|
+
listDomains: async (options = {}) => (await transport.request(PATHS.TEMP_MAIL_DOMAINS, {
|
|
1329
|
+
anonymous: true,
|
|
1330
|
+
signal: options.signal
|
|
1331
|
+
}))?.data ?? [],
|
|
1332
|
+
create: (body = {}, options = {}) => transport.request(PATHS.TEMP_MAIL_INBOXES, {
|
|
1333
|
+
method: HTTP_METHODS.POST,
|
|
1334
|
+
body,
|
|
1335
|
+
anonymous: true,
|
|
1336
|
+
signal: options.signal
|
|
1337
|
+
}),
|
|
1338
|
+
get: (inboxId, options = {}) => transport.request(TempInboxPath(inboxId), {
|
|
1339
|
+
inboxToken: options.inboxToken,
|
|
1340
|
+
signal: options.signal
|
|
1341
|
+
}),
|
|
1342
|
+
extend: (inboxId, options = {}) => transport.request(TempInboxExtendPath(inboxId), {
|
|
1343
|
+
method: HTTP_METHODS.POST,
|
|
1344
|
+
inboxToken: options.inboxToken,
|
|
1345
|
+
signal: options.signal
|
|
1346
|
+
}),
|
|
1347
|
+
delete: (inboxId, options = {}) => transport.request(TempInboxPath(inboxId), {
|
|
1348
|
+
method: HTTP_METHODS.DELETE,
|
|
1349
|
+
inboxToken: options.inboxToken,
|
|
1350
|
+
signal: options.signal
|
|
1351
|
+
}),
|
|
1352
|
+
listMessages: async (inboxId, options = {}) => {
|
|
1353
|
+
const envelope = await transport.request(TempMessagesPath(inboxId), {
|
|
1354
|
+
query: { [QUERY_KEYS.LIMIT]: options.limit },
|
|
1355
|
+
inboxToken: options.inboxToken,
|
|
1356
|
+
signal: options.signal
|
|
1357
|
+
});
|
|
1358
|
+
return { items: envelope?.data ?? [], expiresAt: envelope?.expiresAt };
|
|
1359
|
+
},
|
|
1360
|
+
getMessage: (inboxId, messageId, options = {}) => transport.request(TempMessagePath(inboxId, messageId), {
|
|
1361
|
+
inboxToken: options.inboxToken,
|
|
1362
|
+
signal: options.signal
|
|
1363
|
+
}),
|
|
1364
|
+
deleteMessage: (inboxId, messageId, options = {}) => transport.request(TempMessagePath(inboxId, messageId), {
|
|
1365
|
+
method: HTTP_METHODS.DELETE,
|
|
1366
|
+
inboxToken: options.inboxToken,
|
|
1367
|
+
signal: options.signal
|
|
1368
|
+
}),
|
|
1369
|
+
listAttachments: async (inboxId, messageId, options = {}) => (await transport.request(TempMessageAttachmentsPath(inboxId, messageId), {
|
|
1370
|
+
inboxToken: options.inboxToken,
|
|
1371
|
+
signal: options.signal
|
|
1372
|
+
}))?.data ?? []
|
|
1373
|
+
});
|
|
1374
|
+
|
|
1375
|
+
const ListQuery$1 = (options) => ({ [QUERY_KEYS.STATUS]: options.status });
|
|
1376
|
+
const Templates = (transport) => ({
|
|
1377
|
+
list: (options = {}) => FetchPage(transport, PATHS.TEMPLATES, CURSOR_STYLES.CURSOR, options, ListQuery$1(options)),
|
|
1378
|
+
listAll: (options = {}) => CollectAll(transport, PATHS.TEMPLATES, CURSOR_STYLES.CURSOR, options, ListQuery$1(options)),
|
|
1379
|
+
iterate: (options = {}) => IteratePages(transport, PATHS.TEMPLATES, CURSOR_STYLES.CURSOR, options, ListQuery$1(options)),
|
|
1380
|
+
get: (idOrSlug, options = {}) => transport.request(TemplatePath(idOrSlug), { signal: options.signal, apiKey: options.apiKey }),
|
|
1381
|
+
create: (body, options = {}) => transport.request(PATHS.TEMPLATES, {
|
|
1382
|
+
method: HTTP_METHODS.POST,
|
|
1383
|
+
body,
|
|
1384
|
+
signal: options.signal,
|
|
1385
|
+
apiKey: options.apiKey
|
|
1386
|
+
}),
|
|
1387
|
+
update: (idOrSlug, patch, options = {}) => transport.request(TemplatePath(idOrSlug), {
|
|
1388
|
+
method: HTTP_METHODS.PATCH,
|
|
1389
|
+
body: patch,
|
|
1390
|
+
signal: options.signal,
|
|
1391
|
+
apiKey: options.apiKey
|
|
1392
|
+
}),
|
|
1393
|
+
delete: (idOrSlug, options = {}) => transport.request(TemplatePath(idOrSlug), {
|
|
1394
|
+
method: HTTP_METHODS.DELETE,
|
|
1395
|
+
signal: options.signal,
|
|
1396
|
+
apiKey: options.apiKey
|
|
1397
|
+
}),
|
|
1398
|
+
listVersions: (idOrSlug, options = {}) => FetchList(transport, TemplateChildPath(idOrSlug, SEGMENTS.VERSIONS), options),
|
|
1399
|
+
publish: (idOrSlug, options = {}) => transport.request(TemplateChildPath(idOrSlug, SEGMENTS.VERSIONS), {
|
|
1400
|
+
method: HTTP_METHODS.POST,
|
|
1401
|
+
repeatable: true,
|
|
1402
|
+
signal: options.signal,
|
|
1403
|
+
apiKey: options.apiKey
|
|
1404
|
+
}),
|
|
1405
|
+
preview: (idOrSlug, body = {}, options = {}) => transport.request(TemplateChildPath(idOrSlug, SEGMENTS.PREVIEW), {
|
|
1406
|
+
method: HTTP_METHODS.POST,
|
|
1407
|
+
body,
|
|
1408
|
+
repeatable: true,
|
|
1409
|
+
signal: options.signal,
|
|
1410
|
+
apiKey: options.apiKey
|
|
1411
|
+
}),
|
|
1412
|
+
send: (idOrSlug, body, options = {}) => transport.request(TemplateChildPath(idOrSlug, SEGMENTS.SEND), {
|
|
1413
|
+
method: HTTP_METHODS.POST,
|
|
1414
|
+
body: TemplateSendToWire(body),
|
|
1415
|
+
idempotent: true,
|
|
1416
|
+
idempotencyKey: options.idempotencyKey,
|
|
1417
|
+
repeatable: true,
|
|
1418
|
+
signal: options.signal,
|
|
1419
|
+
apiKey: options.apiKey
|
|
1420
|
+
})
|
|
1421
|
+
});
|
|
1422
|
+
|
|
1423
|
+
const ListQuery = (options) => ({
|
|
1424
|
+
[QUERY_KEYS.FOLDER]: options.folder,
|
|
1425
|
+
[QUERY_KEYS.QUERY]: options.query,
|
|
1426
|
+
[QUERY_KEYS.LABEL_IDS]: Array.isArray(options.labelIds) ? options.labelIds.join(LIST_SEPARATOR) : options.labelIds
|
|
1427
|
+
});
|
|
1428
|
+
const Threads = (transport) => ({
|
|
1429
|
+
list: (options = {}) => FetchPage(transport, PATHS.THREADS, CURSOR_STYLES.PAGE_TOKEN, options, ListQuery(options)),
|
|
1430
|
+
listAll: (options = {}) => CollectAll(transport, PATHS.THREADS, CURSOR_STYLES.PAGE_TOKEN, options, ListQuery(options)),
|
|
1431
|
+
iterate: (options = {}) => IteratePages(transport, PATHS.THREADS, CURSOR_STYLES.PAGE_TOKEN, options, ListQuery(options)),
|
|
1432
|
+
get: (id, options = {}) => transport.request(ThreadPath(id), { signal: options.signal, apiKey: options.apiKey }),
|
|
1433
|
+
update: (id, patch, options = {}) => transport.request(ThreadPath(id), {
|
|
1434
|
+
method: HTTP_METHODS.PATCH,
|
|
1435
|
+
body: patch,
|
|
1436
|
+
repeatable: true,
|
|
1437
|
+
signal: options.signal,
|
|
1438
|
+
apiKey: options.apiKey
|
|
1439
|
+
}),
|
|
1440
|
+
trash: (id, options = {}) => transport.request(ThreadChildPath(id, SEGMENTS.TRASH), {
|
|
1441
|
+
method: HTTP_METHODS.POST,
|
|
1442
|
+
repeatable: true,
|
|
1443
|
+
signal: options.signal,
|
|
1444
|
+
apiKey: options.apiKey
|
|
1445
|
+
}),
|
|
1446
|
+
snooze: (id, wakeAt, options = {}) => transport.request(ThreadChildPath(id, SEGMENTS.SNOOZE), {
|
|
1447
|
+
method: HTTP_METHODS.POST,
|
|
1448
|
+
body: { wakeAt: ToInstant(wakeAt) },
|
|
1449
|
+
repeatable: true,
|
|
1450
|
+
signal: options.signal,
|
|
1451
|
+
apiKey: options.apiKey
|
|
1452
|
+
}),
|
|
1453
|
+
unsnooze: (id, options = {}) => transport.request(ThreadChildPath(id, SEGMENTS.UNSNOOZE), {
|
|
1454
|
+
method: HTTP_METHODS.POST,
|
|
1455
|
+
repeatable: true,
|
|
1456
|
+
signal: options.signal,
|
|
1457
|
+
apiKey: options.apiKey
|
|
1458
|
+
}),
|
|
1459
|
+
listAttachments: (id, messageId, options = {}) => FetchList(transport, ThreadMessageAttachmentsPath(id, messageId), options)
|
|
1460
|
+
});
|
|
1461
|
+
|
|
1462
|
+
const Flag = (value) => value === undefined ? undefined : String(value);
|
|
1463
|
+
const HitQuery = (options) => ({
|
|
1464
|
+
[QUERY_KEYS.INCLUDE_MACHINE]: Flag(options.includeMachine),
|
|
1465
|
+
[QUERY_KEYS.LIMIT]: options.limit
|
|
1466
|
+
});
|
|
1467
|
+
const Tracking = (transport) => ({
|
|
1468
|
+
list: (options = {}) => FetchList(transport, PATHS.TRACKING, options, {
|
|
1469
|
+
[QUERY_KEYS.OPENED]: Flag(options.opened),
|
|
1470
|
+
[QUERY_KEYS.CLICKED]: Flag(options.clicked),
|
|
1471
|
+
[QUERY_KEYS.DAYS]: options.days,
|
|
1472
|
+
[QUERY_KEYS.MINUTES]: options.minutes,
|
|
1473
|
+
[QUERY_KEYS.GRAIN]: options.grain,
|
|
1474
|
+
[QUERY_KEYS.LIMIT]: options.limit
|
|
1475
|
+
}),
|
|
1476
|
+
getStats: (options = {}) => transport.request(PATHS.TRACKING_STATS, {
|
|
1477
|
+
query: {
|
|
1478
|
+
[QUERY_KEYS.DAYS]: options.days,
|
|
1479
|
+
[QUERY_KEYS.MINUTES]: options.minutes,
|
|
1480
|
+
[QUERY_KEYS.GRAIN]: options.grain,
|
|
1481
|
+
[QUERY_KEYS.OFFSET_MINUTES]: options.offsetMinutes
|
|
1482
|
+
},
|
|
1483
|
+
signal: options.signal,
|
|
1484
|
+
apiKey: options.apiKey
|
|
1485
|
+
}),
|
|
1486
|
+
get: (id, options = {}) => transport.request(TrackingPath(id), { signal: options.signal, apiKey: options.apiKey }),
|
|
1487
|
+
listOpens: (id, options = {}) => FetchList(transport, TrackingChildPath(id, SEGMENTS.OPENS), options, HitQuery(options)),
|
|
1488
|
+
listClicks: (id, options = {}) => FetchList(transport, TrackingChildPath(id, SEGMENTS.CLICKS), options, HitQuery(options))
|
|
1489
|
+
});
|
|
1490
|
+
|
|
1491
|
+
const Webhooks = (transport) => ({
|
|
1492
|
+
list: (options = {}) => FetchList(transport, PATHS.WEBHOOKS, options),
|
|
1493
|
+
get: (id, options = {}) => transport.request(WebhookPath(id), { signal: options.signal, apiKey: options.apiKey }),
|
|
1494
|
+
create: (body, options = {}) => transport.request(PATHS.WEBHOOKS, {
|
|
1495
|
+
method: HTTP_METHODS.POST,
|
|
1496
|
+
body,
|
|
1497
|
+
signal: options.signal,
|
|
1498
|
+
apiKey: options.apiKey
|
|
1499
|
+
}),
|
|
1500
|
+
update: (id, patch, options = {}) => transport.request(WebhookPath(id), {
|
|
1501
|
+
method: HTTP_METHODS.PATCH,
|
|
1502
|
+
body: patch,
|
|
1503
|
+
repeatable: true,
|
|
1504
|
+
signal: options.signal,
|
|
1505
|
+
apiKey: options.apiKey
|
|
1506
|
+
}),
|
|
1507
|
+
delete: (id, options = {}) => transport.request(WebhookPath(id), {
|
|
1508
|
+
method: HTTP_METHODS.DELETE,
|
|
1509
|
+
signal: options.signal,
|
|
1510
|
+
apiKey: options.apiKey
|
|
1511
|
+
}),
|
|
1512
|
+
rotateSecret: (id, options = {}) => transport.request(WebhookChildPath(id, SEGMENTS.ROTATE_SECRET), {
|
|
1513
|
+
method: HTTP_METHODS.POST,
|
|
1514
|
+
signal: options.signal,
|
|
1515
|
+
apiKey: options.apiKey
|
|
1516
|
+
}),
|
|
1517
|
+
test: (id, options = {}) => transport.request(WebhookChildPath(id, SEGMENTS.TEST), {
|
|
1518
|
+
method: HTTP_METHODS.POST,
|
|
1519
|
+
signal: options.signal,
|
|
1520
|
+
apiKey: options.apiKey
|
|
1521
|
+
}),
|
|
1522
|
+
listDeliveries: (id, options = {}) => FetchList(transport, WebhookChildPath(id, SEGMENTS.DELIVERIES), options)
|
|
1523
|
+
});
|
|
1524
|
+
|
|
1525
|
+
class OpenEmail {
|
|
1526
|
+
transport;
|
|
1527
|
+
mode;
|
|
1528
|
+
me;
|
|
1529
|
+
addresses;
|
|
1530
|
+
languages;
|
|
1531
|
+
emails;
|
|
1532
|
+
templates;
|
|
1533
|
+
tracking;
|
|
1534
|
+
threads;
|
|
1535
|
+
drafts;
|
|
1536
|
+
labels;
|
|
1537
|
+
contacts;
|
|
1538
|
+
domains;
|
|
1539
|
+
rules;
|
|
1540
|
+
webhooks;
|
|
1541
|
+
calendar;
|
|
1542
|
+
settings;
|
|
1543
|
+
roles;
|
|
1544
|
+
members;
|
|
1545
|
+
tempMail;
|
|
1546
|
+
constructor(configuration) {
|
|
1547
|
+
const options = typeof configuration === 'string' ? { apiKey: configuration } : configuration;
|
|
1548
|
+
AssertApiKey(options.apiKey, '{ apiKey }');
|
|
1549
|
+
if (IsBrowser() && options.dangerouslyAllowBrowser !== true)
|
|
1550
|
+
throw new Error(BROWSER_REFUSAL);
|
|
1551
|
+
this.transport = new Transport(options);
|
|
1552
|
+
this.mode = ModeOf(options.apiKey);
|
|
1553
|
+
this.me = Me(this.transport);
|
|
1554
|
+
this.addresses = Addresses(this.transport);
|
|
1555
|
+
this.languages = Languages(this.transport);
|
|
1556
|
+
this.emails = Emails(this.transport);
|
|
1557
|
+
this.templates = Templates(this.transport);
|
|
1558
|
+
this.tracking = Tracking(this.transport);
|
|
1559
|
+
this.threads = Threads(this.transport);
|
|
1560
|
+
this.drafts = Drafts(this.transport);
|
|
1561
|
+
this.labels = Labels(this.transport);
|
|
1562
|
+
this.contacts = Contacts(this.transport);
|
|
1563
|
+
this.domains = Domains(this.transport);
|
|
1564
|
+
this.rules = Rules(this.transport);
|
|
1565
|
+
this.webhooks = Webhooks(this.transport);
|
|
1566
|
+
this.calendar = Calendar(this.transport);
|
|
1567
|
+
this.settings = Settings(this.transport);
|
|
1568
|
+
this.roles = Roles(this.transport);
|
|
1569
|
+
this.members = Members(this.transport);
|
|
1570
|
+
this.tempMail = TempMail(this.transport);
|
|
1571
|
+
}
|
|
1572
|
+
get raw() {
|
|
1573
|
+
return this.transport;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
const FromEnvironment = (name) => {
|
|
1578
|
+
const scope = globalThis;
|
|
1579
|
+
try {
|
|
1580
|
+
const value = scope.process?.env?.[name] ?? scope.Deno?.env?.get(name);
|
|
1581
|
+
return value && value.length > 0 ? value : undefined;
|
|
1582
|
+
}
|
|
1583
|
+
catch {
|
|
1584
|
+
return undefined;
|
|
1585
|
+
}
|
|
1586
|
+
};
|
|
1587
|
+
|
|
1588
|
+
const NormaliseEndpoint = (value) => {
|
|
1589
|
+
if (!value)
|
|
1590
|
+
return undefined;
|
|
1591
|
+
const trimmed = value.trim().replace(/\/+$/, '');
|
|
1592
|
+
if (trimmed.length === 0)
|
|
1593
|
+
return undefined;
|
|
1594
|
+
if (ENDPOINT_SCHEME_PATTERN.test(trimmed))
|
|
1595
|
+
return trimmed;
|
|
1596
|
+
const host = trimmed.split(':')[0];
|
|
1597
|
+
return `${LOCAL_HOSTS.includes(host) ? 'http' : 'https'}://${trimmed}`;
|
|
1598
|
+
};
|
|
1599
|
+
|
|
1600
|
+
const CreateClient = (configuration = {}) => {
|
|
1601
|
+
const options = typeof configuration === 'string' ? { apiKey: configuration } : configuration;
|
|
1602
|
+
return new OpenEmail({
|
|
1603
|
+
...options,
|
|
1604
|
+
apiKey: options.apiKey ?? FromEnvironment(ENV_VARS.API_KEY),
|
|
1605
|
+
baseUrl: NormaliseEndpoint(options.baseUrl ?? FromEnvironment(ENV_VARS.BASE_URL))
|
|
1606
|
+
});
|
|
1607
|
+
};
|
|
1608
|
+
|
|
1609
|
+
const CreateTempMail = (options = {}) => TempMail(new Transport({
|
|
1610
|
+
...options,
|
|
1611
|
+
apiKey: undefined,
|
|
1612
|
+
baseUrl: NormaliseEndpoint(options.baseUrl ?? FromEnvironment(ENV_VARS.BASE_URL))
|
|
1613
|
+
}));
|
|
1614
|
+
|
|
1615
|
+
const IsSealed = (message) => !!message.encryption && SEALED_ENCRYPTION_FORMATS.includes(message.encryption.format);
|
|
1616
|
+
|
|
1617
|
+
const ReadHeader = (headers, name) => {
|
|
1618
|
+
if (typeof headers.get === 'function')
|
|
1619
|
+
return headers.get(name) ?? '';
|
|
1620
|
+
const wanted = name.toLowerCase();
|
|
1621
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
1622
|
+
if (key.toLowerCase() !== wanted)
|
|
1623
|
+
continue;
|
|
1624
|
+
if (Array.isArray(value))
|
|
1625
|
+
return value[0] ?? '';
|
|
1626
|
+
return value ?? '';
|
|
1627
|
+
}
|
|
1628
|
+
return '';
|
|
1629
|
+
};
|
|
1630
|
+
const ParseSignature = (header) => {
|
|
1631
|
+
let timestamp = Number.NaN;
|
|
1632
|
+
const signatures = [];
|
|
1633
|
+
for (const pair of header.split(WEBHOOK_SIGNATURE.PAIR_SEPARATOR)) {
|
|
1634
|
+
const at = pair.indexOf(WEBHOOK_SIGNATURE.VALUE_SEPARATOR);
|
|
1635
|
+
if (at < 1)
|
|
1636
|
+
continue;
|
|
1637
|
+
const key = pair.slice(0, at).trim();
|
|
1638
|
+
const value = pair.slice(at + 1).trim();
|
|
1639
|
+
if (key === WEBHOOK_SIGNATURE.TIMESTAMP)
|
|
1640
|
+
timestamp = Number(value);
|
|
1641
|
+
if (key === WEBHOOK_SIGNATURE.VERSION && value)
|
|
1642
|
+
signatures.push(value);
|
|
1643
|
+
}
|
|
1644
|
+
if (!Number.isFinite(timestamp) || signatures.length === 0)
|
|
1645
|
+
return null;
|
|
1646
|
+
return { timestamp, signatures };
|
|
1647
|
+
};
|
|
1648
|
+
const Equal = (a, b) => {
|
|
1649
|
+
if (a.length !== b.length)
|
|
1650
|
+
return false;
|
|
1651
|
+
let mismatch = 0;
|
|
1652
|
+
for (let i = 0; i < a.length; i++)
|
|
1653
|
+
mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
1654
|
+
return mismatch === 0;
|
|
1655
|
+
};
|
|
1656
|
+
const Hex = (bytes) => Array.from(new Uint8Array(bytes), byte => byte.toString(16).padStart(2, '0')).join('');
|
|
1657
|
+
const VerifyWebhookSignature = async (props) => {
|
|
1658
|
+
const { payload, headers, secret, toleranceSeconds } = props;
|
|
1659
|
+
const subtle = globalThis.crypto?.subtle;
|
|
1660
|
+
if (!subtle)
|
|
1661
|
+
throw new Error(WEBHOOK_MESSAGES.NO_WEBCRYPTO);
|
|
1662
|
+
const header = ReadHeader(headers, WEBHOOK_HEADERS.SIGNATURE);
|
|
1663
|
+
if (!header)
|
|
1664
|
+
throw new Error(WEBHOOK_MESSAGES.MISSING_HEADER);
|
|
1665
|
+
const parsed = ParseSignature(header);
|
|
1666
|
+
if (!parsed)
|
|
1667
|
+
throw new Error(WEBHOOK_MESSAGES.MALFORMED_HEADER);
|
|
1668
|
+
const tolerance = toleranceSeconds ?? WEBHOOK_SIGNATURE.TOLERANCE_SECONDS;
|
|
1669
|
+
const drift = Math.abs(Math.floor(Date.now() / 1000) - parsed.timestamp);
|
|
1670
|
+
if (tolerance > 0 && drift > tolerance) {
|
|
1671
|
+
throw new Error(WEBHOOK_MESSAGES.OUT_OF_TOLERANCE.replace('{{drift}}', String(drift)).replace('{{tolerance}}', String(tolerance)));
|
|
1672
|
+
}
|
|
1673
|
+
const encoder = new TextEncoder();
|
|
1674
|
+
const key = await subtle.importKey('raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
1675
|
+
const expected = Hex(await subtle.sign('HMAC', key, encoder.encode(`${parsed.timestamp}.${payload}`)));
|
|
1676
|
+
if (!parsed.signatures.some(candidate => Equal(expected, candidate)))
|
|
1677
|
+
throw new Error(WEBHOOK_MESSAGES.MISMATCH);
|
|
1678
|
+
return JSON.parse(payload);
|
|
1679
|
+
};
|
|
1680
|
+
|
|
1681
|
+
const Init = (configuration = {}) => {
|
|
1682
|
+
const client = CreateClient(configuration);
|
|
1683
|
+
CLIENT_REGISTRY[CLIENT_REGISTRY_KEY] = client;
|
|
1684
|
+
return client;
|
|
1685
|
+
};
|
|
1686
|
+
const GetClient = () => {
|
|
1687
|
+
const existing = CLIENT_REGISTRY[CLIENT_REGISTRY_KEY];
|
|
1688
|
+
if (existing)
|
|
1689
|
+
return existing;
|
|
1690
|
+
return Init();
|
|
1691
|
+
};
|
|
1692
|
+
const ResetClient = () => {
|
|
1693
|
+
CLIENT_REGISTRY[CLIENT_REGISTRY_KEY] = null;
|
|
1694
|
+
};
|
|
1695
|
+
const DefaultClient = new Proxy({}, {
|
|
1696
|
+
get: (_target, property) => {
|
|
1697
|
+
if (typeof property === 'symbol' || property === 'then')
|
|
1698
|
+
return undefined;
|
|
1699
|
+
return Reflect.get(GetClient(), property);
|
|
1700
|
+
},
|
|
1701
|
+
has: (_target, property) => Reflect.has(GetClient(), property)
|
|
1702
|
+
});
|
|
1703
|
+
|
|
1704
|
+
const Key = (value) => value.trim().toLowerCase();
|
|
1705
|
+
const BY_CODE = new Map(LANGUAGES.map(language => [Key(language.code), language]));
|
|
1706
|
+
const BY_LABEL = new Map(LANGUAGES.map(language => [Key(language.label), language]));
|
|
1707
|
+
const BY_NATIVE = new Map(LANGUAGES.map(language => [Key(language.native), language]));
|
|
1708
|
+
const ALIASES = new Map(LANGUAGE_ALIASES.map(([alias, code]) => [Key(alias), Key(code)]));
|
|
1709
|
+
const LanguageByCode = (code) => code ? BY_CODE.get(Key(code)) : undefined;
|
|
1710
|
+
const ResolveLanguage = (input) => {
|
|
1711
|
+
if (!input)
|
|
1712
|
+
return undefined;
|
|
1713
|
+
const key = Key(input);
|
|
1714
|
+
if (!key)
|
|
1715
|
+
return undefined;
|
|
1716
|
+
const aliased = ALIASES.get(key);
|
|
1717
|
+
if (aliased)
|
|
1718
|
+
return BY_CODE.get(aliased);
|
|
1719
|
+
const direct = BY_CODE.get(key) ?? BY_LABEL.get(key) ?? BY_NATIVE.get(key);
|
|
1720
|
+
if (direct)
|
|
1721
|
+
return direct;
|
|
1722
|
+
const base = key.split(LANGUAGE_SUBTAG_PATTERN)[0];
|
|
1723
|
+
if (!base || base === key)
|
|
1724
|
+
return undefined;
|
|
1725
|
+
return BY_CODE.get(ALIASES.get(base) ?? base);
|
|
1726
|
+
};
|
|
1727
|
+
const IsRtlLanguage = (code) => ResolveLanguage(code)?.rtl === true;
|
|
1728
|
+
|
|
1729
|
+
const init = Init;
|
|
1730
|
+
const openemail = DefaultClient;
|
|
1731
|
+
const getClient = GetClient;
|
|
1732
|
+
const resetClient = ResetClient;
|
|
1733
|
+
const createOpenEmail = CreateClient;
|
|
1734
|
+
const createClient = CreateClient;
|
|
1735
|
+
const createTempMail = CreateTempMail;
|
|
1736
|
+
const verifyWebhookSignature = VerifyWebhookSignature;
|
|
1737
|
+
const toBase64 = ToBase64;
|
|
1738
|
+
const isApiKey = IsApiKey;
|
|
1739
|
+
const isSealed = IsSealed;
|
|
1740
|
+
const resolveLanguage = ResolveLanguage;
|
|
1741
|
+
const languageByCode = LanguageByCode;
|
|
1742
|
+
const isRtlLanguage = IsRtlLanguage;
|
|
1743
|
+
|
|
1744
|
+
export { API_SCOPES, ERROR_TYPES, LANGUAGES, MESSAGE_ENCRYPTION_FORMATS, OpenEmail, OpenEmailApiError, OpenEmailNetworkError, PAGE_LIMITS, RULE_ACTIONS, RULE_FIELDS, RULE_OPERATORS, VERSION, WEBHOOK_EVENTS, WEBHOOK_SIGNATURE_HEADERS, createClient, createOpenEmail, createTempMail, OpenEmail as default, getClient, init, isApiKey, isRtlLanguage, isSealed, languageByCode, openemail, resetClient, resolveLanguage, toBase64, verifyWebhookSignature };
|