( apiKey: string, apiUrl: string, )
| 80 | } |
| 81 | |
| 82 | async function getDistinctId( |
| 83 | apiKey: string, |
| 84 | apiUrl: string, |
| 85 | ): Promise<{ identity: IdentityInfo; email?: string }> { |
| 86 | const cached = identityCache.get(apiKey); |
| 87 | if (cached) return cached; |
| 88 | |
| 89 | try { |
| 90 | const res = await fetch(`${apiUrl}/users/me`, { |
| 91 | method: "GET", |
| 92 | headers: { |
| 93 | "X-API-Key": apiKey, |
| 94 | "Content-Type": "application/json", |
| 95 | }, |
| 96 | }); |
| 97 | |
| 98 | if (res.ok) { |
| 99 | const payload = await res.json(); |
| 100 | if (payload?.id) { |
| 101 | const result = { |
| 102 | identity: { |
| 103 | distinct_id: payload.id, |
| 104 | distinct_id_source: "database_id", |
| 105 | }, |
| 106 | email: payload.email || undefined, |
| 107 | }; |
| 108 | identityCache.set(apiKey, result); |
| 109 | return result; |
| 110 | } |
| 111 | } |
| 112 | } catch { |
| 113 | // Fall through to API key hash |
| 114 | } |
| 115 | |
| 116 | // Don't cache the fallback — a transient /users/me failure should not poison the cache for the entire process lifetime |
| 117 | const hash = createHash("sha256").update(apiKey).digest("hex").slice(0, 16); |
| 118 | return { |
| 119 | identity: { |
| 120 | distinct_id: `apikey-${hash}`, |
| 121 | distinct_id_source: "api_key_hash", |
| 122 | }, |
| 123 | }; |
| 124 | } |
| 125 | |
| 126 | export function _resetIdentityCache() { |
| 127 | identityCache.clear(); |
no test coverage detected