137 lines
4.0 KiB
JavaScript
137 lines
4.0 KiB
JavaScript
const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager");
|
|
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
|
|
const { InstanceRegion, InstanceIsLocalStackEnabled, InstanceLocalStackEndpoint } = require("../utils/instanceMgr");
|
|
|
|
const CHATTER_BASE_URL = process.env.CHATTER_API_BASE_URL || "https://api.chatterresearch.com";
|
|
|
|
// Configure SecretsManager client with localstack support
|
|
const secretsClientOptions = {
|
|
region: InstanceRegion(),
|
|
credentials: defaultProvider()
|
|
};
|
|
|
|
if (InstanceIsLocalStackEnabled()) {
|
|
secretsClientOptions.endpoint = InstanceLocalStackEndpoint();
|
|
}
|
|
|
|
const secretsClient = new SecretsManagerClient(secretsClientOptions);
|
|
|
|
/**
|
|
* Chatter API Client for making requests to the Chatter API
|
|
*/
|
|
class ChatterApiClient {
|
|
constructor({ baseUrl, apiToken }) {
|
|
if (!apiToken) throw new Error("ChatterApiClient requires apiToken");
|
|
this.baseUrl = String(baseUrl || "").replace(/\/+$/, "");
|
|
this.apiToken = apiToken;
|
|
}
|
|
|
|
async createLocation(companyId, payload) {
|
|
return this.request(`/api/v1/companies/${companyId}/locations`, {
|
|
method: "POST",
|
|
body: payload
|
|
});
|
|
}
|
|
|
|
async postInteraction(companyId, payload) {
|
|
return this.request(`/api/v1/companies/${companyId}/solicitation/interaction`, {
|
|
method: "POST",
|
|
body: payload
|
|
});
|
|
}
|
|
|
|
async request(path, { method = "GET", body } = {}) {
|
|
const res = await fetch(this.baseUrl + path, {
|
|
method,
|
|
headers: {
|
|
"Api-Token": this.apiToken,
|
|
Accept: "application/json",
|
|
...(body ? { "Content-Type": "application/json" } : {})
|
|
},
|
|
body: body ? JSON.stringify(body) : undefined
|
|
});
|
|
|
|
const text = await res.text();
|
|
const data = text ? safeJson(text) : null;
|
|
|
|
if (!res.ok) {
|
|
const err = new Error(`Chatter API error ${res.status} | ${data?.message}`);
|
|
err.status = res.status;
|
|
err.data = data;
|
|
const retryAfterMs = parseRetryAfterMs(res.headers.get("retry-after"));
|
|
if (retryAfterMs != null) err.retryAfterMs = retryAfterMs;
|
|
throw err;
|
|
}
|
|
return data;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Safely parse JSON, returning original text if parsing fails
|
|
*/
|
|
function safeJson(text) {
|
|
try {
|
|
return JSON.parse(text);
|
|
} catch {
|
|
return text;
|
|
}
|
|
}
|
|
|
|
function parseRetryAfterMs(value) {
|
|
if (!value) return null;
|
|
|
|
const sec = Number(value);
|
|
if (Number.isFinite(sec) && sec >= 0) return Math.ceil(sec * 1000);
|
|
|
|
const dateMs = Date.parse(value);
|
|
if (!Number.isFinite(dateMs)) return null;
|
|
return Math.max(0, dateMs - Date.now());
|
|
}
|
|
|
|
/**
|
|
* Fetches Chatter API token from AWS Secrets Manager
|
|
* SecretId format: CHATTER_COMPANY_KEY_<companyId>
|
|
*
|
|
* @param {string|number} companyId - The company ID
|
|
* @returns {Promise<string>} The API token
|
|
*/
|
|
async function getChatterApiToken(companyId) {
|
|
const key = String(companyId ?? "").trim();
|
|
if (!key) throw new Error("getChatterApiToken: companyId is required");
|
|
|
|
// Optional override for development/testing
|
|
if (process.env.CHATTER_API_TOKEN) return process.env.CHATTER_API_TOKEN;
|
|
|
|
const secretId = `CHATTER_COMPANY_KEY_${key}`;
|
|
const command = new GetSecretValueCommand({ SecretId: secretId });
|
|
const { SecretString, SecretBinary } = await secretsClient.send(command);
|
|
|
|
const token =
|
|
(SecretString && SecretString.trim()) ||
|
|
(SecretBinary && Buffer.from(SecretBinary, "base64").toString("ascii").trim()) ||
|
|
"";
|
|
|
|
if (!token) throw new Error(`Chatter API token secret is empty: ${secretId}`);
|
|
return token;
|
|
}
|
|
|
|
/**
|
|
* Creates a Chatter API client instance
|
|
*
|
|
* @param {string|number} companyId - The company ID
|
|
* @param {string} [baseUrl] - Optional base URL override
|
|
* @returns {Promise<ChatterApiClient>} Configured API client
|
|
*/
|
|
async function createChatterClient(companyId, baseUrl = CHATTER_BASE_URL) {
|
|
const apiToken = await getChatterApiToken(companyId);
|
|
return new ChatterApiClient({ baseUrl, apiToken });
|
|
}
|
|
|
|
module.exports = {
|
|
ChatterApiClient,
|
|
getChatterApiToken,
|
|
createChatterClient,
|
|
safeJson,
|
|
CHATTER_BASE_URL
|
|
};
|