@openemail/sdk 0.0.4 → 0.0.6
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 +54 -3
- package/index.cjs +262 -26
- package/index.d.cts +217 -9
- package/index.d.ts +217 -9
- package/index.js +255 -27
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
const AUDIENCE_MEMBER_STATUSES = {
|
|
2
|
+
SUBSCRIBED: 'subscribed',
|
|
3
|
+
UNSUBSCRIBED: 'unsubscribed'
|
|
4
|
+
};
|
|
5
|
+
|
|
1
6
|
const BROADCAST_STATUSES = {
|
|
2
7
|
SCHEDULED: 'scheduled',
|
|
3
8
|
QUEUED: 'queued',
|
|
@@ -19,7 +24,7 @@ const BROADCAST_RECIPIENT_FILTERS = {
|
|
|
19
24
|
UNSUBSCRIBED: 'unsubscribed'
|
|
20
25
|
};
|
|
21
26
|
|
|
22
|
-
var version = "0.0.
|
|
27
|
+
var version = "0.0.6";
|
|
23
28
|
|
|
24
29
|
const BUILD_BASE_URL = 'https://api.openemail.uk';
|
|
25
30
|
|
|
@@ -33,8 +38,8 @@ const DEFAULTS = {
|
|
|
33
38
|
const BROWSER_REFUSAL = [
|
|
34
39
|
'OpenEmail refused to start in a browser.',
|
|
35
40
|
'',
|
|
36
|
-
' This client carries a workspace API key that can send mail and read
|
|
37
|
-
' you ship to a browser is readable by anyone who opens devtools.',
|
|
41
|
+
' This client carries a workspace API key or an OAuth access token that can send mail and read',
|
|
42
|
+
' the mailbox. Anything you ship to a browser is readable by anyone who opens devtools.',
|
|
38
43
|
'',
|
|
39
44
|
' Call it from a server, a serverless function or a script instead. Disposable inboxes are',
|
|
40
45
|
' the exception: createTempMail() needs no API key and is safe to use in a browser.',
|
|
@@ -44,6 +49,7 @@ const BROWSER_REFUSAL = [
|
|
|
44
49
|
].join('\n');
|
|
45
50
|
const ENV_VARS = {
|
|
46
51
|
API_KEY: 'OPENEMAIL_API_KEY',
|
|
52
|
+
ACCESS_TOKEN: 'OPENEMAIL_ACCESS_TOKEN',
|
|
47
53
|
BASE_URL: 'OPENEMAIL_BASE_URL'
|
|
48
54
|
};
|
|
49
55
|
const API_KEY_PREFIXES = {
|
|
@@ -54,10 +60,22 @@ const API_KEY_MODES = {
|
|
|
54
60
|
LIVE: 'live',
|
|
55
61
|
TEST: 'test'
|
|
56
62
|
};
|
|
63
|
+
const ACCESS_TOKEN_RULES = {
|
|
64
|
+
RESERVED_PREFIX: 'oe_',
|
|
65
|
+
MAX_LENGTH: 512
|
|
66
|
+
};
|
|
67
|
+
const CREDENTIAL_KINDS = {
|
|
68
|
+
API_KEY: 'apiKey',
|
|
69
|
+
OAUTH: 'oauth'
|
|
70
|
+
};
|
|
57
71
|
const CLIENT_MESSAGES = {
|
|
58
72
|
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.`,
|
|
59
|
-
|
|
60
|
-
|
|
73
|
+
CREDENTIAL_REQUIRED: `An OpenEmail API key or OAuth access token is required. Pass { apiKey } or { accessToken }, or set ${ENV_VARS.API_KEY} or ${ENV_VARS.ACCESS_TOKEN}. Create a key in OpenEmail under Settings, API keys.`,
|
|
74
|
+
CREDENTIAL_CONFLICT: 'Pass { apiKey } or { accessToken }, not both. Every request carries one credential, so the client cannot tell which of the two you meant.',
|
|
75
|
+
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. Pass an OAuth access token as { accessToken } instead.',
|
|
76
|
+
ACCESS_TOKEN_SHAPE: `is not an OAuth access token. Pass the access token OpenEmail issued when the app was connected: a string of 1 to ${ACCESS_TOKEN_RULES.MAX_LENGTH} characters that does not begin "${ACCESS_TOKEN_RULES.RESERVED_PREFIX}". An API key goes in { apiKey } instead.`,
|
|
77
|
+
ACCESS_TOKEN_PROVIDED: 'The value the { accessToken } function resolved to',
|
|
78
|
+
BASE_URL_SHAPE: 'is not a usable base URL. Pass an https origin such as "https://api.openemail.uk". Plain http is for a server on this machine only, at localhost, a 127.x.x.x address or ::1, because the client never sends a credential over plain http to any other host.',
|
|
61
79
|
NO_FETCH: 'No global fetch is available. Pass one as { fetch }, or run on Node 20+, Bun, Deno, Cloudflare Workers or a browser.',
|
|
62
80
|
EMPTY_SEGMENT: 'An id must not be empty.',
|
|
63
81
|
FILENAME_REQUIRED: 'A file name is required. Pass it as { filename }, the name the file is stored and downloaded under.',
|
|
@@ -86,6 +104,18 @@ const CONTACT_PHOTO_TYPES = {
|
|
|
86
104
|
GIF: 'image/gif'
|
|
87
105
|
};
|
|
88
106
|
|
|
107
|
+
const DOMAIN_RECORD_STATUSES = {
|
|
108
|
+
FOUND: 'found',
|
|
109
|
+
MISSING: 'missing'
|
|
110
|
+
};
|
|
111
|
+
const DOMAIN_DMARC_STAGES = {
|
|
112
|
+
MISSING: 'missing',
|
|
113
|
+
INVALID: 'invalid',
|
|
114
|
+
MONITOR: 'monitor',
|
|
115
|
+
QUARANTINE: 'quarantine',
|
|
116
|
+
REJECT: 'reject'
|
|
117
|
+
};
|
|
118
|
+
|
|
89
119
|
const MESSAGE_ENCRYPTION_FORMATS = {
|
|
90
120
|
PGP_MIME: 'pgp-mime',
|
|
91
121
|
PGP_SIGNED: 'pgp-signed',
|
|
@@ -151,6 +181,10 @@ const FILE_USAGES = {
|
|
|
151
181
|
LINKED: 'linked',
|
|
152
182
|
SCHEDULED: 'scheduled'
|
|
153
183
|
};
|
|
184
|
+
const FILE_VISIBILITIES = {
|
|
185
|
+
PUBLIC: 'public',
|
|
186
|
+
PRIVATE: 'private'
|
|
187
|
+
};
|
|
154
188
|
|
|
155
189
|
const HTTP_METHODS = {
|
|
156
190
|
GET: 'GET',
|
|
@@ -531,6 +565,8 @@ const PATHS = {
|
|
|
531
565
|
TRACKING_STATS: '/tracking/stats',
|
|
532
566
|
CALENDAR_EVENTS: '/calendar/events',
|
|
533
567
|
SETTINGS: '/settings',
|
|
568
|
+
SECURITY_STEP_UP: '/security/step-up',
|
|
569
|
+
SECURITY_STEP_UP_VERIFY: '/security/step-up/verify',
|
|
534
570
|
ROLES: '/roles',
|
|
535
571
|
ROLES_PERMISSIONS: '/roles/permissions',
|
|
536
572
|
MEMBERS: '/members',
|
|
@@ -589,6 +625,7 @@ const SEGMENTS = {
|
|
|
589
625
|
TRASH: 'trash',
|
|
590
626
|
UNSNOOZE: 'unsnooze',
|
|
591
627
|
UPLOAD: 'upload',
|
|
628
|
+
VERIFY: 'verify',
|
|
592
629
|
VERSIONS: 'versions'
|
|
593
630
|
};
|
|
594
631
|
|
|
@@ -685,6 +722,20 @@ const API_SCOPES = {
|
|
|
685
722
|
KEYS_MANAGE: 'keys:manage'
|
|
686
723
|
};
|
|
687
724
|
|
|
725
|
+
const STEP_UP_METHODS = {
|
|
726
|
+
EMAIL: 'email',
|
|
727
|
+
TOTP: 'totp'
|
|
728
|
+
};
|
|
729
|
+
const STEP_UP_ERROR_CODES = {
|
|
730
|
+
STEP_UP_REQUIRED: 'step_up_required',
|
|
731
|
+
STEP_UP_NOT_APPLICABLE: 'step_up_not_applicable',
|
|
732
|
+
STEP_UP_THROTTLED: 'step_up_throttled',
|
|
733
|
+
STEP_UP_UNDELIVERABLE: 'step_up_undeliverable',
|
|
734
|
+
STEP_UP_CODE_INVALID: 'step_up_code_invalid',
|
|
735
|
+
STEP_UP_CODE_EXPIRED: 'step_up_code_expired',
|
|
736
|
+
STEP_UP_LOCKED: 'step_up_locked'
|
|
737
|
+
};
|
|
738
|
+
|
|
688
739
|
const SUPPRESSION_REASONS = {
|
|
689
740
|
BOUNCE: 'bounce',
|
|
690
741
|
COMPLAINT: 'complaint',
|
|
@@ -757,7 +808,12 @@ const WEBHOOK_SIGNATURE_HEADERS = WEBHOOK_HEADERS;
|
|
|
757
808
|
const CLIENT_REGISTRY_KEY = Symbol.for('openemail.sdk.client.v1');
|
|
758
809
|
const CLIENT_REGISTRY = globalThis;
|
|
759
810
|
|
|
760
|
-
const
|
|
811
|
+
const LOOPBACK_HOSTNAMES = ['localhost', '::1', '[::1]'];
|
|
812
|
+
const LOOPBACK_IPV4_PATTERN = /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
|
|
813
|
+
const LOOPBACK_IPV4_ADDRESS = '127.0.0.1';
|
|
814
|
+
const LOOPBACK_IPV6_ADDRESS = '[::1]';
|
|
815
|
+
const UNSPECIFIED_HOSTNAMES = ['0.0.0.0', '::', '[::]'];
|
|
816
|
+
const LOCAL_HOSTS = [...LOOPBACK_HOSTNAMES, LOOPBACK_IPV4_ADDRESS, ...UNSPECIFIED_HOSTNAMES];
|
|
761
817
|
const ENDPOINT_SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
762
818
|
|
|
763
819
|
const RETRYABLE_STATUSES = new Set(RETRY.STATUSES);
|
|
@@ -866,13 +922,36 @@ const RETRYABLE_METHODS = new Set(RETRY.METHODS);
|
|
|
866
922
|
scopes: [API_SCOPES.WEBHOOKS_READ]});
|
|
867
923
|
|
|
868
924
|
const IsApiKey = (value) => typeof value === 'string' && (value.startsWith(API_KEY_PREFIXES.LIVE) || value.startsWith(API_KEY_PREFIXES.TEST));
|
|
869
|
-
const
|
|
925
|
+
const IsAccessToken = (value) => typeof value === 'string'
|
|
926
|
+
&& value.length > 0
|
|
927
|
+
&& value.length <= ACCESS_TOKEN_RULES.MAX_LENGTH
|
|
928
|
+
&& !value.startsWith(ACCESS_TOKEN_RULES.RESERVED_PREFIX);
|
|
929
|
+
const IsPresent = (value) => value !== undefined && value !== null && value !== '';
|
|
930
|
+
const ModeOf = (apiKey) => apiKey?.startsWith(API_KEY_PREFIXES.TEST) ? API_KEY_MODES.TEST : API_KEY_MODES.LIVE;
|
|
870
931
|
const AssertApiKey = (apiKey, where) => {
|
|
871
932
|
if (!apiKey)
|
|
872
933
|
throw new Error(CLIENT_MESSAGES.API_KEY_REQUIRED);
|
|
873
934
|
if (!IsApiKey(apiKey))
|
|
874
935
|
throw new Error(`${where} ${CLIENT_MESSAGES.API_KEY_SHAPE}`);
|
|
875
936
|
};
|
|
937
|
+
const AssertAccessToken = (accessToken, where) => {
|
|
938
|
+
if (typeof accessToken === 'function')
|
|
939
|
+
return;
|
|
940
|
+
if (!IsAccessToken(accessToken))
|
|
941
|
+
throw new Error(`${where} ${CLIENT_MESSAGES.ACCESS_TOKEN_SHAPE}`);
|
|
942
|
+
};
|
|
943
|
+
const AssertCredential = (apiKey, accessToken, required) => {
|
|
944
|
+
const withKey = IsPresent(apiKey);
|
|
945
|
+
const withToken = IsPresent(accessToken);
|
|
946
|
+
if (withKey && withToken)
|
|
947
|
+
throw new Error(CLIENT_MESSAGES.CREDENTIAL_CONFLICT);
|
|
948
|
+
if (withKey)
|
|
949
|
+
AssertApiKey(apiKey, '{ apiKey }');
|
|
950
|
+
else if (withToken)
|
|
951
|
+
AssertAccessToken(accessToken, '{ accessToken }');
|
|
952
|
+
else if (required)
|
|
953
|
+
throw new Error(CLIENT_MESSAGES.CREDENTIAL_REQUIRED);
|
|
954
|
+
};
|
|
876
955
|
|
|
877
956
|
const IsBrowser = () => {
|
|
878
957
|
const scope = globalThis;
|
|
@@ -910,6 +989,9 @@ class OpenEmailApiError extends Error {
|
|
|
910
989
|
get isScopeMissing() {
|
|
911
990
|
return this.code === ERROR_CODES.INSUFFICIENT_SCOPE;
|
|
912
991
|
}
|
|
992
|
+
get isStepUpRequired() {
|
|
993
|
+
return this.code === STEP_UP_ERROR_CODES.STEP_UP_REQUIRED;
|
|
994
|
+
}
|
|
913
995
|
get isInvalidRequest() {
|
|
914
996
|
return this.type === ERROR_TYPES.INVALID_REQUEST_ERROR;
|
|
915
997
|
}
|
|
@@ -1009,8 +1091,25 @@ const Backoff = (attempt) => {
|
|
|
1009
1091
|
return Math.round(ceiling * (0.5 + Math.random() * 0.5));
|
|
1010
1092
|
};
|
|
1011
1093
|
|
|
1012
|
-
const
|
|
1094
|
+
const PATH_SHAPE = 'is not a usable request path. Pass a path on the API that begins with a single "/", such as "/threads". Anything else could send the request, and the credential it carries, to another host.';
|
|
1095
|
+
const OFF_ORIGIN = 'leaves the API origin, so the request was not sent. Pass a path on the API that begins with a single "/", such as "/threads".';
|
|
1096
|
+
const ApiUrl = (baseUrl, path) => {
|
|
1097
|
+
if (typeof path !== 'string' || !path.startsWith('/') || path.startsWith('//') || path.startsWith('/\\')) {
|
|
1098
|
+
throw new Error(`${JSON.stringify(path)} ${PATH_SHAPE}`);
|
|
1099
|
+
}
|
|
1013
1100
|
const url = `${baseUrl.replace(/\/+$/, '')}${path}`;
|
|
1101
|
+
let origin = null;
|
|
1102
|
+
try {
|
|
1103
|
+
origin = new URL(url).origin;
|
|
1104
|
+
}
|
|
1105
|
+
catch { }
|
|
1106
|
+
if (origin === null || origin !== new URL(baseUrl).origin)
|
|
1107
|
+
throw new Error(`${JSON.stringify(path)} ${OFF_ORIGIN}`);
|
|
1108
|
+
return url;
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
const BuildUrl = (baseUrl, path, query) => {
|
|
1112
|
+
const url = ApiUrl(baseUrl, path);
|
|
1014
1113
|
if (!query)
|
|
1015
1114
|
return url;
|
|
1016
1115
|
const params = new URLSearchParams();
|
|
@@ -1023,6 +1122,8 @@ const BuildUrl = (baseUrl, path, query) => {
|
|
|
1023
1122
|
return serialized ? `${url}?${serialized}` : url;
|
|
1024
1123
|
};
|
|
1025
1124
|
|
|
1125
|
+
const CleartextCredentialMessage = (baseUrl) => `Refused to send a credential to ${baseUrl} over plain http, where anyone on the network can read it. Use an https base URL. Plain http is accepted only for a server on this machine: localhost, a 127.x.x.x address or ::1.`;
|
|
1126
|
+
|
|
1026
1127
|
const Deadline = (timeoutMs, signal) => {
|
|
1027
1128
|
const controller = new AbortController();
|
|
1028
1129
|
let expired = false;
|
|
@@ -1061,6 +1162,23 @@ const IdempotencyKey = () => {
|
|
|
1061
1162
|
return `${IDEMPOTENCY_KEY_PREFIX}${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 14)}`;
|
|
1062
1163
|
};
|
|
1063
1164
|
|
|
1165
|
+
const IsLoopbackHost = (hostname) => {
|
|
1166
|
+
const host = hostname.toLowerCase();
|
|
1167
|
+
return LOOPBACK_HOSTNAMES.includes(host) || LOOPBACK_IPV4_PATTERN.test(host);
|
|
1168
|
+
};
|
|
1169
|
+
|
|
1170
|
+
const IsSecureOrigin = (url) => {
|
|
1171
|
+
try {
|
|
1172
|
+
const parsed = new URL(url);
|
|
1173
|
+
return parsed.protocol === 'https:' || (parsed.protocol === 'http:' && IsLoopbackHost(parsed.hostname));
|
|
1174
|
+
}
|
|
1175
|
+
catch {
|
|
1176
|
+
return false;
|
|
1177
|
+
}
|
|
1178
|
+
};
|
|
1179
|
+
|
|
1180
|
+
const IsUnspecifiedHost = (hostname) => UNSPECIFIED_HOSTNAMES.includes(hostname.toLowerCase());
|
|
1181
|
+
|
|
1064
1182
|
const RetryAfter = (value) => {
|
|
1065
1183
|
if (!value)
|
|
1066
1184
|
return undefined;
|
|
@@ -1119,6 +1237,13 @@ const ToErrorProps = (response, body, rawText, retryAfterSeconds) => {
|
|
|
1119
1237
|
};
|
|
1120
1238
|
};
|
|
1121
1239
|
|
|
1240
|
+
const Substitute = (url) => {
|
|
1241
|
+
const suggestion = new URL(url.href);
|
|
1242
|
+
suggestion.hostname = url.hostname.startsWith('[') ? LOOPBACK_IPV6_ADDRESS : LOOPBACK_IPV4_ADDRESS;
|
|
1243
|
+
return suggestion.href.replace(/\/+$/, '');
|
|
1244
|
+
};
|
|
1245
|
+
const UnspecifiedHostMessage = (baseUrl, url) => `${JSON.stringify(baseUrl)} names ${url.hostname}, an address a server listens on, not one to send requests to. For a server on this machine, use ${Substitute(url)}.`;
|
|
1246
|
+
|
|
1122
1247
|
const Describe = (error) => error instanceof Error ? error.message : String(error);
|
|
1123
1248
|
const ParseJson = (text) => {
|
|
1124
1249
|
if (!text)
|
|
@@ -1130,34 +1255,45 @@ const ParseJson = (text) => {
|
|
|
1130
1255
|
return undefined;
|
|
1131
1256
|
}
|
|
1132
1257
|
};
|
|
1133
|
-
const
|
|
1134
|
-
const trimmed = value.trim().replace(/\/+$/, '');
|
|
1258
|
+
const ParseBaseUrl = (value) => {
|
|
1135
1259
|
try {
|
|
1136
|
-
const parsed = new URL(
|
|
1137
|
-
|
|
1138
|
-
|
|
1260
|
+
const parsed = new URL(value);
|
|
1261
|
+
return parsed.protocol === 'https:' || parsed.protocol === 'http:' ? parsed : undefined;
|
|
1262
|
+
}
|
|
1263
|
+
catch {
|
|
1264
|
+
return undefined;
|
|
1139
1265
|
}
|
|
1140
|
-
|
|
1141
|
-
|
|
1266
|
+
};
|
|
1267
|
+
const ValidBaseUrl = (value) => {
|
|
1268
|
+
const trimmed = value.trim().replace(/\/+$/, '');
|
|
1269
|
+
const parsed = ParseBaseUrl(trimmed);
|
|
1270
|
+
if (!parsed)
|
|
1271
|
+
throw new Error(`${JSON.stringify(value)} ${CLIENT_MESSAGES.BASE_URL_SHAPE}`);
|
|
1272
|
+
if (IsUnspecifiedHost(parsed.hostname))
|
|
1273
|
+
throw new Error(UnspecifiedHostMessage(value, parsed));
|
|
1274
|
+
return trimmed;
|
|
1142
1275
|
};
|
|
1143
1276
|
class Transport {
|
|
1144
1277
|
#apiKey;
|
|
1278
|
+
#accessToken;
|
|
1145
1279
|
#inboxToken;
|
|
1146
1280
|
baseUrl;
|
|
1281
|
+
#secureOrigin;
|
|
1147
1282
|
fetchImpl;
|
|
1148
1283
|
maxRetries;
|
|
1149
1284
|
timeoutMs;
|
|
1150
1285
|
userAgent;
|
|
1151
1286
|
headers;
|
|
1152
1287
|
constructor(options) {
|
|
1153
|
-
|
|
1154
|
-
AssertApiKey(options.apiKey, '{ apiKey }');
|
|
1288
|
+
AssertCredential(options.apiKey, options.accessToken, false);
|
|
1155
1289
|
const fetchImpl = options.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
1156
1290
|
if (!fetchImpl)
|
|
1157
1291
|
throw new Error(CLIENT_MESSAGES.NO_FETCH);
|
|
1158
|
-
this.#apiKey = options.apiKey;
|
|
1292
|
+
this.#apiKey = IsPresent(options.apiKey) ? options.apiKey : undefined;
|
|
1293
|
+
this.#accessToken = IsPresent(options.accessToken) ? options.accessToken : undefined;
|
|
1159
1294
|
this.#inboxToken = options.inboxToken;
|
|
1160
1295
|
this.baseUrl = ValidBaseUrl(options.baseUrl ?? DEFAULTS.BASE_URL);
|
|
1296
|
+
this.#secureOrigin = IsSecureOrigin(this.baseUrl);
|
|
1161
1297
|
this.fetchImpl = fetchImpl;
|
|
1162
1298
|
this.maxRetries = Math.max(0, options.maxRetries ?? DEFAULTS.MAX_RETRIES);
|
|
1163
1299
|
this.timeoutMs = options.timeoutMs ?? DEFAULTS.TIMEOUT_MS;
|
|
@@ -1169,7 +1305,7 @@ class Transport {
|
|
|
1169
1305
|
async request(path, options = {}) {
|
|
1170
1306
|
const method = (options.method ?? HTTP_METHODS.GET).toUpperCase();
|
|
1171
1307
|
const url = BuildUrl(this.baseUrl, path, options.query);
|
|
1172
|
-
const headers = this.headersFor(options);
|
|
1308
|
+
const headers = await this.headersFor(options);
|
|
1173
1309
|
const body = options.raw !== undefined
|
|
1174
1310
|
? options.raw
|
|
1175
1311
|
: options.body === undefined ? undefined : JSON.stringify(options.body);
|
|
@@ -1201,7 +1337,16 @@ class Transport {
|
|
|
1201
1337
|
throw new OpenEmailApiError(ToErrorProps(response, ParseJson(text), text, retryAfter));
|
|
1202
1338
|
}
|
|
1203
1339
|
}
|
|
1204
|
-
|
|
1340
|
+
async resolveAccessToken() {
|
|
1341
|
+
const source = this.#accessToken;
|
|
1342
|
+
if (typeof source !== 'function')
|
|
1343
|
+
return source;
|
|
1344
|
+
const token = await source();
|
|
1345
|
+
if (!IsAccessToken(token))
|
|
1346
|
+
throw new Error(`${CLIENT_MESSAGES.ACCESS_TOKEN_PROVIDED} ${CLIENT_MESSAGES.ACCESS_TOKEN_SHAPE}`);
|
|
1347
|
+
return token;
|
|
1348
|
+
}
|
|
1349
|
+
async headersFor(options) {
|
|
1205
1350
|
const headers = {
|
|
1206
1351
|
...this.headers,
|
|
1207
1352
|
[HEADER_KEYS.ACCEPT]: options.accept ?? CONTENT_TYPES.JSON
|
|
@@ -1215,7 +1360,10 @@ class Transport {
|
|
|
1215
1360
|
if (options.anonymous !== true) {
|
|
1216
1361
|
if (options.apiKey !== undefined)
|
|
1217
1362
|
AssertApiKey(options.apiKey, '{ apiKey } on this call');
|
|
1218
|
-
const
|
|
1363
|
+
const direct = options.inboxToken ?? options.apiKey ?? this.#inboxToken ?? this.#apiKey;
|
|
1364
|
+
if ((direct ?? this.#accessToken) && !this.#secureOrigin)
|
|
1365
|
+
throw new Error(CleartextCredentialMessage(this.baseUrl));
|
|
1366
|
+
const credential = direct ?? await this.resolveAccessToken();
|
|
1219
1367
|
if (credential)
|
|
1220
1368
|
headers[HEADER_KEYS.AUTHORIZATION] = `Bearer ${credential}`;
|
|
1221
1369
|
}
|
|
@@ -1338,6 +1486,9 @@ const BroadcastPath = (id) => `${PATHS.BROADCASTS}/${Segment(id)}`;
|
|
|
1338
1486
|
const BroadcastChildPath = (id, segment) => `${BroadcastPath(id)}/${segment}`;
|
|
1339
1487
|
const BroadcastRecipientPath = (id, emailId) => `${BroadcastChildPath(id, SEGMENTS.RECIPIENTS)}/${Segment(emailId)}`;
|
|
1340
1488
|
const DomainPath = (id) => `${PATHS.DOMAINS}/${Segment(id)}`;
|
|
1489
|
+
const DomainChildPath = (id, segment) => `${DomainPath(id)}/${segment}`;
|
|
1490
|
+
const DomainAddressesPath = (id) => `${DomainPath(id)}/${SEGMENTS.ADDRESSES}`;
|
|
1491
|
+
const DomainAddressPath = (id, addressId) => `${DomainAddressesPath(id)}/${Segment(addressId)}`;
|
|
1341
1492
|
const WebhookPath = (id) => `${PATHS.WEBHOOKS}/${Segment(id)}`;
|
|
1342
1493
|
const WebhookChildPath = (id, segment) => `${WebhookPath(id)}/${segment}`;
|
|
1343
1494
|
const WebhookDeliveryPath = (id, deliveryId) => `${WebhookChildPath(id, SEGMENTS.DELIVERIES)}/${Segment(deliveryId)}`;
|
|
@@ -1410,7 +1561,8 @@ const Addresses = (transport) => {
|
|
|
1410
1561
|
const MemberQuery = (options) => ({
|
|
1411
1562
|
[QUERY_KEYS.Q]: options.q,
|
|
1412
1563
|
[QUERY_KEYS.SOURCE]: options.source,
|
|
1413
|
-
[QUERY_KEYS.SORT]: options.sort
|
|
1564
|
+
[QUERY_KEYS.SORT]: options.sort,
|
|
1565
|
+
[QUERY_KEYS.STATUS]: options.statuses?.length ? options.statuses.join(LIST_SEPARATOR) : undefined
|
|
1414
1566
|
});
|
|
1415
1567
|
const GrowthQuery = (options) => ({
|
|
1416
1568
|
[QUERY_KEYS.AUDIENCE_IDS]: options.audienceIds?.join(LIST_SEPARATOR),
|
|
@@ -1728,12 +1880,52 @@ const Domains = (transport) => ({
|
|
|
1728
1880
|
listAll: (options = {}) => CollectAll(transport, PATHS.DOMAINS, CURSOR_STYLES.CURSOR, options),
|
|
1729
1881
|
iterate: (options = {}) => IteratePages(transport, PATHS.DOMAINS, CURSOR_STYLES.CURSOR, options),
|
|
1730
1882
|
get: (id, options = {}) => transport.request(DomainPath(id), { signal: options.signal, apiKey: options.apiKey }),
|
|
1883
|
+
create: (body, options = {}) => transport.request(PATHS.DOMAINS, {
|
|
1884
|
+
method: HTTP_METHODS.POST,
|
|
1885
|
+
body,
|
|
1886
|
+
signal: options.signal,
|
|
1887
|
+
apiKey: options.apiKey
|
|
1888
|
+
}),
|
|
1889
|
+
verify: (id, options = {}) => transport.request(DomainChildPath(id, SEGMENTS.VERIFY), {
|
|
1890
|
+
method: HTTP_METHODS.POST,
|
|
1891
|
+
repeatable: true,
|
|
1892
|
+
signal: options.signal,
|
|
1893
|
+
apiKey: options.apiKey
|
|
1894
|
+
}),
|
|
1731
1895
|
update: (id, patch, options = {}) => transport.request(DomainPath(id), {
|
|
1732
1896
|
method: HTTP_METHODS.PATCH,
|
|
1733
1897
|
body: patch,
|
|
1734
1898
|
repeatable: true,
|
|
1735
1899
|
signal: options.signal,
|
|
1736
1900
|
apiKey: options.apiKey
|
|
1901
|
+
}),
|
|
1902
|
+
delete: (id, options = {}) => transport.request(DomainPath(id), {
|
|
1903
|
+
method: HTTP_METHODS.DELETE,
|
|
1904
|
+
signal: options.signal,
|
|
1905
|
+
apiKey: options.apiKey
|
|
1906
|
+
}),
|
|
1907
|
+
listAddresses: (id, options = {}) => FetchPage(transport, DomainAddressesPath(id), CURSOR_STYLES.CURSOR, options),
|
|
1908
|
+
listAllAddresses: (id, options = {}) => CollectAll(transport, DomainAddressesPath(id), CURSOR_STYLES.CURSOR, options),
|
|
1909
|
+
iterateAddresses: (id, options = {}) => IteratePages(transport, DomainAddressesPath(id), CURSOR_STYLES.CURSOR, options),
|
|
1910
|
+
createAddress: (id, body, options = {}) => transport.request(DomainAddressesPath(id), {
|
|
1911
|
+
method: HTTP_METHODS.POST,
|
|
1912
|
+
body,
|
|
1913
|
+
repeatable: true,
|
|
1914
|
+
signal: options.signal,
|
|
1915
|
+
apiKey: options.apiKey
|
|
1916
|
+
}),
|
|
1917
|
+
getAddress: (id, addressId, options = {}) => transport.request(DomainAddressPath(id, addressId), { signal: options.signal, apiKey: options.apiKey }),
|
|
1918
|
+
updateAddress: (id, addressId, patch, options = {}) => transport.request(DomainAddressPath(id, addressId), {
|
|
1919
|
+
method: HTTP_METHODS.PATCH,
|
|
1920
|
+
body: patch,
|
|
1921
|
+
repeatable: true,
|
|
1922
|
+
signal: options.signal,
|
|
1923
|
+
apiKey: options.apiKey
|
|
1924
|
+
}),
|
|
1925
|
+
deleteAddress: (id, addressId, options = {}) => transport.request(DomainAddressPath(id, addressId), {
|
|
1926
|
+
method: HTTP_METHODS.DELETE,
|
|
1927
|
+
signal: options.signal,
|
|
1928
|
+
apiKey: options.apiKey
|
|
1737
1929
|
})
|
|
1738
1930
|
});
|
|
1739
1931
|
|
|
@@ -2229,6 +2421,22 @@ const Rules = (transport) => ({
|
|
|
2229
2421
|
iterateRuns: (options = {}) => IteratePages(transport, PATHS.RULES_RUNS, CURSOR_STYLES.CURSOR, options, RunsQuery(options))
|
|
2230
2422
|
});
|
|
2231
2423
|
|
|
2424
|
+
const Security = (transport) => ({
|
|
2425
|
+
stepUpStatus: (options = {}) => transport.request(PATHS.SECURITY_STEP_UP, { signal: options.signal, apiKey: options.apiKey }),
|
|
2426
|
+
beginStepUp: (body = {}, options = {}) => transport.request(PATHS.SECURITY_STEP_UP, {
|
|
2427
|
+
method: HTTP_METHODS.POST,
|
|
2428
|
+
body,
|
|
2429
|
+
signal: options.signal,
|
|
2430
|
+
apiKey: options.apiKey
|
|
2431
|
+
}),
|
|
2432
|
+
verifyStepUp: (body, options = {}) => transport.request(PATHS.SECURITY_STEP_UP_VERIFY, {
|
|
2433
|
+
method: HTTP_METHODS.POST,
|
|
2434
|
+
body,
|
|
2435
|
+
signal: options.signal,
|
|
2436
|
+
apiKey: options.apiKey
|
|
2437
|
+
})
|
|
2438
|
+
});
|
|
2439
|
+
|
|
2232
2440
|
const Settings = (transport) => ({
|
|
2233
2441
|
get: (options = {}) => transport.request(PATHS.SETTINGS, {
|
|
2234
2442
|
query: { [QUERY_KEYS.ADDRESS]: options.address },
|
|
@@ -2617,6 +2825,7 @@ class OpenEmail {
|
|
|
2617
2825
|
transport;
|
|
2618
2826
|
mode;
|
|
2619
2827
|
me;
|
|
2828
|
+
security;
|
|
2620
2829
|
keys;
|
|
2621
2830
|
addresses;
|
|
2622
2831
|
languages;
|
|
@@ -2643,12 +2852,13 @@ class OpenEmail {
|
|
|
2643
2852
|
tempMail;
|
|
2644
2853
|
constructor(configuration) {
|
|
2645
2854
|
const options = typeof configuration === 'string' ? { apiKey: configuration } : configuration;
|
|
2646
|
-
|
|
2855
|
+
AssertCredential(options.apiKey, options.accessToken, true);
|
|
2647
2856
|
if (IsBrowser() && options.dangerouslyAllowBrowser !== true)
|
|
2648
2857
|
throw new Error(BROWSER_REFUSAL);
|
|
2649
2858
|
this.transport = new Transport(options);
|
|
2650
2859
|
this.mode = ModeOf(options.apiKey);
|
|
2651
2860
|
this.me = Me(this.transport);
|
|
2861
|
+
this.security = Security(this.transport);
|
|
2652
2862
|
this.keys = Keys(this.transport);
|
|
2653
2863
|
this.addresses = Addresses(this.transport);
|
|
2654
2864
|
this.languages = Languages(this.transport);
|
|
@@ -2690,6 +2900,19 @@ const FromEnvironment = (name) => {
|
|
|
2690
2900
|
}
|
|
2691
2901
|
};
|
|
2692
2902
|
|
|
2903
|
+
const IsLocalHost = (hostname) => {
|
|
2904
|
+
const host = hostname.toLowerCase();
|
|
2905
|
+
return LOCAL_HOSTS.includes(host) || LOOPBACK_IPV4_PATTERN.test(host);
|
|
2906
|
+
};
|
|
2907
|
+
|
|
2908
|
+
const HostnameOf = (authority) => {
|
|
2909
|
+
try {
|
|
2910
|
+
return new URL(`http://${authority}`).hostname;
|
|
2911
|
+
}
|
|
2912
|
+
catch {
|
|
2913
|
+
return '';
|
|
2914
|
+
}
|
|
2915
|
+
};
|
|
2693
2916
|
const NormaliseEndpoint = (value) => {
|
|
2694
2917
|
if (!value)
|
|
2695
2918
|
return undefined;
|
|
@@ -2698,15 +2921,18 @@ const NormaliseEndpoint = (value) => {
|
|
|
2698
2921
|
return undefined;
|
|
2699
2922
|
if (ENDPOINT_SCHEME_PATTERN.test(trimmed))
|
|
2700
2923
|
return trimmed;
|
|
2701
|
-
|
|
2702
|
-
return `${LOCAL_HOSTS.includes(host) ? 'http' : 'https'}://${trimmed}`;
|
|
2924
|
+
return `${IsLocalHost(HostnameOf(trimmed)) ? 'http' : 'https'}://${trimmed}`;
|
|
2703
2925
|
};
|
|
2704
2926
|
|
|
2705
2927
|
const CreateClient = (configuration = {}) => {
|
|
2706
2928
|
const options = typeof configuration === 'string' ? { apiKey: configuration } : configuration;
|
|
2929
|
+
const explicit = options.apiKey !== undefined || options.accessToken !== undefined;
|
|
2930
|
+
const apiKey = explicit ? options.apiKey : FromEnvironment(ENV_VARS.API_KEY);
|
|
2931
|
+
const accessToken = explicit || apiKey !== undefined ? options.accessToken : FromEnvironment(ENV_VARS.ACCESS_TOKEN);
|
|
2707
2932
|
return new OpenEmail({
|
|
2708
2933
|
...options,
|
|
2709
|
-
apiKey
|
|
2934
|
+
apiKey,
|
|
2935
|
+
accessToken,
|
|
2710
2936
|
baseUrl: NormaliseEndpoint(options.baseUrl ?? FromEnvironment(ENV_VARS.BASE_URL))
|
|
2711
2937
|
});
|
|
2712
2938
|
};
|
|
@@ -2714,6 +2940,7 @@ const CreateClient = (configuration = {}) => {
|
|
|
2714
2940
|
const CreateTempMail = (options = {}) => TempMail(new Transport({
|
|
2715
2941
|
...options,
|
|
2716
2942
|
apiKey: undefined,
|
|
2943
|
+
accessToken: undefined,
|
|
2717
2944
|
baseUrl: NormaliseEndpoint(options.baseUrl ?? FromEnvironment(ENV_VARS.BASE_URL))
|
|
2718
2945
|
}));
|
|
2719
2946
|
|
|
@@ -2841,9 +3068,10 @@ const createTempMail = CreateTempMail;
|
|
|
2841
3068
|
const verifyWebhookSignature = VerifyWebhookSignature;
|
|
2842
3069
|
const toBase64 = ToBase64;
|
|
2843
3070
|
const isApiKey = IsApiKey;
|
|
3071
|
+
const isAccessToken = IsAccessToken;
|
|
2844
3072
|
const isSealed = IsSealed;
|
|
2845
3073
|
const resolveLanguage = ResolveLanguage;
|
|
2846
3074
|
const languageByCode = LanguageByCode;
|
|
2847
3075
|
const isRtlLanguage = IsRtlLanguage;
|
|
2848
3076
|
|
|
2849
|
-
export { API_SCOPES, BROADCAST_RECIPIENT_FILTERS, BROADCAST_STATUSES, CONTACT_BLOCK_LISTS, CONTACT_PHOTO_TYPES, CONTACT_THREAD_SORTS, ERROR_TYPES, FILE_DIRECTIONS, FILE_KINDS, FILE_SORTS, FILE_USAGES, LANGUAGES, MESSAGE_ENCRYPTION_FORMATS, OpenEmail, OpenEmailApiError, OpenEmailNetworkError, PAGE_LIMITS, PEOPLE_SORTS, PROVIDER_IMPORT_DOMAIN_STATES, PROVIDER_IMPORT_RESOURCES, PROVIDER_IMPORT_STATUSES, RULE_ACTIONS, RULE_FIELDS, RULE_OPERATORS, SUPPRESSION_REASONS, THREAD_SORTS, VERSION, WEBHOOK_EVENTS, WEBHOOK_REPLAY_ERROR_CODES, WEBHOOK_SIGNATURE_HEADERS, createClient, createOpenEmail, createTempMail, OpenEmail as default, getClient, init, isApiKey, isRtlLanguage, isSealed, languageByCode, openemail, resetClient, resolveLanguage, toBase64, verifyWebhookSignature };
|
|
3077
|
+
export { API_SCOPES, AUDIENCE_MEMBER_STATUSES, BROADCAST_RECIPIENT_FILTERS, BROADCAST_STATUSES, CONTACT_BLOCK_LISTS, CONTACT_PHOTO_TYPES, CONTACT_THREAD_SORTS, CREDENTIAL_KINDS, DOMAIN_DMARC_STAGES, DOMAIN_RECORD_STATUSES, ERROR_TYPES, FILE_DIRECTIONS, FILE_KINDS, FILE_SORTS, FILE_USAGES, FILE_VISIBILITIES, LANGUAGES, MESSAGE_ENCRYPTION_FORMATS, OpenEmail, OpenEmailApiError, OpenEmailNetworkError, PAGE_LIMITS, PEOPLE_SORTS, PROVIDER_IMPORT_DOMAIN_STATES, PROVIDER_IMPORT_RESOURCES, PROVIDER_IMPORT_STATUSES, RULE_ACTIONS, RULE_FIELDS, RULE_OPERATORS, STEP_UP_ERROR_CODES, STEP_UP_METHODS, SUPPRESSION_REASONS, THREAD_SORTS, VERSION, WEBHOOK_EVENTS, WEBHOOK_REPLAY_ERROR_CODES, WEBHOOK_SIGNATURE_HEADERS, createClient, createOpenEmail, createTempMail, OpenEmail as default, getClient, init, isAccessToken, isApiKey, isRtlLanguage, isSealed, languageByCode, openemail, resetClient, resolveLanguage, toBase64, verifyWebhookSignature };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openemail/sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"author": "OpenEmail",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"description": "The official TypeScript SDK for the OpenEmail API. Send email and broadcasts, work with threads, drafts, labels, templates, rules, webhooks, tracking, contacts, audiences, domains, suppressions, files, API keys, roles, members, mailbox imports and disposable inboxes from code.",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"sideEffects": false,
|
|
40
40
|
"homepage": "https://openemail.uk/docs/sdk",
|
|
41
41
|
"bugs": {
|
|
42
|
-
"url": "https://openemail.uk/
|
|
42
|
+
"url": "https://openemail.uk/contact"
|
|
43
43
|
},
|
|
44
44
|
"keywords": [
|
|
45
45
|
"openemail",
|