@ecodrix/erix-api 1.2.7 → 1.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1 -1
- package/package.json +16 -19
- package/schema/openapi.yaml +0 -199
- package/src/cli.ts +0 -305
- package/src/core.ts +0 -359
- package/src/error.ts +0 -72
- package/src/index.ts +0 -34
- package/src/resource.ts +0 -93
- package/src/resources/crm/activities.ts +0 -122
- package/src/resources/crm/analytics.ts +0 -148
- package/src/resources/crm/automationDashboard.ts +0 -42
- package/src/resources/crm/automations.ts +0 -170
- package/src/resources/crm/index.ts +0 -34
- package/src/resources/crm/leads.ts +0 -458
- package/src/resources/crm/payments.ts +0 -27
- package/src/resources/crm/pipelines.ts +0 -197
- package/src/resources/crm/scoring.ts +0 -46
- package/src/resources/crm/sequences.ts +0 -51
- package/src/resources/email.ts +0 -142
- package/src/resources/events.ts +0 -189
- package/src/resources/health.ts +0 -84
- package/src/resources/logs.ts +0 -97
- package/src/resources/marketing.ts +0 -179
- package/src/resources/media.ts +0 -184
- package/src/resources/meet.ts +0 -163
- package/src/resources/notifications.ts +0 -178
- package/src/resources/queue.ts +0 -64
- package/src/resources/storage.ts +0 -101
- package/src/resources/webhooks.ts +0 -80
- package/src/resources/whatsapp/broadcasts.ts +0 -94
- package/src/resources/whatsapp/conversations.ts +0 -128
- package/src/resources/whatsapp/index.ts +0 -67
- package/src/resources/whatsapp/messages.ts +0 -239
- package/src/resources/whatsapp/templates.ts +0 -218
- package/src/types/metadata.ts +0 -71
package/src/cli.ts
DELETED
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
import repl from "node:repl";
|
|
2
|
-
import { Command } from "commander";
|
|
3
|
-
import dotenv from "dotenv";
|
|
4
|
-
import pc from "picocolors";
|
|
5
|
-
import { Ecodrix } from "./core";
|
|
6
|
-
|
|
7
|
-
// Setup environment
|
|
8
|
-
dotenv.config();
|
|
9
|
-
|
|
10
|
-
// Build-time constants injected by tsup
|
|
11
|
-
declare const process: any;
|
|
12
|
-
const VERSION = process.env.SDK_VERSION || "1.0.2";
|
|
13
|
-
const NAME = process.env.SDK_NAME || "@ecodrix/erix-api";
|
|
14
|
-
const DESCRIPTION = process.env.SDK_DESCRIPTION || "Official ECODrIx SDK CLI";
|
|
15
|
-
|
|
16
|
-
const program = new Command();
|
|
17
|
-
|
|
18
|
-
program.name("erix").description(DESCRIPTION).version(VERSION);
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Helper to initialize the Ecodrix client from environment or flags.
|
|
22
|
-
*/
|
|
23
|
-
function getClient(options: any): Ecodrix {
|
|
24
|
-
const apiKey = options.key || process.env.ECOD_API_KEY;
|
|
25
|
-
const clientCode = options.client || process.env.ECOD_CLIENT_CODE;
|
|
26
|
-
|
|
27
|
-
if (!apiKey) {
|
|
28
|
-
console.error(pc.red("Error: API Key is missing."));
|
|
29
|
-
console.log(pc.yellow("Set ECOD_API_KEY environment variable or use --key <key>"));
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return new Ecodrix({
|
|
34
|
-
apiKey,
|
|
35
|
-
clientCode,
|
|
36
|
-
baseUrl: options.baseUrl || process.env.ECOD_BASE_URL,
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
program
|
|
41
|
-
.option("-k, --key <key>", "ECODrIx API Key")
|
|
42
|
-
.option("-c, --client <code...", "Tenant Client Code")
|
|
43
|
-
.option("--base-url <url>", "API Base URL override");
|
|
44
|
-
|
|
45
|
-
// --- WHOAMI ---
|
|
46
|
-
program
|
|
47
|
-
.command("whoami")
|
|
48
|
-
.description("Verify current authentication and tenant status")
|
|
49
|
-
.action(async (options, command) => {
|
|
50
|
-
const globalOptions = command.parent.opts();
|
|
51
|
-
const ecod = getClient(globalOptions);
|
|
52
|
-
|
|
53
|
-
console.log(pc.cyan("Checking connection to ECODrIx Platform..."));
|
|
54
|
-
try {
|
|
55
|
-
const me = (await ecod.request("GET", "/api/saas/me/profile")) as any;
|
|
56
|
-
console.log(pc.green("✔ Authenticated successfully!"));
|
|
57
|
-
console.log(`${pc.bold("User ID:")} ${me.id}`);
|
|
58
|
-
console.log(`${pc.bold("Organisation:")} ${me.organisation?.name || "N/A"}`);
|
|
59
|
-
if (globalOptions.client) {
|
|
60
|
-
console.log(`${pc.bold("Tenant Code:")} ${pc.magenta(globalOptions.client)}`);
|
|
61
|
-
}
|
|
62
|
-
} catch (error: any) {
|
|
63
|
-
console.error(pc.red("✖ Authentication failed"));
|
|
64
|
-
console.error(pc.dim(error.message));
|
|
65
|
-
process.exit(1);
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// --- WHATSAPP ---
|
|
70
|
-
const whatsapp = program.command("whatsapp").description("WhatsApp Business API operations");
|
|
71
|
-
|
|
72
|
-
whatsapp
|
|
73
|
-
.command("send-template")
|
|
74
|
-
.description("Send a WhatsApp template message")
|
|
75
|
-
.argument("<phone>", "Recipient phone number")
|
|
76
|
-
.argument("<template>", "Template name")
|
|
77
|
-
.option("-v, --vars <json>", "Template variables as JSON string", "[]")
|
|
78
|
-
.action(async (phone, template, options, command) => {
|
|
79
|
-
const globalOptions = command.parent.parent.opts();
|
|
80
|
-
const ecod = getClient(globalOptions);
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
const vars = JSON.parse(options.vars);
|
|
84
|
-
console.log(pc.cyan(`Sending template '${template}' to ${phone}...`));
|
|
85
|
-
|
|
86
|
-
const result = (await ecod.whatsapp.messages.sendTemplate({
|
|
87
|
-
to: phone,
|
|
88
|
-
templateName: template,
|
|
89
|
-
language: "en_US",
|
|
90
|
-
variables: vars,
|
|
91
|
-
})) as any;
|
|
92
|
-
|
|
93
|
-
console.log(pc.green("✔ Message sent successfully!"));
|
|
94
|
-
console.log(pc.dim(`ID: ${result?.id || "N/A"}`));
|
|
95
|
-
} catch (error: any) {
|
|
96
|
-
console.error(pc.red("✖ Failed to send message"));
|
|
97
|
-
console.error(pc.dim(error.message));
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
// --- CRM ---
|
|
102
|
-
const crm = program.command("crm").description("CRM and Lead management");
|
|
103
|
-
|
|
104
|
-
crm
|
|
105
|
-
.command("leads")
|
|
106
|
-
.description("List recent leads")
|
|
107
|
-
.option("-l, --limit <number>", "Number of leads to fetch", "10")
|
|
108
|
-
.option("-s, --status <status>", "Filter by lead status")
|
|
109
|
-
.option("-p, --pipeline <id>", "Filter by pipeline ID")
|
|
110
|
-
.option("-q, --search <query>", "Search by name or email")
|
|
111
|
-
.action(async (options, command) => {
|
|
112
|
-
const globalOptions = command.parent.parent.opts();
|
|
113
|
-
const ecod = getClient(globalOptions);
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
const limit = Number.parseInt(options.limit);
|
|
117
|
-
console.log(pc.cyan(`Fetching last ${limit} leads...`));
|
|
118
|
-
|
|
119
|
-
const queryParams: any = { limit };
|
|
120
|
-
if (options.status) queryParams.status = options.status;
|
|
121
|
-
if (options.pipeline) queryParams.pipelineId = options.pipeline;
|
|
122
|
-
if (options.search) queryParams.search = options.search;
|
|
123
|
-
|
|
124
|
-
const response: any = await ecod.crm.leads.list(queryParams);
|
|
125
|
-
const leads = Array.isArray(response.data)
|
|
126
|
-
? response.data
|
|
127
|
-
: Array.isArray(response)
|
|
128
|
-
? response
|
|
129
|
-
: [];
|
|
130
|
-
|
|
131
|
-
if (leads.length === 0) {
|
|
132
|
-
console.log(pc.yellow("No leads found."));
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
console.table(
|
|
137
|
-
leads.map((l: any) => ({
|
|
138
|
-
ID: l.id || l._id,
|
|
139
|
-
Name: `${l.firstName} ${l.lastName || ""}`.trim(),
|
|
140
|
-
Phone: l.phone,
|
|
141
|
-
Status: l.status,
|
|
142
|
-
Score: l.score || 0,
|
|
143
|
-
Created: new Date(l.createdAt).toLocaleDateString(),
|
|
144
|
-
})),
|
|
145
|
-
);
|
|
146
|
-
} catch (error: any) {
|
|
147
|
-
console.error(pc.red("✖ Failed to fetch leads"));
|
|
148
|
-
console.error(pc.dim(error.message));
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
crm
|
|
153
|
-
.command("pipelines")
|
|
154
|
-
.description("List all CRM pipelines")
|
|
155
|
-
.action(async (options, command) => {
|
|
156
|
-
const globalOptions = command.parent.parent.opts();
|
|
157
|
-
const ecod = getClient(globalOptions);
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
console.log(pc.cyan("Fetching pipelines..."));
|
|
161
|
-
const response: any = await ecod.crm.pipelines.list();
|
|
162
|
-
const pipelines = response.data || response || [];
|
|
163
|
-
|
|
164
|
-
if (pipelines.length === 0) {
|
|
165
|
-
console.log(pc.yellow("No pipelines found."));
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
console.table(
|
|
170
|
-
pipelines.map((p: any) => ({
|
|
171
|
-
ID: p.id || p._id,
|
|
172
|
-
Name: p.name,
|
|
173
|
-
Default: p.isDefault ? "Yes" : "No",
|
|
174
|
-
Stages: p.stages?.length || 0,
|
|
175
|
-
})),
|
|
176
|
-
);
|
|
177
|
-
} catch (error: any) {
|
|
178
|
-
console.error(pc.red("✖ Failed to fetch pipelines"));
|
|
179
|
-
console.error(pc.dim(error.message));
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// --- ANALYTICS ---
|
|
184
|
-
const analytics = program.command("analytics").description("Business Intelligence Analytics");
|
|
185
|
-
|
|
186
|
-
analytics
|
|
187
|
-
.command("overview")
|
|
188
|
-
.description("Get high-level CRM KPIs")
|
|
189
|
-
.option("-r, --range <range>", "Date range (e.g., 24h, 7d, 30d, 365d)", "30d")
|
|
190
|
-
.action(async (options, command) => {
|
|
191
|
-
const globalOptions = command.parent.parent.opts();
|
|
192
|
-
const ecod = getClient(globalOptions);
|
|
193
|
-
|
|
194
|
-
try {
|
|
195
|
-
console.log(pc.cyan(`Fetching overview metrics for last ${options.range}...`));
|
|
196
|
-
const response: any = await ecod.crm.analytics.overview({ range: options.range });
|
|
197
|
-
const data = response.data || response;
|
|
198
|
-
|
|
199
|
-
console.log(`\n${pc.bold("OVERVIEW KPIs:")}`);
|
|
200
|
-
console.log(`Total Leads: ${pc.green(data.totalLeads || 0)}`);
|
|
201
|
-
console.log(`Open Value: ${pc.yellow(`$${(data.openValue || 0).toLocaleString()}`)}`);
|
|
202
|
-
console.log(`Won Revenue: ${pc.green(`$${(data.wonRevenue || 0).toLocaleString()}`)}`);
|
|
203
|
-
console.log(`Avg Score: ${pc.blue(data.avgScore?.toFixed(1) || 0)}`);
|
|
204
|
-
console.log(`Conversion: ${pc.magenta(`${(data.conversionRate || 0).toFixed(2)}%`)}\n`);
|
|
205
|
-
} catch (error: any) {
|
|
206
|
-
console.error(pc.red("✖ Failed to fetch analytics overview"));
|
|
207
|
-
console.error(pc.dim(error.message));
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
// --- WEBHOOKS ---
|
|
212
|
-
const webhooks = program.command("webhooks").description("Webhook utility tools");
|
|
213
|
-
|
|
214
|
-
webhooks
|
|
215
|
-
.command("verify")
|
|
216
|
-
.description("Verify a cryptographic webhook signature")
|
|
217
|
-
.argument("<payload>", "The raw request body string")
|
|
218
|
-
.argument("<signature>", "The 'x-ecodrix-signature' header value")
|
|
219
|
-
.argument("<secret>", "Your webhook signing secret")
|
|
220
|
-
.action(async (payload, signature, secret, options, command) => {
|
|
221
|
-
const globalOptions = command.parent.parent.opts();
|
|
222
|
-
const ecod = getClient(globalOptions);
|
|
223
|
-
|
|
224
|
-
try {
|
|
225
|
-
await ecod.webhooks.constructEvent(payload, signature, secret);
|
|
226
|
-
console.log(pc.green("✔ Signature is VALID"));
|
|
227
|
-
} catch (error: any) {
|
|
228
|
-
console.error(pc.red("✖ Error during verification"));
|
|
229
|
-
console.error(pc.dim(error.message));
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
// --- SHELL (REPL) ---
|
|
234
|
-
program
|
|
235
|
-
.command("shell")
|
|
236
|
-
.alias("repl")
|
|
237
|
-
.description("Start an interactive SDK shell")
|
|
238
|
-
.action(async (options, command) => {
|
|
239
|
-
const globalOptions = command.parent.opts();
|
|
240
|
-
const ecod = getClient(globalOptions);
|
|
241
|
-
|
|
242
|
-
console.log(pc.magenta(pc.bold("\nWelcome to the Erix Interactive Shell")));
|
|
243
|
-
console.log(pc.dim(`SDK Version: ${VERSION}`));
|
|
244
|
-
console.log(pc.dim("The 'ecod' client is pre-initialized and ready.\n"));
|
|
245
|
-
|
|
246
|
-
const r = repl.start({
|
|
247
|
-
prompt: pc.cyan("erix > "),
|
|
248
|
-
useColors: true,
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
// Load resources into REPL context for immediate access
|
|
252
|
-
r.context.ecod = ecod;
|
|
253
|
-
r.context.whatsapp = ecod.whatsapp;
|
|
254
|
-
r.context.crm = ecod.crm;
|
|
255
|
-
r.context.meet = ecod.meet;
|
|
256
|
-
r.context.media = ecod.media;
|
|
257
|
-
|
|
258
|
-
r.on("exit", () => {
|
|
259
|
-
console.log(pc.yellow("\nGoodbye!"));
|
|
260
|
-
process.exit(0);
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
// --- COMPLETION ---
|
|
265
|
-
program
|
|
266
|
-
.command("completion")
|
|
267
|
-
.description("Generate Bash auto-completion script")
|
|
268
|
-
.action(() => {
|
|
269
|
-
const script = `
|
|
270
|
-
# Erix Bash Completion
|
|
271
|
-
_erix_completions() {
|
|
272
|
-
local cur opts
|
|
273
|
-
COMPREPLY=()
|
|
274
|
-
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
275
|
-
opts="whoami whatsapp crm analytics webhooks shell completion"
|
|
276
|
-
|
|
277
|
-
if [[ \${COMP_CWORD} -eq 1 ]] ; then
|
|
278
|
-
COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
|
|
279
|
-
return 0
|
|
280
|
-
fi
|
|
281
|
-
|
|
282
|
-
# Simple sub-command completion
|
|
283
|
-
case "\${COMP_WORDS[1]}" in
|
|
284
|
-
whatsapp)
|
|
285
|
-
COMPREPLY=( $(compgen -W "send-template" -- \${cur}) )
|
|
286
|
-
;;
|
|
287
|
-
crm)
|
|
288
|
-
COMPREPLY=( $(compgen -W "leads pipelines" -- \${cur}) )
|
|
289
|
-
;;
|
|
290
|
-
analytics)
|
|
291
|
-
COMPREPLY=( $(compgen -W "overview" -- \${cur}) )
|
|
292
|
-
;;
|
|
293
|
-
webhooks)
|
|
294
|
-
COMPREPLY=( $(compgen -W "verify" -- \${cur}) )
|
|
295
|
-
;;
|
|
296
|
-
esac
|
|
297
|
-
}
|
|
298
|
-
complete -F _erix_completions erix
|
|
299
|
-
`;
|
|
300
|
-
console.log(script.trim());
|
|
301
|
-
console.error(pc.yellow('\n# To enable, run: eval "$(erix completion)"'));
|
|
302
|
-
console.error(pc.dim("# Or add it to your ~/.bashrc: erix completion >> ~/.bashrc"));
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
program.parse();
|
package/src/core.ts
DELETED
|
@@ -1,359 +0,0 @@
|
|
|
1
|
-
import axios, { type AxiosInstance, type Method } from "axios";
|
|
2
|
-
import axiosRetry from "axios-retry";
|
|
3
|
-
import { type Socket, io } from "socket.io-client";
|
|
4
|
-
import { APIError, AuthenticationError } from "./error";
|
|
5
|
-
import { CRM } from "./resources/crm";
|
|
6
|
-
import { Email } from "./resources/email";
|
|
7
|
-
import { EventsResource } from "./resources/events";
|
|
8
|
-
import { Health } from "./resources/health";
|
|
9
|
-
import { Logs } from "./resources/logs";
|
|
10
|
-
import { Marketing } from "./resources/marketing";
|
|
11
|
-
import { Media } from "./resources/media";
|
|
12
|
-
import { Meetings } from "./resources/meet";
|
|
13
|
-
import { Notifications } from "./resources/notifications";
|
|
14
|
-
import { Queue } from "./resources/queue";
|
|
15
|
-
import { Storage } from "./resources/storage";
|
|
16
|
-
import { Webhooks } from "./resources/webhooks";
|
|
17
|
-
import { WhatsApp } from "./resources/whatsapp";
|
|
18
|
-
|
|
19
|
-
declare const process: any;
|
|
20
|
-
|
|
21
|
-
/** @internal The canonical ECODrIx backend URL — not exposed to SDK consumers. */
|
|
22
|
-
const ECOD_API_BASE = "https://api.ecodrix.com";
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Configuration options for the Ecodrix client.
|
|
26
|
-
*
|
|
27
|
-
* Minimum required: `apiKey` and `clientCode`.
|
|
28
|
-
* The backend URL is managed internally and should not need to be changed.
|
|
29
|
-
*/
|
|
30
|
-
export interface EcodrixOptions {
|
|
31
|
-
/**
|
|
32
|
-
* Your ECODrIx Platform API key.
|
|
33
|
-
* Obtain this from the ECODrIx dashboard under Settings → API Keys.
|
|
34
|
-
* @example "ecod_live_sk_..."
|
|
35
|
-
*/
|
|
36
|
-
apiKey: string;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Your tenant ID (Client Code).
|
|
40
|
-
* This scopes all API requests to your specific organisation.
|
|
41
|
-
* @example "ERIX_CLNT_JHBJHF"
|
|
42
|
-
*/
|
|
43
|
-
clientCode?: string;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* @internal Override Socket.io URL for testing/staging.
|
|
47
|
-
* Not documented — internal use only.
|
|
48
|
-
*/
|
|
49
|
-
socketUrl?: string;
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* @internal Override the backend API URL. Used by the CLI and internal tests only.
|
|
53
|
-
* Host projects must never set this — the production URL is hardcoded internally.
|
|
54
|
-
*/
|
|
55
|
-
baseUrl?: string;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* The primary entry point for the ECODrIx SDK.
|
|
60
|
-
*
|
|
61
|
-
* Initialise once with your credentials and use the namespaced resources
|
|
62
|
-
* to interact with every part of the platform.
|
|
63
|
-
*
|
|
64
|
-
* @example
|
|
65
|
-
* ```typescript
|
|
66
|
-
* import { Ecodrix } from "@ecodrix/erix-api";
|
|
67
|
-
*
|
|
68
|
-
* const ecod = new Ecodrix({
|
|
69
|
-
* apiKey: process.env.ECOD_API_KEY!,
|
|
70
|
-
* clientCode: "ERIX_CLNT_JHBJHF",
|
|
71
|
-
* });
|
|
72
|
-
*
|
|
73
|
-
* await ecod.whatsapp.messages.send({ to: "+91...", text: "Hello!" });
|
|
74
|
-
* const lead = await ecod.crm.leads.create({ firstName: "Alice", phone: "+91..." });
|
|
75
|
-
* ```
|
|
76
|
-
*/
|
|
77
|
-
export class Ecodrix {
|
|
78
|
-
/**
|
|
79
|
-
* @internal Axios HTTP client for making API requests.
|
|
80
|
-
*/
|
|
81
|
-
private readonly client: AxiosInstance;
|
|
82
|
-
/**
|
|
83
|
-
* @internal Socket.io client for real-time events.
|
|
84
|
-
*/
|
|
85
|
-
private readonly socket: Socket;
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* The tenant client code this SDK instance is scoped to.
|
|
89
|
-
* Useful for components that need to read the clientCode back
|
|
90
|
-
* from a context-provided SDK instance.
|
|
91
|
-
*/
|
|
92
|
-
public readonly clientCode: string | undefined;
|
|
93
|
-
|
|
94
|
-
/** WhatsApp messaging and conversation management. */
|
|
95
|
-
public readonly whatsapp: WhatsApp;
|
|
96
|
-
|
|
97
|
-
/** CRM resources — Leads and related sub-resources. */
|
|
98
|
-
public readonly crm: CRM;
|
|
99
|
-
|
|
100
|
-
/** Cloudflare R2-backed media storage. */
|
|
101
|
-
public readonly media: Media;
|
|
102
|
-
|
|
103
|
-
/** Google Meet appointment scheduling. */
|
|
104
|
-
public readonly meet: Meetings;
|
|
105
|
-
|
|
106
|
-
/** Automation execution logs and provider webhook callbacks. */
|
|
107
|
-
public readonly notifications: Notifications;
|
|
108
|
-
|
|
109
|
-
/** Outbound email marketing engine and template management. */
|
|
110
|
-
public readonly email: Email;
|
|
111
|
-
|
|
112
|
-
/** Platform-wide execution logs and audit trails. */
|
|
113
|
-
public readonly logs: Logs;
|
|
114
|
-
|
|
115
|
-
/** Lead events and workflow automation triggers. */
|
|
116
|
-
public readonly events: EventsResource;
|
|
117
|
-
|
|
118
|
-
/** Cryptographic webhook signature verification. */
|
|
119
|
-
public readonly webhooks: Webhooks;
|
|
120
|
-
|
|
121
|
-
/** Tenant Cloud Storage mapping. */
|
|
122
|
-
public readonly storage: Storage;
|
|
123
|
-
|
|
124
|
-
/** Email and SMS Marketing Campaigns. */
|
|
125
|
-
public readonly marketing: Marketing;
|
|
126
|
-
|
|
127
|
-
/** Platform and tenant health diagnostics. */
|
|
128
|
-
public readonly health: Health;
|
|
129
|
-
|
|
130
|
-
/** Background job queue management. */
|
|
131
|
-
public readonly queue: Queue;
|
|
132
|
-
|
|
133
|
-
constructor(options: EcodrixOptions) {
|
|
134
|
-
if (!options.apiKey) {
|
|
135
|
-
throw new AuthenticationError("API Key is required");
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
this.clientCode = options.clientCode?.toUpperCase();
|
|
139
|
-
|
|
140
|
-
// @internal: options.baseUrl is available for CLI/test use only.
|
|
141
|
-
// Host projects hardcode to prod — they never set baseUrl.
|
|
142
|
-
const baseUrl = options.baseUrl ?? ECOD_API_BASE;
|
|
143
|
-
const socketUrl = options.socketUrl || baseUrl;
|
|
144
|
-
|
|
145
|
-
const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined";
|
|
146
|
-
const runtime = isBrowser
|
|
147
|
-
? "browser"
|
|
148
|
-
: typeof process !== "undefined"
|
|
149
|
-
? `node ${process.version}`
|
|
150
|
-
: "unknown";
|
|
151
|
-
const os = isBrowser
|
|
152
|
-
? globalThis.navigator?.userAgent || "browser"
|
|
153
|
-
: typeof process !== "undefined"
|
|
154
|
-
? process.platform
|
|
155
|
-
: "unknown";
|
|
156
|
-
|
|
157
|
-
this.client = axios.create({
|
|
158
|
-
baseURL: baseUrl,
|
|
159
|
-
headers: {
|
|
160
|
-
"x-api-key": options.apiKey,
|
|
161
|
-
"x-client-code": options.clientCode?.toUpperCase(),
|
|
162
|
-
"Content-Type": "application/json",
|
|
163
|
-
"x-ecodrix-client-agent": JSON.stringify({
|
|
164
|
-
sdk_version: "1.0.0", // Can be auto-injected during build in future
|
|
165
|
-
runtime,
|
|
166
|
-
os,
|
|
167
|
-
}),
|
|
168
|
-
},
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
// Make the client completely bulletproof for execution from external projects.
|
|
172
|
-
// It will automatically handle network blips, 502 Bad Gateways, and 429 Rate Limits.
|
|
173
|
-
axiosRetry(this.client, {
|
|
174
|
-
retries: 3,
|
|
175
|
-
retryDelay: axiosRetry.exponentialDelay,
|
|
176
|
-
retryCondition: (error) => {
|
|
177
|
-
return (
|
|
178
|
-
axiosRetry.isNetworkOrIdempotentRequestError(error) || error.response?.status === 429
|
|
179
|
-
);
|
|
180
|
-
},
|
|
181
|
-
onRetry: (retryCount, error, requestConfig) => {
|
|
182
|
-
const isDev = typeof process !== "undefined" && process.env?.NODE_ENV === "development";
|
|
183
|
-
if (isDev) {
|
|
184
|
-
console.warn(
|
|
185
|
-
`[ECODrIx SDK] Retrying request (${retryCount}/3): ${requestConfig.method?.toUpperCase()} ${requestConfig.url}. Reason: ${error.message}`,
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
},
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
// Initialise resources
|
|
192
|
-
this.whatsapp = new WhatsApp(this.client);
|
|
193
|
-
this.crm = new CRM(this.client);
|
|
194
|
-
this.media = new Media(this.client);
|
|
195
|
-
this.meet = new Meetings(this.client);
|
|
196
|
-
this.notifications = new Notifications(this.client);
|
|
197
|
-
this.email = new Email(this.client);
|
|
198
|
-
this.logs = new Logs(this.client);
|
|
199
|
-
this.events = new EventsResource(this.client);
|
|
200
|
-
this.webhooks = new Webhooks();
|
|
201
|
-
this.storage = new Storage(this.client);
|
|
202
|
-
this.marketing = new Marketing(this.client);
|
|
203
|
-
this.health = new Health(this.client);
|
|
204
|
-
this.queue = new Queue(this.client);
|
|
205
|
-
|
|
206
|
-
// Establish persistent Socket.io connection
|
|
207
|
-
this.socket = io(socketUrl, {
|
|
208
|
-
extraHeaders: {
|
|
209
|
-
"x-api-key": options.apiKey,
|
|
210
|
-
"x-client-code": options.clientCode?.toUpperCase() || "",
|
|
211
|
-
},
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
this.setupSocket(options.clientCode);
|
|
215
|
-
|
|
216
|
-
// ─── Wappalyzer & Technology Detection ─────────────────────────────────────
|
|
217
|
-
if (isBrowser) {
|
|
218
|
-
const footprint = {
|
|
219
|
-
version: "1.2.2",
|
|
220
|
-
clientCode: options.clientCode,
|
|
221
|
-
initializedAt: new Date().toISOString(),
|
|
222
|
-
};
|
|
223
|
-
// Standard identifying global
|
|
224
|
-
(window as any).__ECODRIX_SDK__ = footprint;
|
|
225
|
-
// Ergonomic access for developers (as attempted by the user)
|
|
226
|
-
if (!(window as any).ecodrix) {
|
|
227
|
-
(window as any).ecodrix = this;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Join a specific real-time room (e.g., a conversation or a lead).
|
|
234
|
-
*
|
|
235
|
-
* @param roomId - The unique identifier for the room.
|
|
236
|
-
*/
|
|
237
|
-
public joinRoom(roomId: string) {
|
|
238
|
-
this.socket.emit("join-room", roomId);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Leave a previously joined real-time room.
|
|
243
|
-
*
|
|
244
|
-
* @param roomId - The unique identifier for the room.
|
|
245
|
-
*/
|
|
246
|
-
public leaveRoom(roomId: string) {
|
|
247
|
-
this.socket.emit("leave-room", roomId);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
private setupSocket(clientCode?: string) {
|
|
251
|
-
this.socket.on("connect", () => {
|
|
252
|
-
if (clientCode) {
|
|
253
|
-
// Join the tenant-scoped room to receive only relevant events.
|
|
254
|
-
this.socket.emit("join-room", clientCode.toUpperCase());
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Subscribe to a real-time event emitted by the ECODrIx platform.
|
|
261
|
-
*
|
|
262
|
-
* The SDK maintains a persistent Socket.io connection. Events are
|
|
263
|
-
* scoped to your `clientCode` — you will only receive events for
|
|
264
|
-
* your own tenant.
|
|
265
|
-
*
|
|
266
|
-
* **Standard events:**
|
|
267
|
-
* - `new_message` — inbound WhatsApp message (includes conversation and message payload)
|
|
268
|
-
* - `message_sent` — outbound message successfully sent
|
|
269
|
-
* - `message_status_update` — WhatsApp message status change (delivered, read, failed)
|
|
270
|
-
* - `conversation_updated` — metadata change (unread count, last message, status)
|
|
271
|
-
* - `message_updated` — real-time updates for reactions or media processing
|
|
272
|
-
* - `notification:new` — new system or CRM notification
|
|
273
|
-
* - `workflow-run-update` — automation execution progress
|
|
274
|
-
* - `meet.scheduled` — Google Meet appointment booked
|
|
275
|
-
*
|
|
276
|
-
* @param event - The event name to subscribe to.
|
|
277
|
-
* @param callback - The handler function invoked when the event fires.
|
|
278
|
-
* @returns `this` for method chaining.
|
|
279
|
-
*
|
|
280
|
-
* @example
|
|
281
|
-
* ```typescript
|
|
282
|
-
* ecod
|
|
283
|
-
* .on("whatsapp.message_received", (msg) => console.log(msg.body))
|
|
284
|
-
* .on("automation.failed", (err) => alertTeam(err));
|
|
285
|
-
* ```
|
|
286
|
-
*/
|
|
287
|
-
public on(event: string, callback: (...args: any[]) => void): this {
|
|
288
|
-
this.socket.on(event, callback);
|
|
289
|
-
return this;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Gracefully disconnect the real-time Socket.io connection.
|
|
294
|
-
* Call this when shutting down your server or when the client is
|
|
295
|
-
* no longer needed to free up resources.
|
|
296
|
-
*
|
|
297
|
-
* @example
|
|
298
|
-
* ```typescript
|
|
299
|
-
* process.on("SIGTERM", () => ecod.disconnect());
|
|
300
|
-
* ```
|
|
301
|
-
*/
|
|
302
|
-
public disconnect() {
|
|
303
|
-
this.socket.disconnect();
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* Remove a previously registered event listener.
|
|
308
|
-
* Always call this in cleanup (e.g. React `useEffect` return) to prevent
|
|
309
|
-
* memory leaks and duplicate handlers after reconnections.
|
|
310
|
-
*
|
|
311
|
-
* @param event - The event name to unsubscribe from.
|
|
312
|
-
* @param callback - The exact handler reference passed to `.on()`.
|
|
313
|
-
* @returns `this` for method chaining.
|
|
314
|
-
*/
|
|
315
|
-
public off(event: string, callback: (...args: any[]) => void): this {
|
|
316
|
-
this.socket.off(event, callback);
|
|
317
|
-
return this;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Raw Execution Escape-Hatch.
|
|
322
|
-
* Send an authenticated HTTP request directly to the ECODrIx backend from ANY external project.
|
|
323
|
-
*
|
|
324
|
-
* This is extremely powerful giving you full unrestricted access to make calls
|
|
325
|
-
* against new, experimental, or completely custom backend APIs while still benefitting
|
|
326
|
-
* from the SDK's built-in authentication and automatic `axios-retry` logic.
|
|
327
|
-
*
|
|
328
|
-
* @example
|
|
329
|
-
* ```typescript
|
|
330
|
-
* const { data } = await ecod.request("POST", "/api/saas/experimental-feature", { flag: true });
|
|
331
|
-
* console.log(data);
|
|
332
|
-
* ```
|
|
333
|
-
*/
|
|
334
|
-
public async request<T = any>(
|
|
335
|
-
method: Method,
|
|
336
|
-
path: string,
|
|
337
|
-
data?: any,
|
|
338
|
-
params?: any,
|
|
339
|
-
): Promise<T> {
|
|
340
|
-
try {
|
|
341
|
-
const response = await this.client.request<T>({
|
|
342
|
-
method,
|
|
343
|
-
url: path,
|
|
344
|
-
data,
|
|
345
|
-
params,
|
|
346
|
-
});
|
|
347
|
-
return response.data;
|
|
348
|
-
} catch (error: any) {
|
|
349
|
-
if (error.response) {
|
|
350
|
-
throw new APIError(
|
|
351
|
-
error.response.data?.message || error.response.data?.error || "Raw Execution Failed",
|
|
352
|
-
error.response.status,
|
|
353
|
-
error.response.data?.code,
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
throw new APIError(error.message || "Network Error");
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|