← back to Rc Fm Callnotes
lib/rc.js
80 lines
// RingCentral REST client — JWT grant (same app as /root/DW-Agents/ringcentral-agent).
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const __dir = dirname(fileURLToPath(import.meta.url));
const CFG = JSON.parse(readFileSync(join(__dir, '..', 'data', 'rc-config.json'), 'utf8'));
let session = null; // { token, exp }
export async function getToken() {
const now = Date.now();
if (session && now < session.exp) return session.token;
const res = await fetch(`${CFG.serverUrl}/restapi/oauth/token`, {
method: 'POST',
headers: {
Authorization: 'Basic ' + Buffer.from(`${CFG.clientId}:${CFG.clientSecret}`).toString('base64'),
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: CFG.jwtToken,
}),
});
const json = await res.json();
if (!res.ok) throw new Error(`RC auth failed: ${JSON.stringify(json).slice(0, 300)}`);
session = { token: json.access_token, exp: now + (json.expires_in - 60) * 1000 };
return session.token;
}
export async function rc(path, { method = 'GET', body, query } = {}) {
const token = await getToken();
const url = new URL(`${CFG.serverUrl}${path}`);
for (const [k, v] of Object.entries(query || {})) url.searchParams.set(k, v);
const res = await fetch(url, {
method,
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: body ? JSON.stringify(body) : undefined,
});
if (res.status === 204) return {};
const json = await res.json().catch(() => ({}));
if (!res.ok) {
const err = new Error(`RC ${method} ${path} -> ${res.status}: ${JSON.stringify(json).slice(0, 300)}`);
err.status = res.status;
throw err;
}
return json;
}
// Company-wide call log (detailed view carries per-leg info).
export function callLog({ dateFrom, dateTo, perPage = 100 } = {}) {
const query = { view: 'Detailed', perPage: String(perPage) };
if (dateFrom) query.dateFrom = dateFrom;
if (dateTo) query.dateTo = dateTo;
return rc('/restapi/v1.0/account/~/call-log', { query });
}
// Personal address-book contacts on the authorized extension (caller-ID source).
export function listContacts({ page = 1, perPage = 100 } = {}) {
return rc('/restapi/v1.0/account/~/extension/~/address-book/contact', {
query: { page: String(page), perPage: String(perPage) },
});
}
export function createContact(contact) {
return rc('/restapi/v1.0/account/~/extension/~/address-book/contact', {
method: 'POST', body: contact,
});
}
export function deleteContact(id) {
return rc(`/restapi/v1.0/account/~/extension/~/address-book/contact/${id}`, { method: 'DELETE' });
}
// Raw media download (call recordings, voicemail audio). Returns a Buffer.
export async function downloadMedia(uri) {
const token = await getToken();
const res = await fetch(uri, { headers: { Authorization: `Bearer ${token}` } });
if (!res.ok) throw new Error(`RC media ${uri.split('/account/')[1] || uri} -> ${res.status}`);
return Buffer.from(await res.arrayBuffer());
}