@better-auth/infra 0.1.12 → 0.1.14
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/dist/client.d.mts +50 -48
- package/dist/client.mjs +67 -69
- package/dist/{constants-B-e0_Nsv.mjs → constants-DdWGfvz1.mjs} +1 -3
- package/dist/email.mjs +2 -4
- package/dist/identification-DF2nvmng.mjs +178 -0
- package/dist/index.d.mts +4611 -1558
- package/dist/index.mjs +2562 -3035
- package/package.json +13 -8
package/dist/client.d.mts
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
|
-
import * as _better_fetch_fetch0 from "@better-fetch/fetch";
|
|
1
|
+
import * as _$_better_fetch_fetch0 from "@better-fetch/fetch";
|
|
2
2
|
|
|
3
|
+
//#region src/sentinel/client.d.ts
|
|
4
|
+
interface SentinelClientOptions {
|
|
5
|
+
/**
|
|
6
|
+
* The URL of the identification service
|
|
7
|
+
* @default "https://kv.better-auth.com"
|
|
8
|
+
*/
|
|
9
|
+
identifyUrl?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Whether to automatically solve PoW challenges (default: true)
|
|
12
|
+
*/
|
|
13
|
+
autoSolveChallenge?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Callback when a PoW challenge is received
|
|
16
|
+
*/
|
|
17
|
+
onChallengeReceived?: (reason: string) => void;
|
|
18
|
+
/**
|
|
19
|
+
* Callback when a PoW challenge is solved
|
|
20
|
+
*/
|
|
21
|
+
onChallengeSolved?: (solveTimeMs: number) => void;
|
|
22
|
+
/**
|
|
23
|
+
* Callback when a PoW challenge fails to solve
|
|
24
|
+
*/
|
|
25
|
+
onChallengeFailed?: (error: Error) => void;
|
|
26
|
+
}
|
|
27
|
+
declare const sentinelClient: (options?: SentinelClientOptions) => {
|
|
28
|
+
id: "sentinel";
|
|
29
|
+
fetchPlugins: ({
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
hooks: {
|
|
33
|
+
onRequest<T extends Record<string, any>>(context: _$_better_fetch_fetch0.RequestContext<T>): Promise<_$_better_fetch_fetch0.RequestContext<T>>;
|
|
34
|
+
onResponse?: undefined;
|
|
35
|
+
};
|
|
36
|
+
} | {
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
hooks: {
|
|
40
|
+
onResponse(context: _$_better_fetch_fetch0.ResponseContext): Promise<_$_better_fetch_fetch0.ResponseContext>;
|
|
41
|
+
onRequest<T extends Record<string, any>>(context: _$_better_fetch_fetch0.RequestContext<T>): Promise<_$_better_fetch_fetch0.RequestContext<T>>;
|
|
42
|
+
};
|
|
43
|
+
})[];
|
|
44
|
+
};
|
|
45
|
+
//#endregion
|
|
3
46
|
//#region src/client.d.ts
|
|
4
47
|
interface DashAuditLog {
|
|
5
48
|
eventType: string;
|
|
@@ -10,10 +53,10 @@ interface DashAuditLog {
|
|
|
10
53
|
updatedAt: string;
|
|
11
54
|
ageInMinutes?: number;
|
|
12
55
|
location?: {
|
|
13
|
-
ipAddress?: string;
|
|
14
|
-
city?: string;
|
|
15
|
-
country?: string;
|
|
16
|
-
countryCode?: string;
|
|
56
|
+
ipAddress?: string | null;
|
|
57
|
+
city?: string | null;
|
|
58
|
+
country?: string | null;
|
|
59
|
+
countryCode?: string | null;
|
|
17
60
|
};
|
|
18
61
|
}
|
|
19
62
|
interface DashAuditLogsResponse {
|
|
@@ -49,7 +92,7 @@ interface DashClientOptions {
|
|
|
49
92
|
}
|
|
50
93
|
declare const dashClient: (options?: DashClientOptions) => {
|
|
51
94
|
id: "dash";
|
|
52
|
-
getActions: ($fetch: _better_fetch_fetch0.BetterFetch) => {
|
|
95
|
+
getActions: ($fetch: _$_better_fetch_fetch0.BetterFetch) => {
|
|
53
96
|
dash: {
|
|
54
97
|
getAuditLogs: (input?: DashGetAuditLogsInput) => Promise<{
|
|
55
98
|
data: DashAuditLogsResponse;
|
|
@@ -68,46 +111,5 @@ declare const dashClient: (options?: DashClientOptions) => {
|
|
|
68
111
|
"/events/audit-logs": "GET";
|
|
69
112
|
};
|
|
70
113
|
};
|
|
71
|
-
interface SentinelClientOptions {
|
|
72
|
-
/**
|
|
73
|
-
* The URL of the identification service
|
|
74
|
-
* @default "https://kv.better-auth.com"
|
|
75
|
-
*/
|
|
76
|
-
identifyUrl?: string;
|
|
77
|
-
/**
|
|
78
|
-
* Whether to automatically solve PoW challenges (default: true)
|
|
79
|
-
*/
|
|
80
|
-
autoSolveChallenge?: boolean;
|
|
81
|
-
/**
|
|
82
|
-
* Callback when a PoW challenge is received
|
|
83
|
-
*/
|
|
84
|
-
onChallengeReceived?: (reason: string) => void;
|
|
85
|
-
/**
|
|
86
|
-
* Callback when a PoW challenge is solved
|
|
87
|
-
*/
|
|
88
|
-
onChallengeSolved?: (solveTimeMs: number) => void;
|
|
89
|
-
/**
|
|
90
|
-
* Callback when a PoW challenge fails to solve
|
|
91
|
-
*/
|
|
92
|
-
onChallengeFailed?: (error: Error) => void;
|
|
93
|
-
}
|
|
94
|
-
declare const sentinelClient: (options?: SentinelClientOptions) => {
|
|
95
|
-
id: "sentinel";
|
|
96
|
-
fetchPlugins: ({
|
|
97
|
-
id: string;
|
|
98
|
-
name: string;
|
|
99
|
-
hooks: {
|
|
100
|
-
onRequest<T extends Record<string, any>>(context: _better_fetch_fetch0.RequestContext<T>): Promise<_better_fetch_fetch0.RequestContext<T>>;
|
|
101
|
-
onResponse?: undefined;
|
|
102
|
-
};
|
|
103
|
-
} | {
|
|
104
|
-
id: string;
|
|
105
|
-
name: string;
|
|
106
|
-
hooks: {
|
|
107
|
-
onResponse(context: _better_fetch_fetch0.ResponseContext): Promise<_better_fetch_fetch0.ResponseContext>;
|
|
108
|
-
onRequest<T extends Record<string, any>>(context: _better_fetch_fetch0.RequestContext<T>): Promise<_better_fetch_fetch0.RequestContext<T>>;
|
|
109
|
-
};
|
|
110
|
-
})[];
|
|
111
|
-
};
|
|
112
114
|
//#endregion
|
|
113
|
-
export { DashAuditLog, DashAuditLogsResponse, DashClientOptions, DashGetAuditLogsInput, SentinelClientOptions, dashClient, sentinelClient };
|
|
115
|
+
export { DashAuditLog, DashAuditLogsResponse, DashClientOptions, DashGetAuditLogsInput, type SentinelClientOptions, dashClient, sentinelClient };
|
package/dist/client.mjs
CHANGED
|
@@ -1,19 +1,7 @@
|
|
|
1
|
-
import { r as KV_TIMEOUT_MS } from "./constants-
|
|
1
|
+
import { r as KV_TIMEOUT_MS } from "./constants-DdWGfvz1.mjs";
|
|
2
|
+
import { a as hash, i as identify, r as generateRequestId } from "./identification-DF2nvmng.mjs";
|
|
2
3
|
import { env } from "@better-auth/core/env";
|
|
3
|
-
|
|
4
|
-
//#region src/client.ts
|
|
5
|
-
const DEFAULT_IDENTIFY_URL = "https://kv.better-auth.com";
|
|
6
|
-
async function sha256(message) {
|
|
7
|
-
const msgBuffer = new TextEncoder().encode(message);
|
|
8
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer);
|
|
9
|
-
return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
10
|
-
}
|
|
11
|
-
function generateRequestId() {
|
|
12
|
-
const array = new Uint8Array(16);
|
|
13
|
-
crypto.getRandomValues(array);
|
|
14
|
-
const hex = Array.from(array).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
15
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
16
|
-
}
|
|
4
|
+
//#region src/sentinel/fingerprint.ts
|
|
17
5
|
function murmurhash3(str, seed = 0) {
|
|
18
6
|
let h1 = seed;
|
|
19
7
|
const c1 = 3432918353;
|
|
@@ -337,7 +325,7 @@ async function generateVisitorId(components) {
|
|
|
337
325
|
fonts: components.fonts,
|
|
338
326
|
maxTouchPoints: components.maxTouchPoints
|
|
339
327
|
};
|
|
340
|
-
return (await
|
|
328
|
+
return (await hash(JSON.stringify(stableData))).slice(0, 20);
|
|
341
329
|
}
|
|
342
330
|
function calculateConfidence(components) {
|
|
343
331
|
const weights = {
|
|
@@ -370,6 +358,8 @@ function calculateConfidence(components) {
|
|
|
370
358
|
let cachedFingerprint = null;
|
|
371
359
|
let fingerprintPromise = null;
|
|
372
360
|
let identifySent = false;
|
|
361
|
+
let identifyCompletePromise = null;
|
|
362
|
+
let identifyCompleteResolve = null;
|
|
373
363
|
async function getFingerprint() {
|
|
374
364
|
if (typeof window === "undefined") return null;
|
|
375
365
|
if (await cachedFingerprint) return cachedFingerprint;
|
|
@@ -394,8 +384,6 @@ async function getFingerprint() {
|
|
|
394
384
|
return null;
|
|
395
385
|
}
|
|
396
386
|
}
|
|
397
|
-
let identifyCompletePromise = null;
|
|
398
|
-
let identifyCompleteResolve = null;
|
|
399
387
|
async function sendIdentify(identifyUrl) {
|
|
400
388
|
if (identifySent || typeof window === "undefined") return;
|
|
401
389
|
const fingerprint = await getFingerprint();
|
|
@@ -413,12 +401,7 @@ async function sendIdentify(identifyUrl) {
|
|
|
413
401
|
incognito: detectIncognito()
|
|
414
402
|
};
|
|
415
403
|
try {
|
|
416
|
-
await
|
|
417
|
-
method: "POST",
|
|
418
|
-
headers: { "Content-Type": "application/json" },
|
|
419
|
-
body: JSON.stringify(payload),
|
|
420
|
-
signal: AbortSignal.timeout(KV_TIMEOUT_MS)
|
|
421
|
-
});
|
|
404
|
+
await identify(identifyUrl, payload, AbortSignal.timeout(KV_TIMEOUT_MS));
|
|
422
405
|
} catch (error) {
|
|
423
406
|
console.warn("[Dash] Identify request failed:", error);
|
|
424
407
|
} finally {
|
|
@@ -433,9 +416,8 @@ async function waitForIdentify(timeoutMs = 500) {
|
|
|
433
416
|
if (!identifyCompletePromise) return;
|
|
434
417
|
await Promise.race([identifyCompletePromise, new Promise((resolve) => setTimeout(resolve, timeoutMs))]);
|
|
435
418
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
*/
|
|
419
|
+
//#endregion
|
|
420
|
+
//#region src/sentinel/pow.ts
|
|
439
421
|
function hasLeadingZeroBits(hash, bits) {
|
|
440
422
|
const fullHexChars = Math.floor(bits / 4);
|
|
441
423
|
const remainingBits = bits % 4;
|
|
@@ -445,15 +427,11 @@ function hasLeadingZeroBits(hash, bits) {
|
|
|
445
427
|
}
|
|
446
428
|
return true;
|
|
447
429
|
}
|
|
448
|
-
/**
|
|
449
|
-
* Solve a PoW challenge
|
|
450
|
-
* @returns solution or null if challenge couldn't be solved
|
|
451
|
-
*/
|
|
452
430
|
async function solvePoWChallenge(challenge) {
|
|
453
431
|
const { nonce, difficulty } = challenge;
|
|
454
432
|
let counter = 0;
|
|
455
433
|
while (true) {
|
|
456
|
-
if (hasLeadingZeroBits(await
|
|
434
|
+
if (hasLeadingZeroBits(await hash(`${nonce}:${counter}`), difficulty)) return {
|
|
457
435
|
nonce,
|
|
458
436
|
counter
|
|
459
437
|
};
|
|
@@ -462,50 +440,42 @@ async function solvePoWChallenge(challenge) {
|
|
|
462
440
|
if (counter > 1e8) throw new Error("PoW challenge took too long to solve");
|
|
463
441
|
}
|
|
464
442
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
443
|
+
function decodeBase64ToUtf8(encoded) {
|
|
444
|
+
if (typeof globalThis.atob === "function") return globalThis.atob(encoded);
|
|
445
|
+
throw new Error("[Sentinel] Base64 decode requires atob (browser, Hermes, or Bun)");
|
|
446
|
+
}
|
|
447
|
+
function encodeUtf8ToBase64(str) {
|
|
448
|
+
if (typeof globalThis.btoa === "function") return globalThis.btoa(str);
|
|
449
|
+
const bytes = new TextEncoder().encode(str);
|
|
450
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
451
|
+
let out = "";
|
|
452
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
453
|
+
const b0 = bytes[i];
|
|
454
|
+
const b1 = bytes[i + 1] ?? 0;
|
|
455
|
+
const b2 = bytes[i + 2] ?? 0;
|
|
456
|
+
const triple = b0 << 16 | b1 << 8 | b2;
|
|
457
|
+
const pad = i + 2 >= bytes.length ? i + 1 >= bytes.length ? 2 : 1 : 0;
|
|
458
|
+
out += chars[triple >> 18 & 63];
|
|
459
|
+
out += chars[triple >> 12 & 63];
|
|
460
|
+
out += pad < 2 ? chars[triple >> 6 & 63] : "=";
|
|
461
|
+
out += pad < 1 ? chars[triple & 63] : "=";
|
|
462
|
+
}
|
|
463
|
+
return out;
|
|
464
|
+
}
|
|
468
465
|
function decodePoWChallenge(encoded) {
|
|
469
466
|
try {
|
|
470
|
-
const decoded =
|
|
467
|
+
const decoded = decodeBase64ToUtf8(encoded);
|
|
471
468
|
return JSON.parse(decoded);
|
|
472
469
|
} catch {
|
|
473
470
|
return null;
|
|
474
471
|
}
|
|
475
472
|
}
|
|
476
|
-
/**
|
|
477
|
-
* Encode a solution to base64
|
|
478
|
-
*/
|
|
479
473
|
function encodePoWSolution(solution) {
|
|
480
|
-
return
|
|
481
|
-
}
|
|
482
|
-
function resolveDashUserId(input, options) {
|
|
483
|
-
return input.userId || options?.resolveUserId?.({
|
|
484
|
-
userId: input.userId,
|
|
485
|
-
user: input.user,
|
|
486
|
-
session: input.session
|
|
487
|
-
}) || input.user?.id || input.session?.user?.id || void 0;
|
|
474
|
+
return encodeUtf8ToBase64(JSON.stringify(solution));
|
|
488
475
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
getActions: ($fetch) => ({ dash: { getAuditLogs: async (input = {}) => {
|
|
493
|
-
const userId = resolveDashUserId(input, options);
|
|
494
|
-
return $fetch("/events/audit-logs", {
|
|
495
|
-
method: "GET",
|
|
496
|
-
query: {
|
|
497
|
-
limit: input.limit,
|
|
498
|
-
offset: input.offset,
|
|
499
|
-
organizationId: input.organizationId,
|
|
500
|
-
identifier: input.identifier,
|
|
501
|
-
eventType: input.eventType,
|
|
502
|
-
userId
|
|
503
|
-
}
|
|
504
|
-
});
|
|
505
|
-
} } }),
|
|
506
|
-
pathMethods: { "/events/audit-logs": "GET" }
|
|
507
|
-
};
|
|
508
|
-
};
|
|
476
|
+
//#endregion
|
|
477
|
+
//#region src/sentinel/client.ts
|
|
478
|
+
const DEFAULT_IDENTIFY_URL = "https://kv.better-auth.com";
|
|
509
479
|
const sentinelClient = (options) => {
|
|
510
480
|
const autoSolve = options?.autoSolveChallenge !== false;
|
|
511
481
|
const identifyUrl = options?.identifyUrl ?? env.BETTER_AUTH_KV_URL ?? DEFAULT_IDENTIFY_URL;
|
|
@@ -594,6 +564,34 @@ const sentinelClient = (options) => {
|
|
|
594
564
|
}]
|
|
595
565
|
};
|
|
596
566
|
};
|
|
597
|
-
|
|
598
567
|
//#endregion
|
|
599
|
-
|
|
568
|
+
//#region src/client.ts
|
|
569
|
+
function resolveDashUserId(input, options) {
|
|
570
|
+
return input.userId || options?.resolveUserId?.({
|
|
571
|
+
userId: input.userId,
|
|
572
|
+
user: input.user,
|
|
573
|
+
session: input.session
|
|
574
|
+
}) || input.user?.id || input.session?.user?.id || void 0;
|
|
575
|
+
}
|
|
576
|
+
const dashClient = (options) => {
|
|
577
|
+
return {
|
|
578
|
+
id: "dash",
|
|
579
|
+
getActions: ($fetch) => ({ dash: { getAuditLogs: async (input = {}) => {
|
|
580
|
+
const userId = resolveDashUserId(input, options);
|
|
581
|
+
return $fetch("/events/audit-logs", {
|
|
582
|
+
method: "GET",
|
|
583
|
+
query: {
|
|
584
|
+
limit: input.limit,
|
|
585
|
+
offset: input.offset,
|
|
586
|
+
organizationId: input.organizationId,
|
|
587
|
+
identifier: input.identifier,
|
|
588
|
+
eventType: input.eventType,
|
|
589
|
+
userId
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
} } }),
|
|
593
|
+
pathMethods: { "/events/audit-logs": "GET" }
|
|
594
|
+
};
|
|
595
|
+
};
|
|
596
|
+
//#endregion
|
|
597
|
+
export { dashClient, sentinelClient };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { env } from "@better-auth/core/env";
|
|
2
|
-
|
|
3
2
|
//#region src/constants.ts
|
|
4
3
|
/**
|
|
5
4
|
* Infrastructure API URL
|
|
@@ -13,6 +12,5 @@ const INFRA_API_URL = env.BETTER_AUTH_API_URL || "https://dash.better-auth.com";
|
|
|
13
12
|
const INFRA_KV_URL = env.BETTER_AUTH_KV_URL || "https://kv.better-auth.com";
|
|
14
13
|
/** Timeout for KV HTTP operations (ms) */
|
|
15
14
|
const KV_TIMEOUT_MS = 5e3;
|
|
16
|
-
|
|
17
15
|
//#endregion
|
|
18
|
-
export { INFRA_KV_URL as n, KV_TIMEOUT_MS as r, INFRA_API_URL as t };
|
|
16
|
+
export { INFRA_KV_URL as n, KV_TIMEOUT_MS as r, INFRA_API_URL as t };
|
package/dist/email.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { t as INFRA_API_URL } from "./constants-
|
|
1
|
+
import { t as INFRA_API_URL } from "./constants-DdWGfvz1.mjs";
|
|
2
2
|
import { logger } from "better-auth";
|
|
3
3
|
import { env } from "@better-auth/core/env";
|
|
4
|
-
|
|
5
4
|
//#region src/email.ts
|
|
6
5
|
/**
|
|
7
6
|
* Email sending module for @better-auth/infra
|
|
@@ -178,6 +177,5 @@ async function sendEmail(options, config) {
|
|
|
178
177
|
async function sendBulkEmails(options, config) {
|
|
179
178
|
return createEmailSender(config).sendBulk(options);
|
|
180
179
|
}
|
|
181
|
-
|
|
182
180
|
//#endregion
|
|
183
|
-
export { EMAIL_TEMPLATES, createEmailSender, sendBulkEmails, sendEmail };
|
|
181
|
+
export { EMAIL_TEMPLATES, createEmailSender, sendBulkEmails, sendEmail };
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { n as INFRA_KV_URL, r as KV_TIMEOUT_MS } from "./constants-DdWGfvz1.mjs";
|
|
2
|
+
import { logger } from "better-auth";
|
|
3
|
+
import { createAuthMiddleware } from "better-auth/api";
|
|
4
|
+
//#region src/crypto.ts
|
|
5
|
+
function randomBytes(length) {
|
|
6
|
+
const bytes = new Uint8Array(length);
|
|
7
|
+
crypto.getRandomValues(bytes);
|
|
8
|
+
return bytes;
|
|
9
|
+
}
|
|
10
|
+
function bytesToHex(bytes) {
|
|
11
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
12
|
+
}
|
|
13
|
+
async function hash(message) {
|
|
14
|
+
const msgBuffer = new TextEncoder().encode(message);
|
|
15
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer);
|
|
16
|
+
return bytesToHex(new Uint8Array(hashBuffer));
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/identification.ts
|
|
20
|
+
/**
|
|
21
|
+
* Identification Service
|
|
22
|
+
*
|
|
23
|
+
* Fetches identification data from the durable-kv service
|
|
24
|
+
* when a request includes an X-Request-Id header.
|
|
25
|
+
*/
|
|
26
|
+
const IDENTIFICATION_COOKIE_NAME = "__infra-rid";
|
|
27
|
+
const identificationCache = /* @__PURE__ */ new Map();
|
|
28
|
+
const CACHE_TTL_MS = 6e4;
|
|
29
|
+
const CACHE_MAX_SIZE = 1e3;
|
|
30
|
+
let lastCleanup = Date.now();
|
|
31
|
+
function cleanupCache() {
|
|
32
|
+
const now = Date.now();
|
|
33
|
+
for (const [key, value] of identificationCache.entries()) if (now - value.timestamp > CACHE_TTL_MS) identificationCache.delete(key);
|
|
34
|
+
lastCleanup = now;
|
|
35
|
+
}
|
|
36
|
+
function maybeCleanup() {
|
|
37
|
+
if (Date.now() - lastCleanup > CACHE_TTL_MS || identificationCache.size > CACHE_MAX_SIZE) cleanupCache();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Fetch identification data from durable-kv by requestId
|
|
41
|
+
*/
|
|
42
|
+
async function getIdentification(requestId, apiKey, kvUrl) {
|
|
43
|
+
maybeCleanup();
|
|
44
|
+
const cached = identificationCache.get(requestId);
|
|
45
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) return cached.data;
|
|
46
|
+
const baseUrl = kvUrl || INFRA_KV_URL;
|
|
47
|
+
const maxRetries = 3;
|
|
48
|
+
const retryDelays = [
|
|
49
|
+
50,
|
|
50
|
+
100,
|
|
51
|
+
200
|
|
52
|
+
];
|
|
53
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) try {
|
|
54
|
+
const response = await fetch(`${baseUrl}/identify/${requestId}`, {
|
|
55
|
+
method: "GET",
|
|
56
|
+
headers: { "x-api-key": apiKey },
|
|
57
|
+
signal: AbortSignal.timeout(KV_TIMEOUT_MS)
|
|
58
|
+
});
|
|
59
|
+
if (response.ok) {
|
|
60
|
+
const data = await response.json();
|
|
61
|
+
identificationCache.set(requestId, {
|
|
62
|
+
data,
|
|
63
|
+
timestamp: Date.now()
|
|
64
|
+
});
|
|
65
|
+
return data;
|
|
66
|
+
}
|
|
67
|
+
if (response.status === 404 && attempt < maxRetries) {
|
|
68
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelays[attempt]));
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (response.status !== 404) identificationCache.set(requestId, {
|
|
72
|
+
data: null,
|
|
73
|
+
timestamp: Date.now()
|
|
74
|
+
});
|
|
75
|
+
return null;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (attempt === maxRetries) {
|
|
78
|
+
logger.error("[Dash] Failed to fetch identification:", error);
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelays[attempt] || 50));
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
function generateRequestId() {
|
|
86
|
+
const hex = bytesToHex(randomBytes(16));
|
|
87
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
88
|
+
}
|
|
89
|
+
async function identify(baseURL, payload, signal) {
|
|
90
|
+
const base = baseURL.replace(/\/$/, "");
|
|
91
|
+
await fetch(`${base}/identify`, {
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: { "Content-Type": "application/json" },
|
|
94
|
+
body: JSON.stringify(payload),
|
|
95
|
+
signal: signal ?? AbortSignal.timeout(5e3)
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Extract identification headers from a request
|
|
100
|
+
*/
|
|
101
|
+
function extractIdentificationHeaders(request) {
|
|
102
|
+
if (!request) return {
|
|
103
|
+
visitorId: null,
|
|
104
|
+
requestId: null
|
|
105
|
+
};
|
|
106
|
+
return {
|
|
107
|
+
visitorId: request.headers.get("X-Visitor-Id"),
|
|
108
|
+
requestId: request.headers.get("X-Request-Id")
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Early middleware that loads identification data
|
|
113
|
+
*/
|
|
114
|
+
function createIdentificationMiddleware(options) {
|
|
115
|
+
return createAuthMiddleware(async (ctx) => {
|
|
116
|
+
const { visitorId, requestId: headerRequestId } = extractIdentificationHeaders(ctx.request);
|
|
117
|
+
const requestId = headerRequestId ?? ctx.getCookie("__infra-rid") ?? null;
|
|
118
|
+
ctx.context.visitorId = visitorId;
|
|
119
|
+
ctx.context.requestId = requestId;
|
|
120
|
+
if (requestId) ctx.context.identification = ctx.context.identification ?? await getIdentification(requestId, options.apiKey, options.kvUrl) ?? null;
|
|
121
|
+
else ctx.context.identification = null;
|
|
122
|
+
const ipConfig = ctx.context.options?.advanced?.ipAddress;
|
|
123
|
+
if (ipConfig?.disableIpTracking === true) {
|
|
124
|
+
ctx.context.location = void 0;
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const identification = ctx.context.identification;
|
|
128
|
+
if (requestId && identification) {
|
|
129
|
+
const loc = getLocation(identification);
|
|
130
|
+
ctx.context.location = {
|
|
131
|
+
ipAddress: identification.ip || void 0,
|
|
132
|
+
city: loc?.city || void 0,
|
|
133
|
+
country: loc?.country?.name || void 0,
|
|
134
|
+
countryCode: loc?.country?.code || void 0
|
|
135
|
+
};
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const ipAddress = getClientIpFromRequest(ctx.request, ipConfig?.ipAddressHeaders || null);
|
|
139
|
+
const countryCode = getCountryCodeFromRequest(ctx.request);
|
|
140
|
+
if (ipAddress || countryCode) {
|
|
141
|
+
ctx.context.location = {
|
|
142
|
+
ipAddress,
|
|
143
|
+
countryCode
|
|
144
|
+
};
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
ctx.context.location = void 0;
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get the visitor's location
|
|
152
|
+
*/
|
|
153
|
+
function getLocation(identification) {
|
|
154
|
+
if (!identification) return null;
|
|
155
|
+
return identification.location;
|
|
156
|
+
}
|
|
157
|
+
function getClientIpFromRequest(request, ipAddressHeaders) {
|
|
158
|
+
if (!request) return void 0;
|
|
159
|
+
const headers = ipAddressHeaders?.length ? ipAddressHeaders : [
|
|
160
|
+
"cf-connecting-ip",
|
|
161
|
+
"x-forwarded-for",
|
|
162
|
+
"x-real-ip",
|
|
163
|
+
"x-vercel-forwarded-for"
|
|
164
|
+
];
|
|
165
|
+
for (const headerName of headers) {
|
|
166
|
+
const value = request.headers.get(headerName);
|
|
167
|
+
if (!value) continue;
|
|
168
|
+
const ip = value.split(",")[0]?.trim();
|
|
169
|
+
if (ip) return ip;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function getCountryCodeFromRequest(request) {
|
|
173
|
+
if (!request) return void 0;
|
|
174
|
+
const cc = request.headers.get("cf-ipcountry") ?? request.headers.get("x-vercel-ip-country");
|
|
175
|
+
return cc ? cc.toUpperCase() : void 0;
|
|
176
|
+
}
|
|
177
|
+
//#endregion
|
|
178
|
+
export { hash as a, identify as i, createIdentificationMiddleware as n, generateRequestId as r, IDENTIFICATION_COOKIE_NAME as t };
|