← Public packages
@danj/audible-auth
Locked Audible credential gateway: Connect, token refresh, and narrow Audible calls for other Kody packages.
src/register.ts
98 lines · 3.3 KB · TypeScript/**
* Audible virtual-device registration.
* Ported from mkb79/Audible register.py (logic only).
*/
import { buildClientId, DEVICE_TYPE } from './login.ts'
import type { LocaleInfo } from './locales.ts'
/** Everything Amazon returns that this package uses. Lives in memory only. */
export type RegisteredDevice = {
adpToken: string
devicePrivateKey: string
accessToken: string
refreshToken: string
/** Unix seconds when accessToken expires. */
expires: number
serial: string
deviceType: string
customerId?: string
deviceName?: string
}
export async function registerDevice(input: {
authorizationCode: string
codeVerifier: string
locale: LocaleInfo
serial: string
withUsername?: boolean
}): Promise<RegisteredDevice> {
const domain = input.locale.domain
const body = {
requested_token_type: ['bearer', 'mac_dms'],
cookies: { website_cookies: [], domain: `.amazon.${domain}` },
registration_data: {
domain: 'Device',
app_version: '3.56.2',
device_serial: input.serial,
device_type: DEVICE_TYPE,
device_name:
'%FIRST_NAME%%FIRST_NAME_POSSESSIVE_STRING%%DUPE_STRATEGY_1ST%Audible for iPhone',
os_version: '15.0.0',
software_version: '35602678',
device_model: 'iPhone',
app_name: 'Audible',
},
auth_data: {
client_id: buildClientId(input.serial),
authorization_code: input.authorizationCode,
code_verifier: input.codeVerifier,
code_algorithm: 'SHA-256',
client_domain: 'DeviceLegacy',
},
requested_extensions: ['device_info', 'customer_info'],
}
const target = input.withUsername ? 'audible' : 'amazon'
const response = await fetch(`https://api.${target}.${domain}/auth/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify(body),
})
const json = (await response.json().catch(() => null)) as Record<string, unknown> | null
if (!response.ok) {
throw new Error(`Device registration failed (HTTP ${response.status}).`)
}
const success = (json?.response as Record<string, unknown> | undefined)?.success as
| Record<string, unknown>
| undefined
if (!success) throw new Error('Device registration response missing success payload.')
const tokens = success.tokens as Record<string, unknown>
const mac = tokens.mac_dms as Record<string, unknown>
const bearer = tokens.bearer as Record<string, unknown>
const extensions = (success.extensions ?? {}) as Record<string, unknown>
const deviceInfo = (extensions.device_info ?? {}) as Record<string, unknown>
const customerInfo = (extensions.customer_info ?? {}) as Record<string, unknown>
if (
typeof mac?.adp_token !== 'string' ||
typeof mac?.device_private_key !== 'string' ||
typeof bearer?.access_token !== 'string' ||
typeof bearer?.refresh_token !== 'string'
) {
throw new Error('Device registration response is missing tokens.')
}
return {
adpToken: mac.adp_token,
devicePrivateKey: mac.device_private_key,
accessToken: bearer.access_token,
refreshToken: bearer.refresh_token,
expires: Math.floor(Date.now() / 1000) + Number(bearer.expires_in ?? 3600),
serial: String(deviceInfo.device_serial_number ?? input.serial),
deviceType: String(deviceInfo.device_type ?? DEVICE_TYPE),
customerId: typeof customerInfo.user_id === 'string' ? customerInfo.user_id : undefined,
deviceName: typeof deviceInfo.device_name === 'string' ? deviceInfo.device_name : undefined,
}
}