← 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());
}