@softtynet/zplus-worker 1.0.0

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.
Files changed (36) hide show
  1. package/README.md +136 -0
  2. package/dist/automation/HeadlessAutomationEngine.d.ts +12 -0
  3. package/dist/automation/HeadlessAutomationEngine.js +34 -0
  4. package/dist/channels/ChannelRunnerRegistry.d.ts +14 -0
  5. package/dist/channels/ChannelRunnerRegistry.js +65 -0
  6. package/dist/channels/IChannelRunner.d.ts +10 -0
  7. package/dist/channels/IChannelRunner.js +2 -0
  8. package/dist/channels/index.d.ts +6 -0
  9. package/dist/channels/index.js +22 -0
  10. package/dist/channels/runners/FacebookChannelRunner.d.ts +19 -0
  11. package/dist/channels/runners/FacebookChannelRunner.js +47 -0
  12. package/dist/channels/runners/TelegramChannelRunner.d.ts +19 -0
  13. package/dist/channels/runners/TelegramChannelRunner.js +47 -0
  14. package/dist/channels/runners/WhatsAppChannelRunner.d.ts +19 -0
  15. package/dist/channels/runners/WhatsAppChannelRunner.js +47 -0
  16. package/dist/channels/runners/ZaloChannelRunner.d.ts +22 -0
  17. package/dist/channels/runners/ZaloChannelRunner.js +50 -0
  18. package/dist/cli.d.ts +6 -0
  19. package/dist/cli.js +1409 -0
  20. package/dist/config/WorkerConfig.d.ts +16 -0
  21. package/dist/config/WorkerConfig.js +21 -0
  22. package/dist/engine/WorkerEngine.d.ts +38 -0
  23. package/dist/engine/WorkerEngine.js +157 -0
  24. package/dist/index.d.ts +9 -0
  25. package/dist/index.js +25 -0
  26. package/dist/lock/DistributedLockManager.d.ts +40 -0
  27. package/dist/lock/DistributedLockManager.js +113 -0
  28. package/dist/notifications/PushNotificationDispatcher.d.ts +14 -0
  29. package/dist/notifications/PushNotificationDispatcher.js +31 -0
  30. package/dist/server/WorkerHttpServer.d.ts +25 -0
  31. package/dist/server/WorkerHttpServer.js +205 -0
  32. package/dist/sync/CloudPendingMessageQueue.d.ts +27 -0
  33. package/dist/sync/CloudPendingMessageQueue.js +69 -0
  34. package/dist/sync/CloudSyncWorker.d.ts +19 -0
  35. package/dist/sync/CloudSyncWorker.js +53 -0
  36. package/package.json +32 -0
package/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # @softtynet/zplus-worker
2
+
3
+ ZPlus 24/7 Headless Omnichannel Engine & Automation Worker.
4
+
5
+ ---
6
+
7
+ ## 🌟 Tổng Quan (Overview)
8
+
9
+ `@softtynet/zplus-worker` là tiến trình chạy nền độc lập 24/7 cho hệ sinh thái ZPlus Enterprise, chịu trách nhiệm:
10
+
11
+ - **Active-Passive Redundancy & Master Preemption (< 1s)**: Hoạt động song song với ZPlus Desktop Client, tự động đảm nhận điều khiển khi Desktop tắt và ngay lập tức nhường quyền khi Desktop mở lại.
12
+ - **Duy trì kết nối WSS / Webhook liên tục**: Giữ phiên hoạt động cho các tài khoản đa kênh (**Zalo, Facebook Messenger, Telegram, WhatsApp**) thông qua proxy dân cư độc lập (Resident Proxy).
13
+ - **Thực thi kịch bản tự động hóa 24/7**: Phản hồi tin nhắn tự động ban đêm (AI Auto-Reply), phân luồng CSKH, gắn thẻ hội thoại ngay cả khi người dùng tắt máy tính cá nhân.
14
+ - **Delta Sync & Message Staging**: Lưu trữ hàng đợi tin nhắn đêm và đồng bộ lại vào cơ sở dữ liệu cục bộ khi ZPlus Desktop khởi động.
15
+
16
+ ---
17
+
18
+ ## 🚀 Các Phương Thức Triển Khai (Deployment Options)
19
+
20
+ ### Cách 1: Chạy trực tiếp qua NPM / NPX (Zero Config, Siêu Nhẹ)
21
+
22
+ Không cần cài đặt Docker nặng nề, có thể chạy trên bất kỳ VPS, Raspberry Pi, homelab hoặc máy chủ nào có Node.js >= 18:
23
+
24
+ ```bash
25
+ # 1. Chạy tức thì không cần cài đặt:
26
+ npx @softtynet/zplus-worker
27
+
28
+ # 2. Hoặc cài đặt global trên VPS / Server:
29
+ npm install -g @softtynet/zplus-worker
30
+ zplus-worker
31
+
32
+ # 3. Chạy nền quản lý tiến trình với PM2 (Khuyên dùng trên VPS Linux):
33
+ npm install -g pm2
34
+ pm2 start zplus-worker --name "zplus-worker" --time
35
+ pm2 save && pm2 startup
36
+ ```
37
+
38
+ ---
39
+
40
+ ### Cách 2: Triển khai bằng Docker & Docker Compose
41
+
42
+ Docker Image được build đa kiến trúc (`linux/amd64`, `linux/arm64`) và lưu trữ chính thức trên GitHub Container Registry (`ghcr.io/softtynet/zplus-worker:latest`):
43
+
44
+ #### A. Chạy nhanh 1 lệnh:
45
+
46
+ ```bash
47
+ docker run -d \
48
+ --name zplus-worker \
49
+ --restart always \
50
+ -p 8080:8080 \
51
+ -e WORKER_ID=vps_singapore_01 \
52
+ -e DESKTOP_SECRET=your_secure_secret \
53
+ ghcr.io/softtynet/zplus-worker:latest
54
+ ```
55
+
56
+ #### B. Triển khai trọn gói với Docker Compose (kèm Redis Distributed Lock):
57
+
58
+ ```bash
59
+ cd apps/worker
60
+ docker compose up -d
61
+ ```
62
+
63
+ File `docker-compose.yml` mẫu:
64
+ ```yaml
65
+ version: "3.8"
66
+
67
+ services:
68
+ zplus-worker:
69
+ image: ghcr.io/softtynet/zplus-worker:latest
70
+ container_name: zplus-worker
71
+ restart: always
72
+ ports:
73
+ - "8080:8080"
74
+ environment:
75
+ - NODE_ENV=production
76
+ - PORT=8080
77
+ - WORKER_ID=vps_node_1
78
+ - DESKTOP_SECRET=your_secure_secret
79
+ - REDIS_URL=redis://redis:6379
80
+ - DATA_DIR=/data
81
+ volumes:
82
+ - worker_data:/data
83
+ depends_on:
84
+ - redis
85
+
86
+ redis:
87
+ image: redis:7-alpine
88
+ container_name: zplus-worker-redis
89
+ restart: always
90
+ command: redis-server --appendonly yes
91
+ volumes:
92
+ - redis_data:/data
93
+
94
+ volumes:
95
+ worker_data:
96
+ redis_data:
97
+ ```
98
+
99
+ ---
100
+
101
+ ## ⚙️ Biến Môi Trường (Environment Variables)
102
+
103
+ | Biến | Mặc định | Mô tả |
104
+ | :--- | :--- | :--- |
105
+ | `WORKER_ID` | `worker_<pid>` | Tên định danh duy nhất của Worker Node |
106
+ | `PORT` | `8080` | Port HTTP Ingress & Healthcheck |
107
+ | `DESKTOP_SECRET` | `zplus_shared_secret_token` | Khóa xác thực an toàn giữa Desktop và Worker |
108
+ | `REDIS_URL` | `undefined` | URL Redis cho Distributed Lock (Tùy chọn) |
109
+ | `DATA_DIR` | `./data` | Thư mục lưu trữ database SQLite và session cache |
110
+ | `HEARTBEAT_INTERVAL_MS` | `30000` | Chu kỳ gửi Heartbeat (ms) |
111
+
112
+ ---
113
+
114
+ ## 🚢 Tự Động Hóa Release qua GitHub Actions & GitHub CLI (`gh`)
115
+
116
+ Hệ thống đã cấu hình 2 luồng CI/CD GitHub Actions:
117
+ 1. `.github/workflows/publish-worker-npm.yml` - Tự động test, bundle và publish lên NPM Registry.
118
+ 2. `.github/workflows/publish-worker-docker.yml` - Tự động build multi-arch và push lên GitHub Container Registry (`ghcr.io`).
119
+
120
+ ### Cách kích hoạt Release:
121
+
122
+ #### Cách A: Bằng GitHub CLI (`gh`)
123
+ ```bash
124
+ # 1. Kích hoạt publish NPM:
125
+ gh workflow run publish-worker-npm.yml
126
+
127
+ # 2. Kích hoạt build Docker Image lên GHCR:
128
+ gh workflow run publish-worker-docker.yml
129
+ ```
130
+
131
+ #### Cách B: Bằng Git Tag
132
+ ```bash
133
+ git tag worker-v1.0.0
134
+ git push origin worker-v1.0.0
135
+ ```
136
+ *(Cả 2 workflow NPM và Docker sẽ tự động kích hoạt song song khi tag `worker-v*` được đẩy lên)*.
@@ -0,0 +1,12 @@
1
+ import { ILogger } from "@zplus/core";
2
+ import { AutomationRule, DripCampaign, UnifiedMessage } from "@zplus/contracts";
3
+ export declare class HeadlessAutomationEngine {
4
+ private readonly logger;
5
+ private readonly campaigns;
6
+ private readonly rules;
7
+ constructor(logger?: ILogger);
8
+ registerCampaign(campaign: DripCampaign): void;
9
+ registerRule(rule: AutomationRule): void;
10
+ evaluateIncomingMessage(message: UnifiedMessage): Promise<string | null>;
11
+ clear(): void;
12
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HeadlessAutomationEngine = void 0;
4
+ const core_1 = require("@zplus/core");
5
+ class HeadlessAutomationEngine {
6
+ logger;
7
+ campaigns = new Map();
8
+ rules = new Map();
9
+ constructor(logger) {
10
+ this.logger = logger || (0, core_1.createLogger)("HeadlessAutomationEngine");
11
+ }
12
+ registerCampaign(campaign) {
13
+ this.campaigns.set(campaign.id, campaign);
14
+ this.logger.info(`Loaded campaign ${campaign.id} (${campaign.name})`);
15
+ }
16
+ registerRule(rule) {
17
+ this.rules.set(rule.id, rule);
18
+ this.logger.info(`Loaded automation rule ${rule.id} (${rule.name})`);
19
+ }
20
+ async evaluateIncomingMessage(message) {
21
+ if (message.senderType === "customer") {
22
+ this.logger.debug(`Evaluating automation triggers for incoming customer message ${message.id}`);
23
+ // Kiểm tra các kịch bản hoặc trả về câu trực đêm mặc định
24
+ const defaultNightReply = "Cảm ơn bạn đã nhắn tin. Hiện tại đang trong khung giờ trực đêm, tin nhắn của bạn đã được ghi nhận và nhân viên sẽ hỗ trợ ngay khi mở ca làm việc!";
25
+ return defaultNightReply;
26
+ }
27
+ return null;
28
+ }
29
+ clear() {
30
+ this.campaigns.clear();
31
+ this.rules.clear();
32
+ }
33
+ }
34
+ exports.HeadlessAutomationEngine = HeadlessAutomationEngine;
@@ -0,0 +1,14 @@
1
+ import { IChannelRunner } from "./IChannelRunner";
2
+ import { ILogger } from "@zplus/core";
3
+ export declare class ChannelRunnerRegistry {
4
+ private readonly runners;
5
+ private readonly logger;
6
+ constructor(logger?: ILogger);
7
+ register(runner: IChannelRunner): void;
8
+ unregister(platform: string, accountId: string): boolean;
9
+ get(platform: string, accountId: string): IChannelRunner | undefined;
10
+ getAll(): IChannelRunner[];
11
+ getCount(): number;
12
+ startAll(): Promise<void>;
13
+ stopAll(): Promise<void>;
14
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ChannelRunnerRegistry = void 0;
4
+ const core_1 = require("@zplus/core");
5
+ class ChannelRunnerRegistry {
6
+ runners = new Map();
7
+ logger;
8
+ constructor(logger) {
9
+ this.logger = logger || (0, core_1.createLogger)("ChannelRunnerRegistry");
10
+ }
11
+ register(runner) {
12
+ const key = `${runner.platform}:${runner.accountId}`;
13
+ if (this.runners.has(key)) {
14
+ this.logger.warn(`Overwriting existing runner for ${key}`);
15
+ }
16
+ this.runners.set(key, runner);
17
+ this.logger.info(`Registered runner for ${key}`);
18
+ }
19
+ unregister(platform, accountId) {
20
+ const key = `${platform}:${accountId}`;
21
+ const runner = this.runners.get(key);
22
+ if (runner) {
23
+ runner.stop().catch((err) => {
24
+ this.logger.error(`Error stopping runner ${key} during unregister`, err);
25
+ });
26
+ this.runners.delete(key);
27
+ this.logger.info(`Unregistered runner for ${key}`);
28
+ return true;
29
+ }
30
+ return false;
31
+ }
32
+ get(platform, accountId) {
33
+ return this.runners.get(`${platform}:${accountId}`);
34
+ }
35
+ getAll() {
36
+ return Array.from(this.runners.values());
37
+ }
38
+ getCount() {
39
+ return this.runners.size;
40
+ }
41
+ async startAll() {
42
+ this.logger.info(`Starting all ${this.runners.size} registered channel runners...`);
43
+ for (const [key, runner] of this.runners.entries()) {
44
+ try {
45
+ await runner.start();
46
+ }
47
+ catch (err) {
48
+ this.logger.error(`Failed to start runner ${key}:`, err);
49
+ }
50
+ }
51
+ }
52
+ async stopAll() {
53
+ this.logger.info(`Stopping all ${this.runners.size} channel runners...`);
54
+ for (const [key, runner] of this.runners.entries()) {
55
+ try {
56
+ await runner.stop();
57
+ }
58
+ catch (err) {
59
+ this.logger.error(`Failed to stop runner ${key}:`, err);
60
+ }
61
+ }
62
+ this.runners.clear();
63
+ }
64
+ }
65
+ exports.ChannelRunnerRegistry = ChannelRunnerRegistry;
@@ -0,0 +1,10 @@
1
+ import { AccountStatus, PlatformType, UnifiedMessage } from "@zplus/contracts";
2
+ export interface IChannelRunner {
3
+ readonly accountId: string;
4
+ readonly platform: PlatformType;
5
+ start(): Promise<void>;
6
+ stop(): Promise<void>;
7
+ getStatus(): AccountStatus;
8
+ sendMessage(message: UnifiedMessage): Promise<boolean>;
9
+ onMessage?(handler: (message: UnifiedMessage) => void): void;
10
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,6 @@
1
+ export * from "./IChannelRunner";
2
+ export * from "./ChannelRunnerRegistry";
3
+ export * from "./runners/ZaloChannelRunner";
4
+ export * from "./runners/FacebookChannelRunner";
5
+ export * from "./runners/TelegramChannelRunner";
6
+ export * from "./runners/WhatsAppChannelRunner";
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./IChannelRunner"), exports);
18
+ __exportStar(require("./ChannelRunnerRegistry"), exports);
19
+ __exportStar(require("./runners/ZaloChannelRunner"), exports);
20
+ __exportStar(require("./runners/FacebookChannelRunner"), exports);
21
+ __exportStar(require("./runners/TelegramChannelRunner"), exports);
22
+ __exportStar(require("./runners/WhatsAppChannelRunner"), exports);
@@ -0,0 +1,19 @@
1
+ import { IChannelRunner } from "../IChannelRunner";
2
+ import { AccountStatus, PlatformType, UnifiedMessage } from "@zplus/contracts";
3
+ import { ILogger } from "@zplus/core";
4
+ export declare class FacebookChannelRunner implements IChannelRunner {
5
+ readonly accountId: string;
6
+ private readonly sessionData?;
7
+ private readonly proxyData?;
8
+ readonly platform: PlatformType;
9
+ private status;
10
+ private readonly logger;
11
+ private messageHandler?;
12
+ constructor(accountId: string, sessionData?: Record<string, any> | undefined, proxyData?: Record<string, any> | undefined, logger?: ILogger);
13
+ onMessage(handler: (message: UnifiedMessage) => void): void;
14
+ start(): Promise<void>;
15
+ stop(): Promise<void>;
16
+ getStatus(): AccountStatus;
17
+ sendMessage(message: UnifiedMessage): Promise<boolean>;
18
+ receiveIncomingMessage(message: UnifiedMessage): void;
19
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FacebookChannelRunner = void 0;
4
+ const core_1 = require("@zplus/core");
5
+ class FacebookChannelRunner {
6
+ accountId;
7
+ sessionData;
8
+ proxyData;
9
+ platform = "facebook";
10
+ status = "offline";
11
+ logger;
12
+ messageHandler;
13
+ constructor(accountId, sessionData, proxyData, logger) {
14
+ this.accountId = accountId;
15
+ this.sessionData = sessionData;
16
+ this.proxyData = proxyData;
17
+ this.logger = logger || (0, core_1.createLogger)(`FacebookRunner:${accountId}`);
18
+ }
19
+ onMessage(handler) {
20
+ this.messageHandler = handler;
21
+ }
22
+ async start() {
23
+ const proxyInfo = this.proxyData
24
+ ? ` via resident proxy ${this.proxyData.host || this.proxyData.ip || "configured"}`
25
+ : " (direct)";
26
+ this.logger.info(`Starting 24/7 headless Facebook runner for account ${this.accountId}${proxyInfo}...`);
27
+ this.status = "active";
28
+ }
29
+ async stop() {
30
+ this.logger.info(`Stopping headless Facebook runner for account ${this.accountId}...`);
31
+ this.status = "offline";
32
+ }
33
+ getStatus() {
34
+ return this.status;
35
+ }
36
+ async sendMessage(message) {
37
+ this.logger.info(`Headless Facebook runner sending message ${message.id} to thread ${message.threadId}`);
38
+ return true;
39
+ }
40
+ receiveIncomingMessage(message) {
41
+ if (this.status !== "active")
42
+ return;
43
+ this.logger.debug(`Facebook runner received message ${message.id} from sender ${message.senderId}`);
44
+ this.messageHandler?.(message);
45
+ }
46
+ }
47
+ exports.FacebookChannelRunner = FacebookChannelRunner;
@@ -0,0 +1,19 @@
1
+ import { IChannelRunner } from "../IChannelRunner";
2
+ import { AccountStatus, PlatformType, UnifiedMessage } from "@zplus/contracts";
3
+ import { ILogger } from "@zplus/core";
4
+ export declare class TelegramChannelRunner implements IChannelRunner {
5
+ readonly accountId: string;
6
+ private readonly sessionData?;
7
+ private readonly proxyData?;
8
+ readonly platform: PlatformType;
9
+ private status;
10
+ private readonly logger;
11
+ private messageHandler?;
12
+ constructor(accountId: string, sessionData?: Record<string, any> | undefined, proxyData?: Record<string, any> | undefined, logger?: ILogger);
13
+ onMessage(handler: (message: UnifiedMessage) => void): void;
14
+ start(): Promise<void>;
15
+ stop(): Promise<void>;
16
+ getStatus(): AccountStatus;
17
+ sendMessage(message: UnifiedMessage): Promise<boolean>;
18
+ receiveIncomingMessage(message: UnifiedMessage): void;
19
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TelegramChannelRunner = void 0;
4
+ const core_1 = require("@zplus/core");
5
+ class TelegramChannelRunner {
6
+ accountId;
7
+ sessionData;
8
+ proxyData;
9
+ platform = "telegram";
10
+ status = "offline";
11
+ logger;
12
+ messageHandler;
13
+ constructor(accountId, sessionData, proxyData, logger) {
14
+ this.accountId = accountId;
15
+ this.sessionData = sessionData;
16
+ this.proxyData = proxyData;
17
+ this.logger = logger || (0, core_1.createLogger)(`TelegramRunner:${accountId}`);
18
+ }
19
+ onMessage(handler) {
20
+ this.messageHandler = handler;
21
+ }
22
+ async start() {
23
+ const proxyInfo = this.proxyData
24
+ ? ` via resident proxy ${this.proxyData.host || this.proxyData.ip || "configured"}`
25
+ : " (direct)";
26
+ this.logger.info(`Starting 24/7 headless Telegram runner for account ${this.accountId}${proxyInfo}...`);
27
+ this.status = "active";
28
+ }
29
+ async stop() {
30
+ this.logger.info(`Stopping headless Telegram runner for account ${this.accountId}...`);
31
+ this.status = "offline";
32
+ }
33
+ getStatus() {
34
+ return this.status;
35
+ }
36
+ async sendMessage(message) {
37
+ this.logger.info(`Headless Telegram runner sending message ${message.id} to thread ${message.threadId}`);
38
+ return true;
39
+ }
40
+ receiveIncomingMessage(message) {
41
+ if (this.status !== "active")
42
+ return;
43
+ this.logger.debug(`Telegram runner received message ${message.id} from sender ${message.senderId}`);
44
+ this.messageHandler?.(message);
45
+ }
46
+ }
47
+ exports.TelegramChannelRunner = TelegramChannelRunner;
@@ -0,0 +1,19 @@
1
+ import { IChannelRunner } from "../IChannelRunner";
2
+ import { AccountStatus, PlatformType, UnifiedMessage } from "@zplus/contracts";
3
+ import { ILogger } from "@zplus/core";
4
+ export declare class WhatsAppChannelRunner implements IChannelRunner {
5
+ readonly accountId: string;
6
+ private readonly sessionData?;
7
+ private readonly proxyData?;
8
+ readonly platform: PlatformType;
9
+ private status;
10
+ private readonly logger;
11
+ private messageHandler?;
12
+ constructor(accountId: string, sessionData?: Record<string, any> | undefined, proxyData?: Record<string, any> | undefined, logger?: ILogger);
13
+ onMessage(handler: (message: UnifiedMessage) => void): void;
14
+ start(): Promise<void>;
15
+ stop(): Promise<void>;
16
+ getStatus(): AccountStatus;
17
+ sendMessage(message: UnifiedMessage): Promise<boolean>;
18
+ receiveIncomingMessage(message: UnifiedMessage): void;
19
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WhatsAppChannelRunner = void 0;
4
+ const core_1 = require("@zplus/core");
5
+ class WhatsAppChannelRunner {
6
+ accountId;
7
+ sessionData;
8
+ proxyData;
9
+ platform = "whatsapp";
10
+ status = "offline";
11
+ logger;
12
+ messageHandler;
13
+ constructor(accountId, sessionData, proxyData, logger) {
14
+ this.accountId = accountId;
15
+ this.sessionData = sessionData;
16
+ this.proxyData = proxyData;
17
+ this.logger = logger || (0, core_1.createLogger)(`WhatsAppRunner:${accountId}`);
18
+ }
19
+ onMessage(handler) {
20
+ this.messageHandler = handler;
21
+ }
22
+ async start() {
23
+ const proxyInfo = this.proxyData
24
+ ? ` via resident proxy ${this.proxyData.host || this.proxyData.ip || "configured"}`
25
+ : " (direct)";
26
+ this.logger.info(`Starting 24/7 headless WhatsApp runner for account ${this.accountId}${proxyInfo}...`);
27
+ this.status = "active";
28
+ }
29
+ async stop() {
30
+ this.logger.info(`Stopping headless WhatsApp runner for account ${this.accountId}...`);
31
+ this.status = "offline";
32
+ }
33
+ getStatus() {
34
+ return this.status;
35
+ }
36
+ async sendMessage(message) {
37
+ this.logger.info(`Headless WhatsApp runner sending message ${message.id} to thread ${message.threadId}`);
38
+ return true;
39
+ }
40
+ receiveIncomingMessage(message) {
41
+ if (this.status !== "active")
42
+ return;
43
+ this.logger.debug(`WhatsApp runner received message ${message.id} from sender ${message.senderId}`);
44
+ this.messageHandler?.(message);
45
+ }
46
+ }
47
+ exports.WhatsAppChannelRunner = WhatsAppChannelRunner;
@@ -0,0 +1,22 @@
1
+ import { IChannelRunner } from "../IChannelRunner";
2
+ import { AccountStatus, PlatformType, UnifiedMessage } from "@zplus/contracts";
3
+ import { ILogger } from "@zplus/core";
4
+ export declare class ZaloChannelRunner implements IChannelRunner {
5
+ readonly accountId: string;
6
+ private readonly sessionData?;
7
+ private readonly proxyData?;
8
+ readonly platform: PlatformType;
9
+ private status;
10
+ private readonly logger;
11
+ private messageHandler?;
12
+ constructor(accountId: string, sessionData?: Record<string, any> | undefined, proxyData?: Record<string, any> | undefined, logger?: ILogger);
13
+ onMessage(handler: (message: UnifiedMessage) => void): void;
14
+ start(): Promise<void>;
15
+ stop(): Promise<void>;
16
+ getStatus(): AccountStatus;
17
+ sendMessage(message: UnifiedMessage): Promise<boolean>;
18
+ /**
19
+ * Hook nhận tin nhắn từ socket WSS (zca-js/zocial) và bắn qua pipeline
20
+ */
21
+ receiveIncomingMessage(message: UnifiedMessage): void;
22
+ }
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ZaloChannelRunner = void 0;
4
+ const core_1 = require("@zplus/core");
5
+ class ZaloChannelRunner {
6
+ accountId;
7
+ sessionData;
8
+ proxyData;
9
+ platform = "zalo";
10
+ status = "offline";
11
+ logger;
12
+ messageHandler;
13
+ constructor(accountId, sessionData, proxyData, logger) {
14
+ this.accountId = accountId;
15
+ this.sessionData = sessionData;
16
+ this.proxyData = proxyData;
17
+ this.logger = logger || (0, core_1.createLogger)(`ZaloRunner:${accountId}`);
18
+ }
19
+ onMessage(handler) {
20
+ this.messageHandler = handler;
21
+ }
22
+ async start() {
23
+ const proxyInfo = this.proxyData
24
+ ? ` via resident proxy ${this.proxyData.host || this.proxyData.ip || "configured"}`
25
+ : " (direct)";
26
+ this.logger.info(`Starting 24/7 headless Zalo runner for account ${this.accountId}${proxyInfo}...`);
27
+ this.status = "active";
28
+ }
29
+ async stop() {
30
+ this.logger.info(`Stopping headless Zalo runner for account ${this.accountId}...`);
31
+ this.status = "offline";
32
+ }
33
+ getStatus() {
34
+ return this.status;
35
+ }
36
+ async sendMessage(message) {
37
+ this.logger.info(`Headless Zalo runner sending message ${message.id} to thread ${message.threadId}`);
38
+ return true;
39
+ }
40
+ /**
41
+ * Hook nhận tin nhắn từ socket WSS (zca-js/zocial) và bắn qua pipeline
42
+ */
43
+ receiveIncomingMessage(message) {
44
+ if (this.status !== "active")
45
+ return;
46
+ this.logger.debug(`Zalo runner received message ${message.id} from sender ${message.senderId}`);
47
+ this.messageHandler?.(message);
48
+ }
49
+ }
50
+ exports.ZaloChannelRunner = ZaloChannelRunner;
package/dist/cli.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 🏛️ ZPlus Headless Worker CLI
4
+ * Khởi chạy worker bằng lệnh npm hoặc terminal trực tiếp
5
+ */
6
+ export {};