Files
oh-my-openagent/src/auth/antigravity/token.ts
YeonGyu-Kim d450c4f966 fix(antigravity-auth): address Oracle feedback - custom credentials, logging, docs
- Fix custom credentials to actually work in OAuth/refresh flows
  - oauth.ts: Add clientId/clientSecret parameters to buildAuthURL(), exchangeCode()
  - token.ts: Add clientId/clientSecret parameters to refreshAccessToken()
  - fetch.ts: Pass credentials to oauth/token functions
  - plugin.ts: Use closure cache for credentials, pass to all flows

- Unify console.* logging policy with ANTIGRAVITY_DEBUG guards
  - constants.ts: Document logging policy
  - tools.ts: Guard console.warn with ANTIGRAVITY_DEBUG
  - plugin.ts: Guard 4 console.error with ANTIGRAVITY_DEBUG

- Add explicit init.body type handling
  - fetch.ts: Check body type, pass-through non-string bodies
  - fetch.ts: Document body type assumption

- Document SSE buffering behavior
  - response.ts: Add warning that current implementation buffers
  - response.ts: Add TODO for future ReadableStream enhancement

🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
2025-12-13 00:35:34 +09:00

120 lines
3.7 KiB
TypeScript

/**
* Antigravity token management utilities.
* Handles token expiration checking, refresh, and storage format parsing.
*/
import {
ANTIGRAVITY_CLIENT_ID,
ANTIGRAVITY_CLIENT_SECRET,
ANTIGRAVITY_TOKEN_REFRESH_BUFFER_MS,
GOOGLE_TOKEN_URL,
} from "./constants"
import type {
AntigravityRefreshParts,
AntigravityTokenExchangeResult,
AntigravityTokens,
} from "./types"
/**
* Check if the access token is expired.
* Includes a 60-second safety buffer to refresh before actual expiration.
*
* @param tokens - The Antigravity tokens to check
* @returns true if the token is expired or will expire within the buffer period
*/
export function isTokenExpired(tokens: AntigravityTokens): boolean {
// Calculate when the token expires (timestamp + expires_in in ms)
// timestamp is in milliseconds, expires_in is in seconds
const expirationTime = tokens.timestamp + tokens.expires_in * 1000
// Check if current time is past (expiration - buffer)
return Date.now() >= expirationTime - ANTIGRAVITY_TOKEN_REFRESH_BUFFER_MS
}
/**
* Refresh an access token using a refresh token.
* Exchanges the refresh token for a new access token via Google's OAuth endpoint.
*
* @param refreshToken - The refresh token to use
* @param clientId - Optional custom client ID (defaults to ANTIGRAVITY_CLIENT_ID)
* @param clientSecret - Optional custom client secret (defaults to ANTIGRAVITY_CLIENT_SECRET)
* @returns Token exchange result with new access token, or throws on error
*/
export async function refreshAccessToken(
refreshToken: string,
clientId: string = ANTIGRAVITY_CLIENT_ID,
clientSecret: string = ANTIGRAVITY_CLIENT_SECRET
): Promise<AntigravityTokenExchangeResult> {
const params = new URLSearchParams({
grant_type: "refresh_token",
refresh_token: refreshToken,
client_id: clientId,
client_secret: clientSecret,
})
const response = await fetch(GOOGLE_TOKEN_URL, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: params,
})
if (!response.ok) {
const errorText = await response.text().catch(() => "Unknown error")
throw new Error(
`Token refresh failed: ${response.status} ${response.statusText} - ${errorText}`
)
}
const data = (await response.json()) as {
access_token: string
refresh_token?: string
expires_in: number
token_type: string
}
return {
access_token: data.access_token,
// Google may return a new refresh token, fall back to the original
refresh_token: data.refresh_token || refreshToken,
expires_in: data.expires_in,
token_type: data.token_type,
}
}
/**
* Parse a stored token string into its component parts.
* Storage format: `refreshToken|projectId|managedProjectId`
*
* @param stored - The pipe-separated stored token string
* @returns Parsed refresh parts with refreshToken, projectId, and optional managedProjectId
*/
export function parseStoredToken(stored: string): AntigravityRefreshParts {
const parts = stored.split("|")
const [refreshToken, projectId, managedProjectId] = parts
return {
refreshToken: refreshToken || "",
projectId: projectId || undefined,
managedProjectId: managedProjectId || undefined,
}
}
/**
* Format token components for storage.
* Creates a pipe-separated string: `refreshToken|projectId|managedProjectId`
*
* @param refreshToken - The refresh token
* @param projectId - The GCP project ID
* @param managedProjectId - Optional managed project ID for enterprise users
* @returns Formatted string for storage
*/
export function formatTokenForStorage(
refreshToken: string,
projectId: string,
managedProjectId?: string
): string {
return `${refreshToken}|${projectId}|${managedProjectId || ""}`
}