182 lines
6.4 KiB
JavaScript
182 lines
6.4 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.listmonkClient = void 0;
|
|
const env_1 = require("../config/env");
|
|
const logger_1 = require("../utils/logger");
|
|
// --- Client ---
|
|
class ListmonkClient {
|
|
get baseUrl() {
|
|
return env_1.env.LISTMONK_URL;
|
|
}
|
|
get authHeader() {
|
|
return 'Basic ' + Buffer.from(`${env_1.env.LISTMONK_ADMIN_USER}:${env_1.env.LISTMONK_ADMIN_PASSWORD}`).toString('base64');
|
|
}
|
|
get enabled() {
|
|
return env_1.env.LISTMONK_SYNC_ENABLED === 'true';
|
|
}
|
|
assertEnabled() {
|
|
if (!this.enabled) {
|
|
throw new Error('Listmonk sync is disabled. Set LISTMONK_SYNC_ENABLED=true to enable.');
|
|
}
|
|
}
|
|
async request(method, path, body) {
|
|
const url = `${this.baseUrl}${path}`;
|
|
const controller = new AbortController();
|
|
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
try {
|
|
const res = await fetch(url, {
|
|
method,
|
|
headers: {
|
|
'Authorization': this.authHeader,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: body ? JSON.stringify(body) : undefined,
|
|
signal: controller.signal,
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => '');
|
|
throw new Error(`Listmonk API ${method} ${path} returned ${res.status}: ${text}`);
|
|
}
|
|
return await res.json();
|
|
}
|
|
finally {
|
|
clearTimeout(timeout);
|
|
}
|
|
}
|
|
async checkHealth() {
|
|
try {
|
|
const controller = new AbortController();
|
|
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
try {
|
|
const res = await fetch(`${this.baseUrl}/api/health`, {
|
|
headers: { 'Authorization': this.authHeader },
|
|
signal: controller.signal,
|
|
});
|
|
return res.ok;
|
|
}
|
|
finally {
|
|
clearTimeout(timeout);
|
|
}
|
|
}
|
|
catch (err) {
|
|
logger_1.logger.warn('Listmonk health check failed:', err instanceof Error ? err.message : err);
|
|
return false;
|
|
}
|
|
}
|
|
async getLists() {
|
|
this.assertEnabled();
|
|
try {
|
|
const res = await this.request('GET', '/api/lists?per_page=all');
|
|
return res.data.results || [];
|
|
}
|
|
catch (err) {
|
|
logger_1.logger.warn('Failed to fetch Listmonk lists:', err instanceof Error ? err.message : err);
|
|
throw err;
|
|
}
|
|
}
|
|
async createList(name, type, tags) {
|
|
this.assertEnabled();
|
|
try {
|
|
const res = await this.request('POST', '/api/lists', {
|
|
name,
|
|
type,
|
|
optin: 'single',
|
|
tags,
|
|
});
|
|
return res.data;
|
|
}
|
|
catch (err) {
|
|
logger_1.logger.warn(`Failed to create Listmonk list "${name}":`, err instanceof Error ? err.message : err);
|
|
throw err;
|
|
}
|
|
}
|
|
async findSubscriberByEmail(email) {
|
|
this.assertEnabled();
|
|
try {
|
|
const safeEmail = email.replace(/'/g, "''");
|
|
const query = encodeURIComponent(`subscribers.email='${safeEmail}'`);
|
|
const res = await this.request('GET', `/api/subscribers?query=${query}&per_page=1`);
|
|
const results = res.data.results;
|
|
return results && results.length > 0 ? results[0] : null;
|
|
}
|
|
catch (err) {
|
|
logger_1.logger.warn(`Failed to find subscriber ${email}:`, err instanceof Error ? err.message : err);
|
|
return null;
|
|
}
|
|
}
|
|
async createSubscriber(email, name, listIds, attribs) {
|
|
this.assertEnabled();
|
|
const res = await this.request('POST', '/api/subscribers', {
|
|
email,
|
|
name,
|
|
status: 'enabled',
|
|
lists: listIds,
|
|
attribs,
|
|
});
|
|
return res.data;
|
|
}
|
|
async updateSubscriber(id, data) {
|
|
this.assertEnabled();
|
|
const res = await this.request('PUT', `/api/subscribers/${id}`, data);
|
|
return res.data;
|
|
}
|
|
async updateList(id, data) {
|
|
this.assertEnabled();
|
|
const res = await this.request('PUT', `/api/lists/${id}`, data);
|
|
return res.data;
|
|
}
|
|
async deleteList(id) {
|
|
this.assertEnabled();
|
|
await this.request('DELETE', `/api/lists/${id}`);
|
|
}
|
|
async removeSubscriberFromLists(subscriberId, listIdsToRemove, currentEmail, currentListIds) {
|
|
this.assertEnabled();
|
|
const filteredIds = currentListIds.filter(id => !listIdsToRemove.includes(id));
|
|
return this.updateSubscriber(subscriberId, {
|
|
email: currentEmail,
|
|
lists: filteredIds,
|
|
});
|
|
}
|
|
/**
|
|
* Get campaigns with stats (for dashboard)
|
|
*/
|
|
async getCampaigns() {
|
|
try {
|
|
const res = await this.request('GET', '/api/campaigns?per_page=all&order_by=updated_at&order=desc');
|
|
return (res.data.results || []).map(c => ({
|
|
id: c.id,
|
|
name: c.name,
|
|
status: c.status,
|
|
sent: c.stats?.sent || 0,
|
|
views: c.stats?.views || 0,
|
|
clicks: c.stats?.clicks || 0,
|
|
started_at: c.started_at,
|
|
updated_at: c.updated_at,
|
|
}));
|
|
}
|
|
catch (err) {
|
|
logger_1.logger.warn('Failed to fetch Listmonk campaigns:', err instanceof Error ? err.message : err);
|
|
return [];
|
|
}
|
|
}
|
|
async upsertSubscriber(email, name, listIds, attribs) {
|
|
this.assertEnabled();
|
|
const existing = await this.findSubscriberByEmail(email);
|
|
if (existing) {
|
|
// Merge lists: combine existing + new, deduplicate
|
|
const existingListIds = existing.lists.map(l => l.id);
|
|
const mergedListIds = [...new Set([...existingListIds, ...listIds])];
|
|
// Merge attribs
|
|
const mergedAttribs = { ...existing.attribs, ...attribs };
|
|
return this.updateSubscriber(existing.id, {
|
|
email,
|
|
name: name || existing.name,
|
|
lists: mergedListIds,
|
|
attribs: mergedAttribs,
|
|
});
|
|
}
|
|
return this.createSubscriber(email, name, listIds, attribs);
|
|
}
|
|
}
|
|
exports.listmonkClient = new ListmonkClient();
|
|
//# sourceMappingURL=listmonk.client.js.map
|