Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | 6x 6x 6x 6x 6x 1x 5x 6x 6x 9x 9x 9x 2x 2x 2x 1x | import * as fs from 'node:fs'
import * as os from 'node:os'
import * as path from 'node:path'
import { fetchModels, getUserInfo } from '../api/appStoreClient'
import { login } from '../api/authClient'
import { getEnvConfig } from '../api/config'
import { readAccessToken } from '../api/credentials'
export class AuthService {
userInfo: { displayName: string; username: string; avatarUrl: string } | null = null
constructor(private postToWebview: (msg: unknown) => void) {}
async handleLogin(userName: string, password: string, restartAgent: () => void): Promise<void> {
const { loginUrl, authUrl } = getEnvConfig()
try {
const { accessToken, refreshToken } = await login(loginUrl, userName, password)
writeCredentials(authUrl, accessToken, refreshToken)
this.userInfo = await getUserInfo(accessToken)
this.postToWebview({ type: 'loginSuccess', userInfo: this.userInfo })
restartAgent()
} catch (err) {
this.postToWebview({
type: 'loginError',
error: err instanceof Error ? err.message : 'Login failed',
})
}
}
handleLogout(): void {
const { authUrl } = getEnvConfig()
writeCredentials(authUrl, null, null)
this.userInfo = null
this.postToWebview({ type: 'loggedOut' })
}
async fetchAndSendUserInfo(): Promise<void> {
const accessToken = readAccessToken()
if (!accessToken) return
try {
this.userInfo = await getUserInfo(accessToken)
this.postToWebview({ type: 'userInfo', userInfo: this.userInfo })
} catch {
// token expired or invalid — silently stay logged out
}
}
async fetchAndSendModels(): Promise<void> {
const accessToken = readAccessToken()
try {
const models = await fetchModels(accessToken ?? undefined)
this.postToWebview({
type: 'availableModels',
models: models.map((m) => ({
name: m.codename,
displayName: m.displayName,
vendor: m.vendor,
...(m.contextWindow !== undefined && { contextWindow: m.contextWindow }),
...(m.tokenScalingFactor !== undefined && { tokenScalingFactor: m.tokenScalingFactor }),
})),
})
} catch {
// silently ignore — model dropdown will stay empty
}
}
}
export function writeCredentials(authUrl: string, accessToken: string | null, refreshToken: string | null): void {
const credPath = path.join(os.homedir(), '.boltzbit', 'credentials.json')
let store: Record<string, { accessToken?: string; refreshToken?: string; expiresAt?: number }> = {}
try {
store = JSON.parse(fs.readFileSync(credPath, 'utf-8'))
} catch {
// file doesn't exist yet — start fresh
}
if (accessToken === null) {
delete store[authUrl]
} else {
store[authUrl] = {
accessToken,
refreshToken: refreshToken ?? undefined,
expiresAt: decodeJwtExpiry(accessToken),
}
}
fs.mkdirSync(path.dirname(credPath), { recursive: true })
fs.writeFileSync(credPath, JSON.stringify(store, null, 2))
}
export function decodeJwtExpiry(token: string): number | undefined {
try {
const payload = token.split('.')[1]
if (!payload) return undefined
const json = Buffer.from(payload, 'base64url').toString('utf-8')
const { exp } = JSON.parse(json) as { exp?: number }
if (typeof exp !== 'number') return undefined
return exp * 1000
} catch {
return undefined
}
}
|