@dregs/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/LICENSE +21 -0
- package/README.md +287 -0
- package/dist/chunk-AIUWT3Z7.js +149 -0
- package/dist/chunk-AIUWT3Z7.js.map +1 -0
- package/dist/chunk-K63AUEQR.cjs +170 -0
- package/dist/chunk-K63AUEQR.cjs.map +1 -0
- package/dist/index.cjs +645 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +576 -0
- package/dist/index.d.ts +576 -0
- package/dist/index.js +554 -0
- package/dist/index.js.map +1 -0
- package/dist/webhooks.cjs +36 -0
- package/dist/webhooks.cjs.map +1 -0
- package/dist/webhooks.d.cts +83 -0
- package/dist/webhooks.d.ts +83 -0
- package/dist/webhooks.js +3 -0
- package/dist/webhooks.js.map +1 -0
- package/package.json +86 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkK63AUEQR_cjs = require('./chunk-K63AUEQR.cjs');
|
|
4
|
+
|
|
5
|
+
// src/version.ts
|
|
6
|
+
var VERSION = "0.1.0";
|
|
7
|
+
|
|
8
|
+
// src/models.ts
|
|
9
|
+
var CATEGORIES = [
|
|
10
|
+
"HUMANITY",
|
|
11
|
+
"AUTHENTICITY",
|
|
12
|
+
"UNIQUENESS",
|
|
13
|
+
"BEHAVIOR"
|
|
14
|
+
];
|
|
15
|
+
var Scores = class extends Array {
|
|
16
|
+
/**
|
|
17
|
+
* `map`, `filter`, and `slice` return plain arrays rather than trying to rebuild a `Scores`
|
|
18
|
+
* through a constructor whose shape they know nothing about.
|
|
19
|
+
*/
|
|
20
|
+
static get [Symbol.species]() {
|
|
21
|
+
return Array;
|
|
22
|
+
}
|
|
23
|
+
constructor(items = []) {
|
|
24
|
+
super();
|
|
25
|
+
for (const item of items) {
|
|
26
|
+
this.push(item);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Returns the score for `category`, or `null` when it has not been scored. */
|
|
30
|
+
get(category) {
|
|
31
|
+
return this.find((score) => score.category === category) ?? null;
|
|
32
|
+
}
|
|
33
|
+
value(category) {
|
|
34
|
+
return this.get(category)?.value ?? null;
|
|
35
|
+
}
|
|
36
|
+
/** How likely it is that a person, rather than a script, is behind the account. */
|
|
37
|
+
get humanity() {
|
|
38
|
+
return this.value("HUMANITY");
|
|
39
|
+
}
|
|
40
|
+
/** How genuine the details on the account look. */
|
|
41
|
+
get authenticity() {
|
|
42
|
+
return this.value("AUTHENTICITY");
|
|
43
|
+
}
|
|
44
|
+
/** How distinct the account is from others in the same tenant. */
|
|
45
|
+
get uniqueness() {
|
|
46
|
+
return this.value("UNIQUENESS");
|
|
47
|
+
}
|
|
48
|
+
/** How ordinary the account's activity looks. */
|
|
49
|
+
get behavior() {
|
|
50
|
+
return this.value("BEHAVIOR");
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
function asPayload(value) {
|
|
54
|
+
return isPayload(value) ? value : {};
|
|
55
|
+
}
|
|
56
|
+
function isPayload(value) {
|
|
57
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
58
|
+
}
|
|
59
|
+
function asString(value) {
|
|
60
|
+
return typeof value === "string" ? value : null;
|
|
61
|
+
}
|
|
62
|
+
function asNumber(value) {
|
|
63
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
64
|
+
}
|
|
65
|
+
function asDate(value) {
|
|
66
|
+
if (typeof value !== "string" || !value) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const parsed = new Date(value);
|
|
70
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
71
|
+
}
|
|
72
|
+
function asCategory(value) {
|
|
73
|
+
return typeof value === "string" && CATEGORIES.includes(value) ? value : null;
|
|
74
|
+
}
|
|
75
|
+
function asArray(value) {
|
|
76
|
+
return Array.isArray(value) ? value.filter(isPayload) : [];
|
|
77
|
+
}
|
|
78
|
+
function parseTrackResult(payload) {
|
|
79
|
+
const body = asPayload(payload);
|
|
80
|
+
const id = asString(body.id);
|
|
81
|
+
return {
|
|
82
|
+
status: asString(body.status),
|
|
83
|
+
id,
|
|
84
|
+
fingerprint: asString(body.fingerprint),
|
|
85
|
+
accepted: id !== null,
|
|
86
|
+
raw: body
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function parseBadge(payload) {
|
|
90
|
+
const body = asPayload(payload);
|
|
91
|
+
return {
|
|
92
|
+
slug: asString(body.slug),
|
|
93
|
+
name: asString(body.name),
|
|
94
|
+
type: asString(body.type),
|
|
95
|
+
explanation: asString(body.explanation),
|
|
96
|
+
metadata: asPayload(body.metadata),
|
|
97
|
+
raw: body
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function parseObservation(payload) {
|
|
101
|
+
const body = asPayload(payload);
|
|
102
|
+
return {
|
|
103
|
+
category: asCategory(body.category),
|
|
104
|
+
id: asString(body.id),
|
|
105
|
+
label: asString(body.label),
|
|
106
|
+
explanation: asString(body.explanation),
|
|
107
|
+
value: asNumber(body.value),
|
|
108
|
+
confidence: asNumber(body.confidence),
|
|
109
|
+
weight: asNumber(body.weight),
|
|
110
|
+
metadata: asPayload(body.metadata),
|
|
111
|
+
raw: body
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function parseScore(payload) {
|
|
115
|
+
const body = asPayload(payload);
|
|
116
|
+
return {
|
|
117
|
+
category: asCategory(body.category),
|
|
118
|
+
value: asNumber(body.value),
|
|
119
|
+
observations: asArray(body.observations).map(parseObservation),
|
|
120
|
+
raw: body
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function parseScores(payload) {
|
|
124
|
+
return new Scores(asArray(payload).map(parseScore));
|
|
125
|
+
}
|
|
126
|
+
function parseIdentity(payload) {
|
|
127
|
+
const body = asPayload(payload);
|
|
128
|
+
const humanityScore = asNumber(body.humanityScore);
|
|
129
|
+
const authenticityScore = asNumber(body.authenticityScore);
|
|
130
|
+
const uniquenessScore = asNumber(body.uniquenessScore);
|
|
131
|
+
const behaviorScore = asNumber(body.behaviorScore);
|
|
132
|
+
const pairs = [
|
|
133
|
+
["HUMANITY", humanityScore],
|
|
134
|
+
["AUTHENTICITY", authenticityScore],
|
|
135
|
+
["UNIQUENESS", uniquenessScore],
|
|
136
|
+
["BEHAVIOR", behaviorScore]
|
|
137
|
+
];
|
|
138
|
+
const scores = new Scores(
|
|
139
|
+
pairs.filter(([, value]) => value !== null).map(([category, value]) => ({ category, value, observations: [], raw: {} }))
|
|
140
|
+
);
|
|
141
|
+
return {
|
|
142
|
+
id: asString(body.id),
|
|
143
|
+
displayName: asString(body.displayName),
|
|
144
|
+
displayEmail: asString(body.displayEmail),
|
|
145
|
+
displayUsername: asString(body.displayUsername),
|
|
146
|
+
humanityScore,
|
|
147
|
+
authenticityScore,
|
|
148
|
+
uniquenessScore,
|
|
149
|
+
behaviorScore,
|
|
150
|
+
createdAt: asDate(body.createdAt),
|
|
151
|
+
updatedAt: asDate(body.updatedAt),
|
|
152
|
+
lastTrackedAt: asDate(body.lastTrackedAt),
|
|
153
|
+
lastScoredAt: asDate(body.lastScoredAt),
|
|
154
|
+
disregarded: body.disregarded === true,
|
|
155
|
+
badges: asArray(body.badges).map(parseBadge),
|
|
156
|
+
data: asPayload(body.data),
|
|
157
|
+
scores,
|
|
158
|
+
raw: body
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function parseAnalysis(payload) {
|
|
162
|
+
const body = asPayload(payload);
|
|
163
|
+
const scores = parseScores(body.scores);
|
|
164
|
+
return {
|
|
165
|
+
id: asNumber(body.id),
|
|
166
|
+
identityId: asString(body.identityId),
|
|
167
|
+
scores,
|
|
168
|
+
observations: scores.flatMap((score) => [...score.observations]),
|
|
169
|
+
eventCount: asNumber(body.eventCount),
|
|
170
|
+
deviceCount: asNumber(body.deviceCount),
|
|
171
|
+
durationMillis: asNumber(body.durationMillis),
|
|
172
|
+
startedAt: asDate(body.startedAt),
|
|
173
|
+
finishedAt: asDate(body.finishedAt),
|
|
174
|
+
raw: body
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/resources.ts
|
|
179
|
+
function path(identityId, suffix = "") {
|
|
180
|
+
if (!identityId) {
|
|
181
|
+
throw new TypeError("An identity id is required.");
|
|
182
|
+
}
|
|
183
|
+
return `/identities/${encodeURIComponent(identityId)}${suffix}`;
|
|
184
|
+
}
|
|
185
|
+
var Identities = class {
|
|
186
|
+
#request;
|
|
187
|
+
/** @internal */
|
|
188
|
+
constructor(request) {
|
|
189
|
+
this.#request = request;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Returns the identity, with its current scores, badges, and attributes.
|
|
193
|
+
*
|
|
194
|
+
* @param identityId Your own id for the user, the one you pass to `track()`.
|
|
195
|
+
* @throws {NotFoundError} Dregs has never seen this identity.
|
|
196
|
+
*/
|
|
197
|
+
async get(identityId) {
|
|
198
|
+
return parseIdentity(await this.#request("GET", path(identityId)));
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Returns the current category scores.
|
|
202
|
+
*
|
|
203
|
+
* This is the cheap read and the one most integrations want. It reports the scores Dregs has
|
|
204
|
+
* already computed without triggering any work. For the observations behind them, use
|
|
205
|
+
* {@link Identities.analysis}.
|
|
206
|
+
*
|
|
207
|
+
* A category that has not been scored yet is absent, so a brand-new identity comes back empty.
|
|
208
|
+
*
|
|
209
|
+
* @throws {NotFoundError} Dregs has never seen this identity.
|
|
210
|
+
*/
|
|
211
|
+
async scores(identityId) {
|
|
212
|
+
return parseScores(await this.#request("GET", path(identityId, "/scores")));
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Returns the most recent analysis cycle, with the observations behind each score.
|
|
216
|
+
*
|
|
217
|
+
* Use this when you need to show or log *why* an identity scored the way it did.
|
|
218
|
+
*
|
|
219
|
+
* @throws {NotFoundError} The identity is unknown, or it has not been analyzed yet.
|
|
220
|
+
*/
|
|
221
|
+
async analysis(identityId) {
|
|
222
|
+
return parseAnalysis(await this.#request("GET", path(identityId, "/analysis")));
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Queues a re-analysis of the identity.
|
|
226
|
+
*
|
|
227
|
+
* Scoring is asynchronous: this resolves as soon as the job is queued, not when it has run.
|
|
228
|
+
* Poll {@link Identities.scores} or watch for a webhook rather than expecting fresh scores on
|
|
229
|
+
* the next line.
|
|
230
|
+
*
|
|
231
|
+
* @throws {NotFoundError} Dregs has never seen this identity.
|
|
232
|
+
*/
|
|
233
|
+
async analyze(identityId) {
|
|
234
|
+
await this.#request("POST", path(identityId, "/actions/analyze"));
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// src/client.ts
|
|
239
|
+
var DEFAULT_BASE_URL = "https://dregs.com/api";
|
|
240
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
241
|
+
var DEFAULT_MAX_RETRIES = 2;
|
|
242
|
+
var SECRET_KEY_ENV = "DREGS_SECRET_KEY";
|
|
243
|
+
var BASE_URL_ENV = "DREGS_BASE_URL";
|
|
244
|
+
var DEFAULT_SOURCE = "node-sdk";
|
|
245
|
+
var RETRY_STATUSES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
|
|
246
|
+
var STATUS_RATE_LIMITED = "rate_limited";
|
|
247
|
+
var STATUS_QUOTA_EXCEEDED = "quota_exceeded";
|
|
248
|
+
var MAX_BACKOFF_MS = 8e3;
|
|
249
|
+
var MAX_RETRY_AFTER_MS = 6e4;
|
|
250
|
+
var Dregs = class {
|
|
251
|
+
/** Read identities, their scores, and their analysis. */
|
|
252
|
+
identities;
|
|
253
|
+
#secretKey;
|
|
254
|
+
#baseUrl;
|
|
255
|
+
#timeout;
|
|
256
|
+
#maxRetries;
|
|
257
|
+
#fetch;
|
|
258
|
+
/**
|
|
259
|
+
* @param options Configuration. Every field has a default, so `new Dregs()` reads the
|
|
260
|
+
* environment and is usually enough.
|
|
261
|
+
* @throws {TypeError} No secret key was found, the key given is a `pk_` public key, or
|
|
262
|
+
* `maxRetries` is negative.
|
|
263
|
+
*/
|
|
264
|
+
constructor(options = {}) {
|
|
265
|
+
const secretKey = options.secretKey ?? readEnv(SECRET_KEY_ENV);
|
|
266
|
+
if (!secretKey) {
|
|
267
|
+
throw new TypeError(
|
|
268
|
+
`No Dregs secret key. Pass { secretKey: '...' } or set the ${SECRET_KEY_ENV} environment variable. You will find your credential's secret key under Settings -> Credentials in the Dregs dashboard.`
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
if (secretKey.startsWith("pk_")) {
|
|
272
|
+
throw new TypeError(
|
|
273
|
+
"That is a public key. The public key is for the browser tracker and cannot read identities or scores; this SDK needs the secret key from the same credential, which starts with 'sk_'."
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
277
|
+
if (maxRetries < 0 || !Number.isInteger(maxRetries)) {
|
|
278
|
+
throw new TypeError("maxRetries must be a non-negative integer.");
|
|
279
|
+
}
|
|
280
|
+
const timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
281
|
+
if (timeout <= 0) {
|
|
282
|
+
throw new TypeError("timeout must be greater than zero.");
|
|
283
|
+
}
|
|
284
|
+
const resolvedFetch = options.fetch ?? globalThis.fetch;
|
|
285
|
+
if (!resolvedFetch) {
|
|
286
|
+
throw new TypeError(
|
|
287
|
+
"No global fetch. This SDK needs Node 20 or newer, or a fetch implementation passed as the fetch option."
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
this.#secretKey = secretKey;
|
|
291
|
+
this.#baseUrl = (options.baseUrl ?? readEnv(BASE_URL_ENV) ?? DEFAULT_BASE_URL).replace(
|
|
292
|
+
/\/+$/,
|
|
293
|
+
""
|
|
294
|
+
);
|
|
295
|
+
this.#timeout = timeout;
|
|
296
|
+
this.#maxRetries = maxRetries;
|
|
297
|
+
this.#fetch = resolvedFetch;
|
|
298
|
+
const request = (method, path2, body) => this.request(method, path2, body);
|
|
299
|
+
this.identities = new Identities(request);
|
|
300
|
+
}
|
|
301
|
+
/** The API root every request is built against, with any trailing slash removed. */
|
|
302
|
+
get baseUrl() {
|
|
303
|
+
return this.#baseUrl;
|
|
304
|
+
}
|
|
305
|
+
/** How many times a failed request is retried before the error is thrown. */
|
|
306
|
+
get maxRetries() {
|
|
307
|
+
return this.#maxRetries;
|
|
308
|
+
}
|
|
309
|
+
/** Milliseconds before a request is abandoned. */
|
|
310
|
+
get timeout() {
|
|
311
|
+
return this.#timeout;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Records a backend event against an identity.
|
|
315
|
+
*
|
|
316
|
+
* ```ts
|
|
317
|
+
* await client.track('user.signup', {
|
|
318
|
+
* identity: 'user_12345',
|
|
319
|
+
* data: { plan: 'pro', referrer: 'partner-x' },
|
|
320
|
+
* identityData: { email: 'ada@example.com', name: 'Ada Lovelace' },
|
|
321
|
+
* });
|
|
322
|
+
* ```
|
|
323
|
+
*
|
|
324
|
+
* @param type Your name for the event, such as `"user.signup"`. Map it to one of Dregs's
|
|
325
|
+
* canonical types under **Settings → Mappings** so the analyzers know what it means.
|
|
326
|
+
* @param options The identity the event belongs to, and anything else worth sending.
|
|
327
|
+
* @returns The outcome. Check `.accepted` to confirm Dregs recorded the event.
|
|
328
|
+
* @throws {QuotaExceededError} The account is over its monthly event limit.
|
|
329
|
+
* @throws {RateLimitError} The credential is ingesting too fast.
|
|
330
|
+
* @throws {AuthenticationError} The secret key was not recognized.
|
|
331
|
+
* @throws {BadRequestError} The event was malformed.
|
|
332
|
+
* @throws {TypeError} The event type, identity, or event id was unusable. These are thrown
|
|
333
|
+
* before anything is sent.
|
|
334
|
+
*/
|
|
335
|
+
async track(type, options) {
|
|
336
|
+
return parseTrackResult(await this.request("POST", "/events", this.trackBody(type, options)));
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Builds the `POST /api/events` body.
|
|
340
|
+
*
|
|
341
|
+
* An event id is always sent. When the caller has an id of their own it is used verbatim, so
|
|
342
|
+
* reposting the same event is a no-op on the Dregs side; otherwise one is generated, which is
|
|
343
|
+
* what makes this client's own retries safe to perform.
|
|
344
|
+
*/
|
|
345
|
+
trackBody(type, options) {
|
|
346
|
+
if (!type) {
|
|
347
|
+
throw new TypeError("An event type is required.");
|
|
348
|
+
}
|
|
349
|
+
if (!options?.identity) {
|
|
350
|
+
throw new TypeError(
|
|
351
|
+
"An identity is required. A server-side event has no device signature, so the identity is the only thing tying the event to a user."
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
const eventId = options.eventId ?? randomEventId();
|
|
355
|
+
if (eventId.startsWith("dregs-")) {
|
|
356
|
+
throw new TypeError("Event ids starting with 'dregs-' are reserved for Dregs itself.");
|
|
357
|
+
}
|
|
358
|
+
if (eventId.length > 64) {
|
|
359
|
+
throw new TypeError("Event ids cannot be longer than 64 characters.");
|
|
360
|
+
}
|
|
361
|
+
const body = {
|
|
362
|
+
id: eventId,
|
|
363
|
+
type,
|
|
364
|
+
data: { ...options.data ?? {} },
|
|
365
|
+
identity: { id: options.identity, data: { ...options.identityData ?? {} } },
|
|
366
|
+
source: options.source ?? DEFAULT_SOURCE
|
|
367
|
+
};
|
|
368
|
+
if (options.timestamp !== void 0) {
|
|
369
|
+
body.timestamp = toIsoUtc(options.timestamp);
|
|
370
|
+
}
|
|
371
|
+
return body;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Sends one request, retrying what is worth retrying, and returns the parsed JSON body.
|
|
375
|
+
*
|
|
376
|
+
* The retry loop reuses the request body verbatim, which is what keeps a retried event
|
|
377
|
+
* idempotent: the generated id is built once, before the first attempt.
|
|
378
|
+
*/
|
|
379
|
+
async request(method, path2, body) {
|
|
380
|
+
const url = `${this.#baseUrl}/${path2.replace(/^\/+/, "")}`;
|
|
381
|
+
const init = this.requestInit(method, body);
|
|
382
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
383
|
+
let response;
|
|
384
|
+
let text;
|
|
385
|
+
try {
|
|
386
|
+
response = await this.#fetch(url, { ...init, signal: AbortSignal.timeout(this.#timeout) });
|
|
387
|
+
text = await response.text();
|
|
388
|
+
} catch (cause) {
|
|
389
|
+
if (!this.shouldRetry(attempt, null)) {
|
|
390
|
+
throw isTimeout(cause) ? new chunkK63AUEQR_cjs.DregsTimeoutError(
|
|
391
|
+
`The request to ${url} did not answer within ${this.#timeout}ms.`,
|
|
392
|
+
{ cause }
|
|
393
|
+
) : new chunkK63AUEQR_cjs.DregsConnectionError(`Could not reach Dregs at ${url}: ${describe(cause)}`, {
|
|
394
|
+
cause
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
await sleep(this.backoff(attempt, null));
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
try {
|
|
401
|
+
return processResponse(response, text);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
if (error instanceof chunkK63AUEQR_cjs.DregsAPIError && this.shouldRetry(attempt, error.statusCode)) {
|
|
404
|
+
await sleep(
|
|
405
|
+
this.backoff(attempt, error instanceof chunkK63AUEQR_cjs.RateLimitError ? error.retryAfter : null)
|
|
406
|
+
);
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
throw error;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
requestInit(method, body) {
|
|
414
|
+
const headers = {
|
|
415
|
+
Authorization: `Bearer ${this.#secretKey}`,
|
|
416
|
+
Accept: "application/json",
|
|
417
|
+
"User-Agent": userAgent()
|
|
418
|
+
};
|
|
419
|
+
if (body === void 0) {
|
|
420
|
+
return { method, headers };
|
|
421
|
+
}
|
|
422
|
+
headers["Content-Type"] = "application/json";
|
|
423
|
+
return { method, headers, body: JSON.stringify(body) };
|
|
424
|
+
}
|
|
425
|
+
shouldRetry(attempt, statusCode) {
|
|
426
|
+
if (attempt >= this.#maxRetries) {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
return statusCode === null || RETRY_STATUSES.has(statusCode);
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Milliseconds to wait before attempt `attempt + 1`.
|
|
433
|
+
*
|
|
434
|
+
* `Retry-After` wins when the server sent one. Otherwise this is exponential with full jitter,
|
|
435
|
+
* which keeps a fleet of workers that all hit the limit at once from retrying in lockstep.
|
|
436
|
+
*
|
|
437
|
+
* Protected rather than private so a test can stub the waiting out.
|
|
438
|
+
*/
|
|
439
|
+
backoff(attempt, retryAfterSeconds) {
|
|
440
|
+
if (retryAfterSeconds !== null && retryAfterSeconds >= 0) {
|
|
441
|
+
return Math.min(retryAfterSeconds * 1e3, MAX_RETRY_AFTER_MS);
|
|
442
|
+
}
|
|
443
|
+
return Math.random() * Math.min(500 * 2 ** attempt, MAX_BACKOFF_MS);
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
function processResponse(response, text) {
|
|
447
|
+
const payload = parseJson(text);
|
|
448
|
+
if (response.status >= 400) {
|
|
449
|
+
throw apiError(response, payload);
|
|
450
|
+
}
|
|
451
|
+
if (isRecord(payload)) {
|
|
452
|
+
if (payload.status === STATUS_RATE_LIMITED) {
|
|
453
|
+
throw new chunkK63AUEQR_cjs.RateLimitError("Ingestion rate limit exceeded for this credential.", {
|
|
454
|
+
body: payload,
|
|
455
|
+
requestId: requestId(response),
|
|
456
|
+
retryAfter: retryAfter(response)
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
if (payload.status === STATUS_QUOTA_EXCEEDED) {
|
|
460
|
+
throw new chunkK63AUEQR_cjs.QuotaExceededError("The account is over its monthly event limit.", {
|
|
461
|
+
statusCode: 402,
|
|
462
|
+
body: payload,
|
|
463
|
+
requestId: requestId(response)
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return payload;
|
|
468
|
+
}
|
|
469
|
+
function apiError(response, payload) {
|
|
470
|
+
const message = messageFrom(payload) ?? response.statusText ?? "Request failed";
|
|
471
|
+
const id = requestId(response);
|
|
472
|
+
if (response.status === 429) {
|
|
473
|
+
return new chunkK63AUEQR_cjs.RateLimitError(message, {
|
|
474
|
+
body: payload,
|
|
475
|
+
requestId: id,
|
|
476
|
+
retryAfter: retryAfter(response)
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
const ErrorClass = chunkK63AUEQR_cjs.errorForStatus(response.status);
|
|
480
|
+
return new ErrorClass(message, { statusCode: response.status, body: payload, requestId: id });
|
|
481
|
+
}
|
|
482
|
+
function messageFrom(payload) {
|
|
483
|
+
if (!isRecord(payload)) {
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
for (const key of ["message", "error", "status"]) {
|
|
487
|
+
const value = payload[key];
|
|
488
|
+
if (typeof value === "string" && value) {
|
|
489
|
+
return value;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
function requestId(response) {
|
|
495
|
+
return response.headers.get("X-Request-Id");
|
|
496
|
+
}
|
|
497
|
+
function retryAfter(response) {
|
|
498
|
+
const raw = response.headers.get("Retry-After");
|
|
499
|
+
if (raw === null) {
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
const seconds = Number(raw);
|
|
503
|
+
return Number.isFinite(seconds) ? seconds : null;
|
|
504
|
+
}
|
|
505
|
+
function parseJson(text) {
|
|
506
|
+
if (!text) {
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
try {
|
|
510
|
+
return JSON.parse(text);
|
|
511
|
+
} catch {
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
function isRecord(value) {
|
|
516
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
517
|
+
}
|
|
518
|
+
function isTimeout(cause) {
|
|
519
|
+
const names = [nameOf(cause), nameOf(cause?.cause)];
|
|
520
|
+
return names.includes("TimeoutError") || names.includes("AbortError");
|
|
521
|
+
}
|
|
522
|
+
function nameOf(value) {
|
|
523
|
+
if (typeof value !== "object" || value === null || !("name" in value)) {
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
const { name } = value;
|
|
527
|
+
return typeof name === "string" ? name : null;
|
|
528
|
+
}
|
|
529
|
+
function describe(cause) {
|
|
530
|
+
return cause instanceof Error ? cause.message : String(cause);
|
|
531
|
+
}
|
|
532
|
+
function sleep(ms) {
|
|
533
|
+
return ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
|
|
534
|
+
}
|
|
535
|
+
function readEnv(name) {
|
|
536
|
+
return typeof process === "undefined" ? void 0 : process.env[name];
|
|
537
|
+
}
|
|
538
|
+
function randomEventId() {
|
|
539
|
+
return globalThis.crypto.randomUUID().replace(/-/g, "");
|
|
540
|
+
}
|
|
541
|
+
function toIsoUtc(value) {
|
|
542
|
+
const moment = value instanceof Date ? value : new Date(value);
|
|
543
|
+
if (Number.isNaN(moment.getTime())) {
|
|
544
|
+
throw new TypeError(`The timestamp ${JSON.stringify(value)} is not a valid date.`);
|
|
545
|
+
}
|
|
546
|
+
return moment.toISOString().replace(/\.000Z$/, "Z");
|
|
547
|
+
}
|
|
548
|
+
function userAgent() {
|
|
549
|
+
const runtime = typeof process !== "undefined" && process.versions?.node ? `node ${process.versions.node}` : "unknown";
|
|
550
|
+
return `dregs-node/${VERSION} (${runtime})`;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
Object.defineProperty(exports, "AuthenticationError", {
|
|
554
|
+
enumerable: true,
|
|
555
|
+
get: function () { return chunkK63AUEQR_cjs.AuthenticationError; }
|
|
556
|
+
});
|
|
557
|
+
Object.defineProperty(exports, "BadRequestError", {
|
|
558
|
+
enumerable: true,
|
|
559
|
+
get: function () { return chunkK63AUEQR_cjs.BadRequestError; }
|
|
560
|
+
});
|
|
561
|
+
Object.defineProperty(exports, "DEFAULT_TOLERANCE_SECONDS", {
|
|
562
|
+
enumerable: true,
|
|
563
|
+
get: function () { return chunkK63AUEQR_cjs.DEFAULT_TOLERANCE_SECONDS; }
|
|
564
|
+
});
|
|
565
|
+
Object.defineProperty(exports, "DregsAPIError", {
|
|
566
|
+
enumerable: true,
|
|
567
|
+
get: function () { return chunkK63AUEQR_cjs.DregsAPIError; }
|
|
568
|
+
});
|
|
569
|
+
Object.defineProperty(exports, "DregsConnectionError", {
|
|
570
|
+
enumerable: true,
|
|
571
|
+
get: function () { return chunkK63AUEQR_cjs.DregsConnectionError; }
|
|
572
|
+
});
|
|
573
|
+
Object.defineProperty(exports, "DregsError", {
|
|
574
|
+
enumerable: true,
|
|
575
|
+
get: function () { return chunkK63AUEQR_cjs.DregsError; }
|
|
576
|
+
});
|
|
577
|
+
Object.defineProperty(exports, "DregsTimeoutError", {
|
|
578
|
+
enumerable: true,
|
|
579
|
+
get: function () { return chunkK63AUEQR_cjs.DregsTimeoutError; }
|
|
580
|
+
});
|
|
581
|
+
Object.defineProperty(exports, "EVENT_HEADER", {
|
|
582
|
+
enumerable: true,
|
|
583
|
+
get: function () { return chunkK63AUEQR_cjs.EVENT_HEADER; }
|
|
584
|
+
});
|
|
585
|
+
Object.defineProperty(exports, "NotFoundError", {
|
|
586
|
+
enumerable: true,
|
|
587
|
+
get: function () { return chunkK63AUEQR_cjs.NotFoundError; }
|
|
588
|
+
});
|
|
589
|
+
Object.defineProperty(exports, "PermissionDeniedError", {
|
|
590
|
+
enumerable: true,
|
|
591
|
+
get: function () { return chunkK63AUEQR_cjs.PermissionDeniedError; }
|
|
592
|
+
});
|
|
593
|
+
Object.defineProperty(exports, "QuotaExceededError", {
|
|
594
|
+
enumerable: true,
|
|
595
|
+
get: function () { return chunkK63AUEQR_cjs.QuotaExceededError; }
|
|
596
|
+
});
|
|
597
|
+
Object.defineProperty(exports, "RateLimitError", {
|
|
598
|
+
enumerable: true,
|
|
599
|
+
get: function () { return chunkK63AUEQR_cjs.RateLimitError; }
|
|
600
|
+
});
|
|
601
|
+
Object.defineProperty(exports, "SIGNATURE_HEADER", {
|
|
602
|
+
enumerable: true,
|
|
603
|
+
get: function () { return chunkK63AUEQR_cjs.SIGNATURE_HEADER; }
|
|
604
|
+
});
|
|
605
|
+
Object.defineProperty(exports, "ServerError", {
|
|
606
|
+
enumerable: true,
|
|
607
|
+
get: function () { return chunkK63AUEQR_cjs.ServerError; }
|
|
608
|
+
});
|
|
609
|
+
Object.defineProperty(exports, "TIMESTAMP_HEADER", {
|
|
610
|
+
enumerable: true,
|
|
611
|
+
get: function () { return chunkK63AUEQR_cjs.TIMESTAMP_HEADER; }
|
|
612
|
+
});
|
|
613
|
+
Object.defineProperty(exports, "WebhookVerificationError", {
|
|
614
|
+
enumerable: true,
|
|
615
|
+
get: function () { return chunkK63AUEQR_cjs.WebhookVerificationError; }
|
|
616
|
+
});
|
|
617
|
+
Object.defineProperty(exports, "computeWebhookSignature", {
|
|
618
|
+
enumerable: true,
|
|
619
|
+
get: function () { return chunkK63AUEQR_cjs.computeWebhookSignature; }
|
|
620
|
+
});
|
|
621
|
+
Object.defineProperty(exports, "errorForStatus", {
|
|
622
|
+
enumerable: true,
|
|
623
|
+
get: function () { return chunkK63AUEQR_cjs.errorForStatus; }
|
|
624
|
+
});
|
|
625
|
+
Object.defineProperty(exports, "verifyWebhook", {
|
|
626
|
+
enumerable: true,
|
|
627
|
+
get: function () { return chunkK63AUEQR_cjs.verifyWebhook; }
|
|
628
|
+
});
|
|
629
|
+
Object.defineProperty(exports, "verifyWebhookSignature", {
|
|
630
|
+
enumerable: true,
|
|
631
|
+
get: function () { return chunkK63AUEQR_cjs.verifyWebhookSignature; }
|
|
632
|
+
});
|
|
633
|
+
exports.BASE_URL_ENV = BASE_URL_ENV;
|
|
634
|
+
exports.CATEGORIES = CATEGORIES;
|
|
635
|
+
exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
|
|
636
|
+
exports.DEFAULT_MAX_RETRIES = DEFAULT_MAX_RETRIES;
|
|
637
|
+
exports.DEFAULT_SOURCE = DEFAULT_SOURCE;
|
|
638
|
+
exports.DEFAULT_TIMEOUT_MS = DEFAULT_TIMEOUT_MS;
|
|
639
|
+
exports.Dregs = Dregs;
|
|
640
|
+
exports.Identities = Identities;
|
|
641
|
+
exports.SECRET_KEY_ENV = SECRET_KEY_ENV;
|
|
642
|
+
exports.Scores = Scores;
|
|
643
|
+
exports.VERSION = VERSION;
|
|
644
|
+
//# sourceMappingURL=index.cjs.map
|
|
645
|
+
//# sourceMappingURL=index.cjs.map
|