@openemail/sdk 0.0.5 → 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 +191 -25
- package/index.d.cts +112 -9
- package/index.d.ts +112 -9
- package/index.js +188 -26
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -24,7 +24,7 @@ const BROADCAST_RECIPIENT_FILTERS = {
|
|
|
24
24
|
UNSUBSCRIBED: 'unsubscribed'
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
var version = "0.0.
|
|
27
|
+
var version = "0.0.6";
|
|
28
28
|
|
|
29
29
|
const BUILD_BASE_URL = 'https://api.openemail.uk';
|
|
30
30
|
|
|
@@ -38,8 +38,8 @@ const DEFAULTS = {
|
|
|
38
38
|
const BROWSER_REFUSAL = [
|
|
39
39
|
'OpenEmail refused to start in a browser.',
|
|
40
40
|
'',
|
|
41
|
-
' This client carries a workspace API key that can send mail and read
|
|
42
|
-
' 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.',
|
|
43
43
|
'',
|
|
44
44
|
' Call it from a server, a serverless function or a script instead. Disposable inboxes are',
|
|
45
45
|
' the exception: createTempMail() needs no API key and is safe to use in a browser.',
|
|
@@ -49,6 +49,7 @@ const BROWSER_REFUSAL = [
|
|
|
49
49
|
].join('\n');
|
|
50
50
|
const ENV_VARS = {
|
|
51
51
|
API_KEY: 'OPENEMAIL_API_KEY',
|
|
52
|
+
ACCESS_TOKEN: 'OPENEMAIL_ACCESS_TOKEN',
|
|
52
53
|
BASE_URL: 'OPENEMAIL_BASE_URL'
|
|
53
54
|
};
|
|
54
55
|
const API_KEY_PREFIXES = {
|
|
@@ -59,10 +60,22 @@ const API_KEY_MODES = {
|
|
|
59
60
|
LIVE: 'live',
|
|
60
61
|
TEST: 'test'
|
|
61
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
|
+
};
|
|
62
71
|
const CLIENT_MESSAGES = {
|
|
63
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.`,
|
|
64
|
-
|
|
65
|
-
|
|
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.',
|
|
66
79
|
NO_FETCH: 'No global fetch is available. Pass one as { fetch }, or run on Node 20+, Bun, Deno, Cloudflare Workers or a browser.',
|
|
67
80
|
EMPTY_SEGMENT: 'An id must not be empty.',
|
|
68
81
|
FILENAME_REQUIRED: 'A file name is required. Pass it as { filename }, the name the file is stored and downloaded under.',
|
|
@@ -552,6 +565,8 @@ const PATHS = {
|
|
|
552
565
|
TRACKING_STATS: '/tracking/stats',
|
|
553
566
|
CALENDAR_EVENTS: '/calendar/events',
|
|
554
567
|
SETTINGS: '/settings',
|
|
568
|
+
SECURITY_STEP_UP: '/security/step-up',
|
|
569
|
+
SECURITY_STEP_UP_VERIFY: '/security/step-up/verify',
|
|
555
570
|
ROLES: '/roles',
|
|
556
571
|
ROLES_PERMISSIONS: '/roles/permissions',
|
|
557
572
|
MEMBERS: '/members',
|
|
@@ -707,6 +722,20 @@ const API_SCOPES = {
|
|
|
707
722
|
KEYS_MANAGE: 'keys:manage'
|
|
708
723
|
};
|
|
709
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
|
+
|
|
710
739
|
const SUPPRESSION_REASONS = {
|
|
711
740
|
BOUNCE: 'bounce',
|
|
712
741
|
COMPLAINT: 'complaint',
|
|
@@ -779,7 +808,12 @@ const WEBHOOK_SIGNATURE_HEADERS = WEBHOOK_HEADERS;
|
|
|
779
808
|
const CLIENT_REGISTRY_KEY = Symbol.for('openemail.sdk.client.v1');
|
|
780
809
|
const CLIENT_REGISTRY = globalThis;
|
|
781
810
|
|
|
782
|
-
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];
|
|
783
817
|
const ENDPOINT_SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
784
818
|
|
|
785
819
|
const RETRYABLE_STATUSES = new Set(RETRY.STATUSES);
|
|
@@ -888,13 +922,36 @@ const RETRYABLE_METHODS = new Set(RETRY.METHODS);
|
|
|
888
922
|
scopes: [API_SCOPES.WEBHOOKS_READ]});
|
|
889
923
|
|
|
890
924
|
const IsApiKey = (value) => typeof value === 'string' && (value.startsWith(API_KEY_PREFIXES.LIVE) || value.startsWith(API_KEY_PREFIXES.TEST));
|
|
891
|
-
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;
|
|
892
931
|
const AssertApiKey = (apiKey, where) => {
|
|
893
932
|
if (!apiKey)
|
|
894
933
|
throw new Error(CLIENT_MESSAGES.API_KEY_REQUIRED);
|
|
895
934
|
if (!IsApiKey(apiKey))
|
|
896
935
|
throw new Error(`${where} ${CLIENT_MESSAGES.API_KEY_SHAPE}`);
|
|
897
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
|
+
};
|
|
898
955
|
|
|
899
956
|
const IsBrowser = () => {
|
|
900
957
|
const scope = globalThis;
|
|
@@ -932,6 +989,9 @@ class OpenEmailApiError extends Error {
|
|
|
932
989
|
get isScopeMissing() {
|
|
933
990
|
return this.code === ERROR_CODES.INSUFFICIENT_SCOPE;
|
|
934
991
|
}
|
|
992
|
+
get isStepUpRequired() {
|
|
993
|
+
return this.code === STEP_UP_ERROR_CODES.STEP_UP_REQUIRED;
|
|
994
|
+
}
|
|
935
995
|
get isInvalidRequest() {
|
|
936
996
|
return this.type === ERROR_TYPES.INVALID_REQUEST_ERROR;
|
|
937
997
|
}
|
|
@@ -1031,8 +1091,25 @@ const Backoff = (attempt) => {
|
|
|
1031
1091
|
return Math.round(ceiling * (0.5 + Math.random() * 0.5));
|
|
1032
1092
|
};
|
|
1033
1093
|
|
|
1034
|
-
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
|
+
}
|
|
1035
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);
|
|
1036
1113
|
if (!query)
|
|
1037
1114
|
return url;
|
|
1038
1115
|
const params = new URLSearchParams();
|
|
@@ -1045,6 +1122,8 @@ const BuildUrl = (baseUrl, path, query) => {
|
|
|
1045
1122
|
return serialized ? `${url}?${serialized}` : url;
|
|
1046
1123
|
};
|
|
1047
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
|
+
|
|
1048
1127
|
const Deadline = (timeoutMs, signal) => {
|
|
1049
1128
|
const controller = new AbortController();
|
|
1050
1129
|
let expired = false;
|
|
@@ -1083,6 +1162,23 @@ const IdempotencyKey = () => {
|
|
|
1083
1162
|
return `${IDEMPOTENCY_KEY_PREFIX}${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 14)}`;
|
|
1084
1163
|
};
|
|
1085
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
|
+
|
|
1086
1182
|
const RetryAfter = (value) => {
|
|
1087
1183
|
if (!value)
|
|
1088
1184
|
return undefined;
|
|
@@ -1141,6 +1237,13 @@ const ToErrorProps = (response, body, rawText, retryAfterSeconds) => {
|
|
|
1141
1237
|
};
|
|
1142
1238
|
};
|
|
1143
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
|
+
|
|
1144
1247
|
const Describe = (error) => error instanceof Error ? error.message : String(error);
|
|
1145
1248
|
const ParseJson = (text) => {
|
|
1146
1249
|
if (!text)
|
|
@@ -1152,34 +1255,45 @@ const ParseJson = (text) => {
|
|
|
1152
1255
|
return undefined;
|
|
1153
1256
|
}
|
|
1154
1257
|
};
|
|
1155
|
-
const
|
|
1156
|
-
const trimmed = value.trim().replace(/\/+$/, '');
|
|
1258
|
+
const ParseBaseUrl = (value) => {
|
|
1157
1259
|
try {
|
|
1158
|
-
const parsed = new URL(
|
|
1159
|
-
|
|
1160
|
-
|
|
1260
|
+
const parsed = new URL(value);
|
|
1261
|
+
return parsed.protocol === 'https:' || parsed.protocol === 'http:' ? parsed : undefined;
|
|
1262
|
+
}
|
|
1263
|
+
catch {
|
|
1264
|
+
return undefined;
|
|
1161
1265
|
}
|
|
1162
|
-
|
|
1163
|
-
|
|
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;
|
|
1164
1275
|
};
|
|
1165
1276
|
class Transport {
|
|
1166
1277
|
#apiKey;
|
|
1278
|
+
#accessToken;
|
|
1167
1279
|
#inboxToken;
|
|
1168
1280
|
baseUrl;
|
|
1281
|
+
#secureOrigin;
|
|
1169
1282
|
fetchImpl;
|
|
1170
1283
|
maxRetries;
|
|
1171
1284
|
timeoutMs;
|
|
1172
1285
|
userAgent;
|
|
1173
1286
|
headers;
|
|
1174
1287
|
constructor(options) {
|
|
1175
|
-
|
|
1176
|
-
AssertApiKey(options.apiKey, '{ apiKey }');
|
|
1288
|
+
AssertCredential(options.apiKey, options.accessToken, false);
|
|
1177
1289
|
const fetchImpl = options.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
1178
1290
|
if (!fetchImpl)
|
|
1179
1291
|
throw new Error(CLIENT_MESSAGES.NO_FETCH);
|
|
1180
|
-
this.#apiKey = options.apiKey;
|
|
1292
|
+
this.#apiKey = IsPresent(options.apiKey) ? options.apiKey : undefined;
|
|
1293
|
+
this.#accessToken = IsPresent(options.accessToken) ? options.accessToken : undefined;
|
|
1181
1294
|
this.#inboxToken = options.inboxToken;
|
|
1182
1295
|
this.baseUrl = ValidBaseUrl(options.baseUrl ?? DEFAULTS.BASE_URL);
|
|
1296
|
+
this.#secureOrigin = IsSecureOrigin(this.baseUrl);
|
|
1183
1297
|
this.fetchImpl = fetchImpl;
|
|
1184
1298
|
this.maxRetries = Math.max(0, options.maxRetries ?? DEFAULTS.MAX_RETRIES);
|
|
1185
1299
|
this.timeoutMs = options.timeoutMs ?? DEFAULTS.TIMEOUT_MS;
|
|
@@ -1191,7 +1305,7 @@ class Transport {
|
|
|
1191
1305
|
async request(path, options = {}) {
|
|
1192
1306
|
const method = (options.method ?? HTTP_METHODS.GET).toUpperCase();
|
|
1193
1307
|
const url = BuildUrl(this.baseUrl, path, options.query);
|
|
1194
|
-
const headers = this.headersFor(options);
|
|
1308
|
+
const headers = await this.headersFor(options);
|
|
1195
1309
|
const body = options.raw !== undefined
|
|
1196
1310
|
? options.raw
|
|
1197
1311
|
: options.body === undefined ? undefined : JSON.stringify(options.body);
|
|
@@ -1223,7 +1337,16 @@ class Transport {
|
|
|
1223
1337
|
throw new OpenEmailApiError(ToErrorProps(response, ParseJson(text), text, retryAfter));
|
|
1224
1338
|
}
|
|
1225
1339
|
}
|
|
1226
|
-
|
|
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) {
|
|
1227
1350
|
const headers = {
|
|
1228
1351
|
...this.headers,
|
|
1229
1352
|
[HEADER_KEYS.ACCEPT]: options.accept ?? CONTENT_TYPES.JSON
|
|
@@ -1237,7 +1360,10 @@ class Transport {
|
|
|
1237
1360
|
if (options.anonymous !== true) {
|
|
1238
1361
|
if (options.apiKey !== undefined)
|
|
1239
1362
|
AssertApiKey(options.apiKey, '{ apiKey } on this call');
|
|
1240
|
-
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();
|
|
1241
1367
|
if (credential)
|
|
1242
1368
|
headers[HEADER_KEYS.AUTHORIZATION] = `Bearer ${credential}`;
|
|
1243
1369
|
}
|
|
@@ -2295,6 +2421,22 @@ const Rules = (transport) => ({
|
|
|
2295
2421
|
iterateRuns: (options = {}) => IteratePages(transport, PATHS.RULES_RUNS, CURSOR_STYLES.CURSOR, options, RunsQuery(options))
|
|
2296
2422
|
});
|
|
2297
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
|
+
|
|
2298
2440
|
const Settings = (transport) => ({
|
|
2299
2441
|
get: (options = {}) => transport.request(PATHS.SETTINGS, {
|
|
2300
2442
|
query: { [QUERY_KEYS.ADDRESS]: options.address },
|
|
@@ -2683,6 +2825,7 @@ class OpenEmail {
|
|
|
2683
2825
|
transport;
|
|
2684
2826
|
mode;
|
|
2685
2827
|
me;
|
|
2828
|
+
security;
|
|
2686
2829
|
keys;
|
|
2687
2830
|
addresses;
|
|
2688
2831
|
languages;
|
|
@@ -2709,12 +2852,13 @@ class OpenEmail {
|
|
|
2709
2852
|
tempMail;
|
|
2710
2853
|
constructor(configuration) {
|
|
2711
2854
|
const options = typeof configuration === 'string' ? { apiKey: configuration } : configuration;
|
|
2712
|
-
|
|
2855
|
+
AssertCredential(options.apiKey, options.accessToken, true);
|
|
2713
2856
|
if (IsBrowser() && options.dangerouslyAllowBrowser !== true)
|
|
2714
2857
|
throw new Error(BROWSER_REFUSAL);
|
|
2715
2858
|
this.transport = new Transport(options);
|
|
2716
2859
|
this.mode = ModeOf(options.apiKey);
|
|
2717
2860
|
this.me = Me(this.transport);
|
|
2861
|
+
this.security = Security(this.transport);
|
|
2718
2862
|
this.keys = Keys(this.transport);
|
|
2719
2863
|
this.addresses = Addresses(this.transport);
|
|
2720
2864
|
this.languages = Languages(this.transport);
|
|
@@ -2756,6 +2900,19 @@ const FromEnvironment = (name) => {
|
|
|
2756
2900
|
}
|
|
2757
2901
|
};
|
|
2758
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
|
+
};
|
|
2759
2916
|
const NormaliseEndpoint = (value) => {
|
|
2760
2917
|
if (!value)
|
|
2761
2918
|
return undefined;
|
|
@@ -2764,15 +2921,18 @@ const NormaliseEndpoint = (value) => {
|
|
|
2764
2921
|
return undefined;
|
|
2765
2922
|
if (ENDPOINT_SCHEME_PATTERN.test(trimmed))
|
|
2766
2923
|
return trimmed;
|
|
2767
|
-
|
|
2768
|
-
return `${LOCAL_HOSTS.includes(host) ? 'http' : 'https'}://${trimmed}`;
|
|
2924
|
+
return `${IsLocalHost(HostnameOf(trimmed)) ? 'http' : 'https'}://${trimmed}`;
|
|
2769
2925
|
};
|
|
2770
2926
|
|
|
2771
2927
|
const CreateClient = (configuration = {}) => {
|
|
2772
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);
|
|
2773
2932
|
return new OpenEmail({
|
|
2774
2933
|
...options,
|
|
2775
|
-
apiKey
|
|
2934
|
+
apiKey,
|
|
2935
|
+
accessToken,
|
|
2776
2936
|
baseUrl: NormaliseEndpoint(options.baseUrl ?? FromEnvironment(ENV_VARS.BASE_URL))
|
|
2777
2937
|
});
|
|
2778
2938
|
};
|
|
@@ -2780,6 +2940,7 @@ const CreateClient = (configuration = {}) => {
|
|
|
2780
2940
|
const CreateTempMail = (options = {}) => TempMail(new Transport({
|
|
2781
2941
|
...options,
|
|
2782
2942
|
apiKey: undefined,
|
|
2943
|
+
accessToken: undefined,
|
|
2783
2944
|
baseUrl: NormaliseEndpoint(options.baseUrl ?? FromEnvironment(ENV_VARS.BASE_URL))
|
|
2784
2945
|
}));
|
|
2785
2946
|
|
|
@@ -2907,9 +3068,10 @@ const createTempMail = CreateTempMail;
|
|
|
2907
3068
|
const verifyWebhookSignature = VerifyWebhookSignature;
|
|
2908
3069
|
const toBase64 = ToBase64;
|
|
2909
3070
|
const isApiKey = IsApiKey;
|
|
3071
|
+
const isAccessToken = IsAccessToken;
|
|
2910
3072
|
const isSealed = IsSealed;
|
|
2911
3073
|
const resolveLanguage = ResolveLanguage;
|
|
2912
3074
|
const languageByCode = LanguageByCode;
|
|
2913
3075
|
const isRtlLanguage = IsRtlLanguage;
|
|
2914
3076
|
|
|
2915
|
-
export { API_SCOPES, AUDIENCE_MEMBER_STATUSES, BROADCAST_RECIPIENT_FILTERS, BROADCAST_STATUSES, CONTACT_BLOCK_LISTS, CONTACT_PHOTO_TYPES, CONTACT_THREAD_SORTS, 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, 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",
|