@better-auth/infra 0.1.13 → 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 +49 -47
- package/dist/client.mjs +65 -65
- package/dist/identification-DF2nvmng.mjs +178 -0
- package/dist/index.d.mts +908 -1108
- package/dist/index.mjs +738 -841
- package/package.json +8 -6
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;
|
|
@@ -49,18 +92,18 @@ 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<{
|
|
98
|
+
data: DashAuditLogsResponse;
|
|
99
|
+
error: null;
|
|
100
|
+
} | {
|
|
55
101
|
data: null;
|
|
56
102
|
error: {
|
|
57
103
|
message?: string | undefined;
|
|
58
104
|
status: number;
|
|
59
105
|
statusText: string;
|
|
60
106
|
};
|
|
61
|
-
} | {
|
|
62
|
-
data: DashAuditLogsResponse;
|
|
63
|
-
error: null;
|
|
64
107
|
}>;
|
|
65
108
|
};
|
|
66
109
|
};
|
|
@@ -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,18 +1,7 @@
|
|
|
1
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
|
-
//#region src/
|
|
4
|
-
const DEFAULT_IDENTIFY_URL = "https://kv.better-auth.com";
|
|
5
|
-
async function sha256(message) {
|
|
6
|
-
const msgBuffer = new TextEncoder().encode(message);
|
|
7
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer);
|
|
8
|
-
return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
9
|
-
}
|
|
10
|
-
function generateRequestId() {
|
|
11
|
-
const array = new Uint8Array(16);
|
|
12
|
-
crypto.getRandomValues(array);
|
|
13
|
-
const hex = Array.from(array).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
14
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
15
|
-
}
|
|
4
|
+
//#region src/sentinel/fingerprint.ts
|
|
16
5
|
function murmurhash3(str, seed = 0) {
|
|
17
6
|
let h1 = seed;
|
|
18
7
|
const c1 = 3432918353;
|
|
@@ -336,7 +325,7 @@ async function generateVisitorId(components) {
|
|
|
336
325
|
fonts: components.fonts,
|
|
337
326
|
maxTouchPoints: components.maxTouchPoints
|
|
338
327
|
};
|
|
339
|
-
return (await
|
|
328
|
+
return (await hash(JSON.stringify(stableData))).slice(0, 20);
|
|
340
329
|
}
|
|
341
330
|
function calculateConfidence(components) {
|
|
342
331
|
const weights = {
|
|
@@ -369,6 +358,8 @@ function calculateConfidence(components) {
|
|
|
369
358
|
let cachedFingerprint = null;
|
|
370
359
|
let fingerprintPromise = null;
|
|
371
360
|
let identifySent = false;
|
|
361
|
+
let identifyCompletePromise = null;
|
|
362
|
+
let identifyCompleteResolve = null;
|
|
372
363
|
async function getFingerprint() {
|
|
373
364
|
if (typeof window === "undefined") return null;
|
|
374
365
|
if (await cachedFingerprint) return cachedFingerprint;
|
|
@@ -393,8 +384,6 @@ async function getFingerprint() {
|
|
|
393
384
|
return null;
|
|
394
385
|
}
|
|
395
386
|
}
|
|
396
|
-
let identifyCompletePromise = null;
|
|
397
|
-
let identifyCompleteResolve = null;
|
|
398
387
|
async function sendIdentify(identifyUrl) {
|
|
399
388
|
if (identifySent || typeof window === "undefined") return;
|
|
400
389
|
const fingerprint = await getFingerprint();
|
|
@@ -412,12 +401,7 @@ async function sendIdentify(identifyUrl) {
|
|
|
412
401
|
incognito: detectIncognito()
|
|
413
402
|
};
|
|
414
403
|
try {
|
|
415
|
-
await
|
|
416
|
-
method: "POST",
|
|
417
|
-
headers: { "Content-Type": "application/json" },
|
|
418
|
-
body: JSON.stringify(payload),
|
|
419
|
-
signal: AbortSignal.timeout(KV_TIMEOUT_MS)
|
|
420
|
-
});
|
|
404
|
+
await identify(identifyUrl, payload, AbortSignal.timeout(KV_TIMEOUT_MS));
|
|
421
405
|
} catch (error) {
|
|
422
406
|
console.warn("[Dash] Identify request failed:", error);
|
|
423
407
|
} finally {
|
|
@@ -432,9 +416,8 @@ async function waitForIdentify(timeoutMs = 500) {
|
|
|
432
416
|
if (!identifyCompletePromise) return;
|
|
433
417
|
await Promise.race([identifyCompletePromise, new Promise((resolve) => setTimeout(resolve, timeoutMs))]);
|
|
434
418
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
*/
|
|
419
|
+
//#endregion
|
|
420
|
+
//#region src/sentinel/pow.ts
|
|
438
421
|
function hasLeadingZeroBits(hash, bits) {
|
|
439
422
|
const fullHexChars = Math.floor(bits / 4);
|
|
440
423
|
const remainingBits = bits % 4;
|
|
@@ -444,15 +427,11 @@ function hasLeadingZeroBits(hash, bits) {
|
|
|
444
427
|
}
|
|
445
428
|
return true;
|
|
446
429
|
}
|
|
447
|
-
/**
|
|
448
|
-
* Solve a PoW challenge
|
|
449
|
-
* @returns solution or null if challenge couldn't be solved
|
|
450
|
-
*/
|
|
451
430
|
async function solvePoWChallenge(challenge) {
|
|
452
431
|
const { nonce, difficulty } = challenge;
|
|
453
432
|
let counter = 0;
|
|
454
433
|
while (true) {
|
|
455
|
-
if (hasLeadingZeroBits(await
|
|
434
|
+
if (hasLeadingZeroBits(await hash(`${nonce}:${counter}`), difficulty)) return {
|
|
456
435
|
nonce,
|
|
457
436
|
counter
|
|
458
437
|
};
|
|
@@ -461,50 +440,42 @@ async function solvePoWChallenge(challenge) {
|
|
|
461
440
|
if (counter > 1e8) throw new Error("PoW challenge took too long to solve");
|
|
462
441
|
}
|
|
463
442
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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
|
+
}
|
|
467
465
|
function decodePoWChallenge(encoded) {
|
|
468
466
|
try {
|
|
469
|
-
const decoded =
|
|
467
|
+
const decoded = decodeBase64ToUtf8(encoded);
|
|
470
468
|
return JSON.parse(decoded);
|
|
471
469
|
} catch {
|
|
472
470
|
return null;
|
|
473
471
|
}
|
|
474
472
|
}
|
|
475
|
-
/**
|
|
476
|
-
* Encode a solution to base64
|
|
477
|
-
*/
|
|
478
473
|
function encodePoWSolution(solution) {
|
|
479
|
-
return
|
|
480
|
-
}
|
|
481
|
-
function resolveDashUserId(input, options) {
|
|
482
|
-
return input.userId || options?.resolveUserId?.({
|
|
483
|
-
userId: input.userId,
|
|
484
|
-
user: input.user,
|
|
485
|
-
session: input.session
|
|
486
|
-
}) || input.user?.id || input.session?.user?.id || void 0;
|
|
474
|
+
return encodeUtf8ToBase64(JSON.stringify(solution));
|
|
487
475
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
getActions: ($fetch) => ({ dash: { getAuditLogs: async (input = {}) => {
|
|
492
|
-
const userId = resolveDashUserId(input, options);
|
|
493
|
-
return $fetch("/events/audit-logs", {
|
|
494
|
-
method: "GET",
|
|
495
|
-
query: {
|
|
496
|
-
limit: input.limit,
|
|
497
|
-
offset: input.offset,
|
|
498
|
-
organizationId: input.organizationId,
|
|
499
|
-
identifier: input.identifier,
|
|
500
|
-
eventType: input.eventType,
|
|
501
|
-
userId
|
|
502
|
-
}
|
|
503
|
-
});
|
|
504
|
-
} } }),
|
|
505
|
-
pathMethods: { "/events/audit-logs": "GET" }
|
|
506
|
-
};
|
|
507
|
-
};
|
|
476
|
+
//#endregion
|
|
477
|
+
//#region src/sentinel/client.ts
|
|
478
|
+
const DEFAULT_IDENTIFY_URL = "https://kv.better-auth.com";
|
|
508
479
|
const sentinelClient = (options) => {
|
|
509
480
|
const autoSolve = options?.autoSolveChallenge !== false;
|
|
510
481
|
const identifyUrl = options?.identifyUrl ?? env.BETTER_AUTH_KV_URL ?? DEFAULT_IDENTIFY_URL;
|
|
@@ -594,4 +565,33 @@ const sentinelClient = (options) => {
|
|
|
594
565
|
};
|
|
595
566
|
};
|
|
596
567
|
//#endregion
|
|
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
597
|
export { dashClient, sentinelClient };
|
|
@@ -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 };
|