diff --git a/src/bw/client.ts b/src/bw/client.ts index 1972b05..03aecb0 100644 --- a/src/bw/client.ts +++ b/src/bw/client.ts @@ -1,5 +1,6 @@ import { Readable } from "node:stream"; import { TokenManager } from "./token.js"; +import { USER_AGENT } from "./user-agent.js"; /** * Validate an identifier before interpolating it into a Bandwidth API URL path. @@ -111,7 +112,7 @@ export function createBwClient(cfg: { async createCall({ to, from, answerUrl }) { const res = await fetchImpl(`${base}/accounts/${cfg.accountId}/calls`, { method: "POST", - headers: { "Content-Type": "application/json", Authorization: await authHeader() }, + headers: { "Content-Type": "application/json", Authorization: await authHeader(), "User-Agent": USER_AGENT }, body: JSON.stringify({ to, from, answerUrl, applicationId: cfg.applicationId }), }); if (!res.ok) @@ -122,7 +123,7 @@ export function createBwClient(cfg: { async modifyCall(callId, opts) { const res = await fetchImpl(`${base}/accounts/${cfg.accountId}/calls/${safeId(callId)}`, { method: "POST", - headers: { "Content-Type": "application/json", Authorization: await authHeader() }, + headers: { "Content-Type": "application/json", Authorization: await authHeader(), "User-Agent": USER_AGENT }, body: JSON.stringify(opts), }); if (!res.ok) @@ -130,7 +131,7 @@ export function createBwClient(cfg: { }, async getCall(callId) { const res = await fetchImpl(`${base}/accounts/${cfg.accountId}/calls/${safeId(callId)}`, { - headers: { Accept: "application/json", Authorization: await authHeader() }, + headers: { Accept: "application/json", Authorization: await authHeader(), "User-Agent": USER_AGENT }, }); if (!res.ok) throw new Error(`Bandwidth getCall failed: ${res.status} ${await res.text()}`); @@ -140,7 +141,7 @@ export function createBwClient(cfg: { }, async listRecordings(callId) { const res = await fetchImpl(`${base}/accounts/${cfg.accountId}/calls/${safeId(callId)}/recordings`, { - headers: { Accept: "application/json", Authorization: await authHeader() }, + headers: { Accept: "application/json", Authorization: await authHeader(), "User-Agent": USER_AGENT }, }); if (!res.ok) throw new Error(`Bandwidth listRecordings failed: ${res.status} ${await res.text()}`); @@ -150,7 +151,7 @@ export function createBwClient(cfg: { async getRecording(callId, recordingId) { const res = await fetchImpl( `${base}/accounts/${cfg.accountId}/calls/${safeId(callId)}/recordings/${safeId(recordingId)}`, - { headers: { Accept: "application/json", Authorization: await authHeader() } }, + { headers: { Accept: "application/json", Authorization: await authHeader(), "User-Agent": USER_AGENT } }, ); if (!res.ok) throw new Error(`Bandwidth getRecording failed: ${res.status} ${await res.text()}`); @@ -160,7 +161,7 @@ export function createBwClient(cfg: { async getRecordingMedia(callId, recordingId) { const res = await fetchImpl( `${base}/accounts/${cfg.accountId}/calls/${safeId(callId)}/recordings/${safeId(recordingId)}/media`, - { headers: { Authorization: await authHeader() } }, + { headers: { Authorization: await authHeader(), "User-Agent": USER_AGENT } }, ); if (!res.ok || !res.body) throw new Error(`Bandwidth getRecordingMedia failed: ${res.status} ${await res.text()}`); @@ -173,7 +174,7 @@ export function createBwClient(cfg: { async updateRecording(callId, state) { const res = await fetchImpl(`${base}/accounts/${cfg.accountId}/calls/${safeId(callId)}/recording`, { method: "PUT", - headers: { "Content-Type": "application/json", Authorization: await authHeader() }, + headers: { "Content-Type": "application/json", Authorization: await authHeader(), "User-Agent": USER_AGENT }, body: JSON.stringify({ state }), }); if (!res.ok) diff --git a/src/bw/token.ts b/src/bw/token.ts index 0478287..980a0fc 100644 --- a/src/bw/token.ts +++ b/src/bw/token.ts @@ -1,3 +1,5 @@ +import { USER_AGENT } from "./user-agent.js"; + export interface TokenManagerOpts { clientId: string; clientSecret: string; @@ -40,7 +42,11 @@ export class TokenManager { const basic = "Basic " + Buffer.from(`${this.opts.clientId}:${this.opts.clientSecret}`).toString("base64"); const res = await this.fetchImpl(`${this.opts.apiHost}/api/v1/oauth2/token`, { method: "POST", - headers: { Authorization: basic, "Content-Type": "application/x-www-form-urlencoded" }, + headers: { + Authorization: basic, + "Content-Type": "application/x-www-form-urlencoded", + "User-Agent": USER_AGENT, + }, body: "grant_type=client_credentials", }); if (!res.ok) diff --git a/src/bw/user-agent.ts b/src/bw/user-agent.ts new file mode 100644 index 0000000..5fd963d --- /dev/null +++ b/src/bw/user-agent.ts @@ -0,0 +1,7 @@ +import pkg from "../../package.json" with { type: "json" }; + +/** + * Client identifier sent on every outbound Bandwidth request so BW can + * attribute traffic to this adapter and version. Format: `bw-voice-adapter/`. + */ +export const USER_AGENT = `bw-voice-adapter/${pkg.version}`;