user.ts
utils/user.ts
No strong subsystem tag
195
Lines
5714
Bytes
7
Exports
8
Imports
10
Keywords
What this is
This page documents one file from the repository and includes its full source so you can read it without leaving the docs site.
Beginner explanation
This file is one piece of the larger system. Its name, directory, imports, and exports show where it fits. Start by reading the exports and related files first.
How it is used
Start from the exports list and related files. Those are the easiest clues for where this file fits into the system.
Expert explanation
Architecturally, this file intersects with general runtime concerns. It contains 195 lines, 8 detected imports, and 7 detected exports.
Important relationships
Detected exports
GitHubActionsMetadataCoreUserDatainitUserresetUserCachegetCoreUserDatagetUserForGrowthBookgetGitEmail
Keywords
processemailoauthaccountuserdataconfigcachedemailemailfetchpromiseoauthcache
Detected imports
execalodash-es/memoize.js../bootstrap/state.js./auth.js./config.js./cwd.js./env.js./envUtils.js
Source notes
This page embeds the full file contents. Small or leaf files are still indexed honestly instead of being over-explained.
Full source
import { execa } from 'execa'
import memoize from 'lodash-es/memoize.js'
import { getSessionId } from '../bootstrap/state.js'
import {
getOauthAccountInfo,
getRateLimitTier,
getSubscriptionType,
} from './auth.js'
import { getGlobalConfig, getOrCreateUserID } from './config.js'
import { getCwd } from './cwd.js'
import { type env, getHostPlatformForAnalytics } from './env.js'
import { isEnvTruthy } from './envUtils.js'
// Cache for email fetched asynchronously at startup
let cachedEmail: string | undefined | null = null // null means not fetched yet
let emailFetchPromise: Promise<string | undefined> | null = null
/**
* GitHub Actions metadata when running in CI
*/
export type GitHubActionsMetadata = {
actor?: string
actorId?: string
repository?: string
repositoryId?: string
repositoryOwner?: string
repositoryOwnerId?: string
}
/**
* Core user data used as base for all analytics providers.
* This is also the format used by GrowthBook.
*/
export type CoreUserData = {
deviceId: string
sessionId: string
email?: string
appVersion: string
platform: typeof env.platform
organizationUuid?: string
accountUuid?: string
userType?: string
subscriptionType?: string
rateLimitTier?: string
firstTokenTime?: number
githubActionsMetadata?: GitHubActionsMetadata
}
/**
* Initialize user data asynchronously. Should be called early in startup.
* This pre-fetches the email so getUser() can remain synchronous.
*/
export async function initUser(): Promise<void> {
if (cachedEmail === null && !emailFetchPromise) {
emailFetchPromise = getEmailAsync()
cachedEmail = await emailFetchPromise
emailFetchPromise = null
// Clear memoization cache so next call picks up the email
getCoreUserData.cache.clear?.()
}
}
/**
* Reset all user data caches. Call on auth changes (login/logout/account switch)
* so the next getCoreUserData() call picks up fresh credentials and email.
*/
export function resetUserCache(): void {
cachedEmail = null
emailFetchPromise = null
getCoreUserData.cache.clear?.()
getGitEmail.cache.clear?.()
}
/**
* Get core user data.
* This is the base representation that gets transformed for different analytics providers.
*/
export const getCoreUserData = memoize(
(includeAnalyticsMetadata?: boolean): CoreUserData => {
const deviceId = getOrCreateUserID()
const config = getGlobalConfig()
let subscriptionType: string | undefined
let rateLimitTier: string | undefined
let firstTokenTime: number | undefined
if (includeAnalyticsMetadata) {
subscriptionType = getSubscriptionType() ?? undefined
rateLimitTier = getRateLimitTier() ?? undefined
if (subscriptionType && config.claudeCodeFirstTokenDate) {
const configFirstTokenTime = new Date(
config.claudeCodeFirstTokenDate,
).getTime()
if (!isNaN(configFirstTokenTime)) {
firstTokenTime = configFirstTokenTime
}
}
}
// Only include OAuth account data when actively using OAuth authentication
const oauthAccount = getOauthAccountInfo()
const organizationUuid = oauthAccount?.organizationUuid
const accountUuid = oauthAccount?.accountUuid
return {
deviceId,
sessionId: getSessionId(),
email: getEmail(),
appVersion: MACRO.VERSION,
platform: getHostPlatformForAnalytics(),
organizationUuid,
accountUuid,
userType: process.env.USER_TYPE,
subscriptionType,
rateLimitTier,
firstTokenTime,
...(isEnvTruthy(process.env.GITHUB_ACTIONS) && {
githubActionsMetadata: {
actor: process.env.GITHUB_ACTOR,
actorId: process.env.GITHUB_ACTOR_ID,
repository: process.env.GITHUB_REPOSITORY,
repositoryId: process.env.GITHUB_REPOSITORY_ID,
repositoryOwner: process.env.GITHUB_REPOSITORY_OWNER,
repositoryOwnerId: process.env.GITHUB_REPOSITORY_OWNER_ID,
},
}),
}
},
)
/**
* Get user data for GrowthBook (same as core data with analytics metadata).
*/
export function getUserForGrowthBook(): CoreUserData {
return getCoreUserData(true)
}
function getEmail(): string | undefined {
// Return cached email if available (from async initialization)
if (cachedEmail !== null) {
return cachedEmail
}
// Only include OAuth email when actively using OAuth authentication
const oauthAccount = getOauthAccountInfo()
if (oauthAccount?.emailAddress) {
return oauthAccount.emailAddress
}
// Ant-only fallbacks below (no execSync)
if (process.env.USER_TYPE !== 'ant') {
return undefined
}
if (process.env.COO_CREATOR) {
return `${process.env.COO_CREATOR}@anthropic.com`
}
// If initUser() wasn't called, we return undefined instead of blocking
return undefined
}
async function getEmailAsync(): Promise<string | undefined> {
// Only include OAuth email when actively using OAuth authentication
const oauthAccount = getOauthAccountInfo()
if (oauthAccount?.emailAddress) {
return oauthAccount.emailAddress
}
// Ant-only fallbacks below
if (process.env.USER_TYPE !== 'ant') {
return undefined
}
if (process.env.COO_CREATOR) {
return `${process.env.COO_CREATOR}@anthropic.com`
}
return getGitEmail()
}
/**
* Get the user's git email from `git config user.email`.
* Memoized so the subprocess only spawns once per process.
*/
export const getGitEmail = memoize(async (): Promise<string | undefined> => {
const result = await execa('git config --get user.email', {
shell: true,
reject: false,
cwd: getCwd(),
})
return result.exitCode === 0 && result.stdout
? result.stdout.trim()
: undefined
})