Download src/services/api/referral.ts from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 7.99 kB
-
https://huggingface.co/chenbhao/codev/resolve/main/src/services/api/referral.ts
- Command line
-
hf download hf://chenbhao/codev/src/services/api/referral.ts
-
curl -L -o referral.ts https://huggingface.co/chenbhao/codev/resolve/main/src/services/api/referral.ts
7.99 kB
| import axios from 'axios' | |
| import { getOauthConfig } from '../../constants/oauth.js' | |
| import { | |
| getOauthAccountInfo, | |
| getSubscriptionType, | |
| isClaudeAISubscriber, | |
| } from '../../utils/auth.js' | |
| import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js' | |
| import { logForDebugging } from '../../utils/debug.js' | |
| import { logError } from '../../utils/log.js' | |
| import { isEssentialTrafficOnly } from '../../utils/privacyLevel.js' | |
| import { getOAuthHeaders, prepareApiRequest } from '../../utils/teleport/api.js' | |
| import type { | |
| ReferralCampaign, | |
| ReferralEligibilityResponse, | |
| ReferralRedemptionsResponse, | |
| ReferrerRewardInfo, | |
| } from '../oauth/types.js' | |
| // Cache expiration time: 24 hours (eligibility changes only on subscription/experiment changes) | |
| const CACHE_EXPIRATION_MS = 24 * 60 * 60 * 1000 | |
| // Track in-flight fetch to prevent duplicate API calls | |
| let fetchInProgress: Promise<ReferralEligibilityResponse | null> | null = null | |
| export async function fetchReferralEligibility( | |
| campaign: ReferralCampaign = 'claude_code_guest_pass', | |
| ): Promise<ReferralEligibilityResponse> { | |
| const { accessToken, orgUUID } = await prepareApiRequest() | |
| const headers = { | |
| ...getOAuthHeaders(accessToken), | |
| 'x-organization-uuid': orgUUID, | |
| } | |
| const url = `${getOauthConfig().BASE_API_URL}/api/oauth/organizations/${orgUUID}/referral/eligibility` | |
| const response = await axios.get(url, { | |
| headers, | |
| params: { campaign }, | |
| timeout: 5000, // 5 second timeout for background fetch | |
| }) | |
| return response.data | |
| } | |
| export async function fetchReferralRedemptions( | |
| campaign: string = 'claude_code_guest_pass', | |
| ): Promise<ReferralRedemptionsResponse> { | |
| const { accessToken, orgUUID } = await prepareApiRequest() | |
| const headers = { | |
| ...getOAuthHeaders(accessToken), | |
| 'x-organization-uuid': orgUUID, | |
| } | |
| const url = `${getOauthConfig().BASE_API_URL}/api/oauth/organizations/${orgUUID}/referral/redemptions` | |
| const response = await axios.get<ReferralRedemptionsResponse>(url, { | |
| headers, | |
| params: { campaign }, | |
| timeout: 10000, // 10 second timeout | |
| }) | |
| return response.data | |
| } | |
| /** | |
| * Prechecks for if user can access guest passes feature | |
| */ | |
| function shouldCheckForPasses(): boolean { | |
| return !!( | |
| getOauthAccountInfo()?.organizationUuid && | |
| isClaudeAISubscriber() && | |
| getSubscriptionType() === 'max' | |
| ) | |
| } | |
| /** | |
| * Check cached passes eligibility from GlobalConfig | |
| * Returns current cached state and cache status | |
| */ | |
| export function checkCachedPassesEligibility(): { | |
| eligible: boolean | |
| needsRefresh: boolean | |
| hasCache: boolean | |
| } { | |
| if (!shouldCheckForPasses()) { | |
| return { | |
| eligible: false, | |
| needsRefresh: false, | |
| hasCache: false, | |
| } | |
| } | |
| const orgId = getOauthAccountInfo()?.organizationUuid | |
| if (!orgId) { | |
| return { | |
| eligible: false, | |
| needsRefresh: false, | |
| hasCache: false, | |
| } | |
| } | |
| const config = getGlobalConfig() | |
| const cachedEntry = config.passesEligibilityCache?.[orgId] | |
| if (!cachedEntry) { | |
| // No cached entry, needs fetch | |
| return { | |
| eligible: false, | |
| needsRefresh: true, | |
| hasCache: false, | |
| } | |
| } | |
| const { eligible, timestamp } = cachedEntry | |
| const now = Date.now() | |
| const needsRefresh = now - timestamp > CACHE_EXPIRATION_MS | |
| return { | |
| eligible, | |
| needsRefresh, | |
| hasCache: true, | |
| } | |
| } | |
| const CURRENCY_SYMBOLS: Record<string, string> = { | |
| USD: '$', | |
| EUR: '€', | |
| GBP: '£', | |
| BRL: 'R$', | |
| CAD: 'CA$', | |
| AUD: 'A$', | |
| NZD: 'NZ$', | |
| SGD: 'S$', | |
| } | |
| export function formatCreditAmount(reward: ReferrerRewardInfo): string { | |
| const symbol = CURRENCY_SYMBOLS[reward.currency] ?? `${reward.currency} ` | |
| const amount = reward.amount_minor_units / 100 | |
| const formatted = amount % 1 === 0 ? amount.toString() : amount.toFixed(2) | |
| return `${symbol}${formatted}` | |
| } | |
| /** | |
| * Get cached referrer reward info from eligibility cache | |
| * Returns the reward info if the user is in a v1 campaign, null otherwise | |
| */ | |
| export function getCachedReferrerReward(): ReferrerRewardInfo | null { | |
| const orgId = getOauthAccountInfo()?.organizationUuid | |
| if (!orgId) return null | |
| const config = getGlobalConfig() | |
| const cachedEntry = config.passesEligibilityCache?.[orgId] | |
| return cachedEntry?.referrer_reward ?? null | |
| } | |
| /** | |
| * Get the cached remaining passes count from eligibility cache | |
| * Returns the number of remaining passes, or null if not available | |
| */ | |
| export function getCachedRemainingPasses(): number | null { | |
| const orgId = getOauthAccountInfo()?.organizationUuid | |
| if (!orgId) return null | |
| const config = getGlobalConfig() | |
| const cachedEntry = config.passesEligibilityCache?.[orgId] | |
| return cachedEntry?.remaining_passes ?? null | |
| } | |
| /** | |
| * Fetch passes eligibility and store in GlobalConfig | |
| * Returns the fetched response or null on error | |
| */ | |
| export async function fetchAndStorePassesEligibility(): Promise<ReferralEligibilityResponse | null> { | |
| // Return existing promise if fetch is already in progress | |
| if (fetchInProgress) { | |
| logForDebugging('Passes: Reusing in-flight eligibility fetch') | |
| return fetchInProgress | |
| } | |
| const orgId = getOauthAccountInfo()?.organizationUuid | |
| if (!orgId) { | |
| return null | |
| } | |
| // Store the promise to share with concurrent calls | |
| fetchInProgress = (async () => { | |
| try { | |
| const response = await fetchReferralEligibility() | |
| const cacheEntry = { | |
| ...response, | |
| timestamp: Date.now(), | |
| } | |
| saveGlobalConfig(current => ({ | |
| ...current, | |
| passesEligibilityCache: { | |
| ...current.passesEligibilityCache, | |
| [orgId]: cacheEntry, | |
| }, | |
| })) | |
| logForDebugging( | |
| `Passes eligibility cached for org ${orgId}: ${response.eligible}`, | |
| ) | |
| return response | |
| } catch (error) { | |
| logForDebugging('Failed to fetch and cache passes eligibility') | |
| logError(error as Error) | |
| return null | |
| } finally { | |
| // Clear the promise when done | |
| fetchInProgress = null | |
| } | |
| })() | |
| return fetchInProgress | |
| } | |
| /** | |
| * Get cached passes eligibility data or fetch if needed | |
| * Main entry point for all eligibility checks | |
| * | |
| * This function never blocks on network - it returns cached data immediately | |
| * and fetches in the background if needed. On cold start (no cache), it returns | |
| * null and the passes command won't be available until the next session. | |
| */ | |
| export async function getCachedOrFetchPassesEligibility(): Promise<ReferralEligibilityResponse | null> { | |
| if (!shouldCheckForPasses()) { | |
| return null | |
| } | |
| const orgId = getOauthAccountInfo()?.organizationUuid | |
| if (!orgId) { | |
| return null | |
| } | |
| const config = getGlobalConfig() | |
| const cachedEntry = config.passesEligibilityCache?.[orgId] | |
| const now = Date.now() | |
| // No cache - trigger background fetch and return null (non-blocking) | |
| // The passes command won't be available this session, but will be next time | |
| if (!cachedEntry) { | |
| logForDebugging( | |
| 'Passes: No cache, fetching eligibility in background (command unavailable this session)', | |
| ) | |
| void fetchAndStorePassesEligibility() | |
| return null | |
| } | |
| // Cache exists but is stale - return stale cache and trigger background refresh | |
| if (now - cachedEntry.timestamp > CACHE_EXPIRATION_MS) { | |
| logForDebugging( | |
| 'Passes: Cache stale, returning cached data and refreshing in background', | |
| ) | |
| void fetchAndStorePassesEligibility() // Background refresh | |
| const { timestamp, ...response } = cachedEntry | |
| return response as ReferralEligibilityResponse | |
| } | |
| // Cache is fresh - return it immediately | |
| logForDebugging('Passes: Using fresh cached eligibility data') | |
| const { timestamp, ...response } = cachedEntry | |
| return response as ReferralEligibilityResponse | |
| } | |
| /** | |
| * Prefetch passes eligibility on startup | |
| */ | |
| export async function prefetchPassesEligibility(): Promise<void> { | |
| // Skip network requests if nonessential traffic is disabled | |
| if (isEssentialTrafficOnly()) { | |
| return | |
| } | |
| void getCachedOrFetchPassesEligibility() | |
| } | |