libzapitu 1.0.0-alpha.16 → 1.0.0-alpha.18

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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ /**
7
+ * Worker child entry point.
8
+ * Runs makeWASocket inside a worker_threads context and bridges all
9
+ * method calls and events between the parent and the WA socket.
10
+ *
11
+ * Supports multiple concurrent sockets per worker — each identified
12
+ * by a unique `socketId` sent in the `init` message from the parent.
13
+ */
14
+ const worker_threads_1 = require("worker_threads");
15
+ const Socket_1 = __importDefault(require("../Socket"));
16
+ // ---------------------------------------------------------------------------
17
+ // Ensure we never run this in the main thread
18
+ // ---------------------------------------------------------------------------
19
+ if (worker_threads_1.isMainThread || !worker_threads_1.parentPort) {
20
+ throw new Error('This file must be run as a Worker (worker_threads).');
21
+ }
22
+ // ---------------------------------------------------------------------------
23
+ // Per-socket state
24
+ // ---------------------------------------------------------------------------
25
+ const CALLBACK_PROXIED_KEYS = [
26
+ 'getMessage',
27
+ 'shouldIgnoreJid',
28
+ 'patchMessageBeforeSending',
29
+ 'cachedGroupMetadata',
30
+ 'shouldSyncHistoryMessage',
31
+ 'makeSignalRepository',
32
+ ];
33
+ const sockets = new Map();
34
+ let _reqId = 0;
35
+ const nextReqId = () => ++_reqId;
36
+ // ---------------------------------------------------------------------------
37
+ // Callback proxy helper
38
+ // ---------------------------------------------------------------------------
39
+ function proxyCallback(socketId, key, ...args) {
40
+ const id = nextReqId();
41
+ return new Promise((resolve, reject) => {
42
+ const onMsg = (msg) => {
43
+ if ((msg === null || msg === void 0 ? void 0 : msg.type) === 'callback-result' && msg.id === id && msg.socketId === socketId) {
44
+ worker_threads_1.parentPort.off('message', onMsg);
45
+ if (msg.error) {
46
+ reject(new Error(msg.error));
47
+ }
48
+ else {
49
+ resolve(msg.result);
50
+ }
51
+ }
52
+ };
53
+ worker_threads_1.parentPort.on('message', onMsg);
54
+ worker_threads_1.parentPort.postMessage({ type: 'callback-call', socketId, id, key, args });
55
+ });
56
+ }
57
+ // ---------------------------------------------------------------------------
58
+ // Create a socket for a given socketId
59
+ // ---------------------------------------------------------------------------
60
+ function createSocket(socketId, rawConfig) {
61
+ const config = rawConfig;
62
+ // Replace sentinel callbacks with forwarding stubs
63
+ for (const k of CALLBACK_PROXIED_KEYS) {
64
+ if (config[k] === '__proxy_callback__') {
65
+ ;
66
+ config[k] = (...args) => proxyCallback(socketId, k, ...args);
67
+ }
68
+ }
69
+ const sock = (0, Socket_1.default)(config);
70
+ sock.ev.on('event', (map) => {
71
+ for (const [event, data] of Object.entries(map)) {
72
+ worker_threads_1.parentPort.postMessage({ type: 'event', socketId, event, data });
73
+ }
74
+ });
75
+ sockets.set(socketId, { sock, config });
76
+ }
77
+ // ---------------------------------------------------------------------------
78
+ // Handle incoming messages from the parent
79
+ // ---------------------------------------------------------------------------
80
+ worker_threads_1.parentPort.on('message', async (msg) => {
81
+ if (!msg || typeof msg !== 'object')
82
+ return;
83
+ switch (msg.type) {
84
+ // --- Initialize a new socket on this worker ---
85
+ case 'init': {
86
+ const { socketId, config } = msg;
87
+ if (sockets.has(socketId)) {
88
+ // Already initialized — ignore duplicate
89
+ return;
90
+ }
91
+ createSocket(socketId, config);
92
+ break;
93
+ }
94
+ // --- RPC method call ---
95
+ case 'call': {
96
+ const { socketId, id, method, args } = msg;
97
+ const entry = sockets.get(socketId);
98
+ if (!entry) {
99
+ worker_threads_1.parentPort.postMessage({
100
+ type: 'result',
101
+ socketId,
102
+ id,
103
+ error: `Socket ${socketId} not found`,
104
+ });
105
+ return;
106
+ }
107
+ try {
108
+ let target = entry.sock;
109
+ const path = method.split('.');
110
+ for (const segment of path) {
111
+ if (target === null || target === undefined) {
112
+ throw new Error(`Cannot read property '${segment}' of ${target}`);
113
+ }
114
+ target = target[segment];
115
+ }
116
+ if (typeof target === 'function') {
117
+ const thisCtx = path.length > 1 ? resolveTarget(entry.sock, path.slice(0, -1)) : entry.sock;
118
+ const result = await target.apply(thisCtx, args || []);
119
+ worker_threads_1.parentPort.postMessage({ type: 'result', socketId, id, result });
120
+ }
121
+ else {
122
+ worker_threads_1.parentPort.postMessage({ type: 'result', socketId, id, result: target });
123
+ }
124
+ }
125
+ catch (err) {
126
+ worker_threads_1.parentPort.postMessage({
127
+ type: 'result',
128
+ socketId,
129
+ id,
130
+ error: (err === null || err === void 0 ? void 0 : err.message) || String(err),
131
+ });
132
+ }
133
+ break;
134
+ }
135
+ }
136
+ });
137
+ /**
138
+ * Walk a dot-separated path on the socket to resolve a nested target
139
+ * (used as `this` context for method calls).
140
+ */
141
+ function resolveTarget(obj, path) {
142
+ let t = obj;
143
+ for (const seg of path) {
144
+ t = t[seg];
145
+ }
146
+ return t;
147
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Worker entry point — encapsulated mode.
3
+ *
4
+ * Import from here to run makeWASocket inside a child worker_threads
5
+ * process instead of the main thread. Multiple sockets are automatically
6
+ * distributed across a pool of workers (default: CPU count).
7
+ *
8
+ * @example
9
+ * import makeWASocket from 'libzapitu-rf/worker'
10
+ * const sock = makeWASocket({ ... })
11
+ * sock.ev.on('messages.upsert', ...)
12
+ * await sock.sendMessage(...)
13
+ *
14
+ * // Pool stats (available on every socket):
15
+ * console.log(sock.pool) // { size, active, idle, totalSockets }
16
+ *
17
+ * // Global pool control:
18
+ * import { resizePool, drainPool, getStats } from 'libzapitu-rf/worker'
19
+ * await resizePool(4) // scale to 4 workers
20
+ * await drainPool() // wait for all sockets to close, then terminate
21
+ */
22
+ import makeWASocket, { getStats, resizePool, drainPool } from './proxy';
23
+ export * from '../Types';
24
+ export * from '../Utils';
25
+ export * from '../Defaults';
26
+ export * from '../WABinary';
27
+ export * from '../WAM';
28
+ export * from '../WAUSync';
29
+ export type WASocket = ReturnType<typeof makeWASocket>;
30
+ export { makeWASocket, getStats, resizePool, drainPool };
31
+ export default makeWASocket;
@@ -0,0 +1,72 @@
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
36
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.drainPool = exports.resizePool = exports.getStats = exports.makeWASocket = void 0;
40
+ /**
41
+ * Worker entry point — encapsulated mode.
42
+ *
43
+ * Import from here to run makeWASocket inside a child worker_threads
44
+ * process instead of the main thread. Multiple sockets are automatically
45
+ * distributed across a pool of workers (default: CPU count).
46
+ *
47
+ * @example
48
+ * import makeWASocket from 'libzapitu-rf/worker'
49
+ * const sock = makeWASocket({ ... })
50
+ * sock.ev.on('messages.upsert', ...)
51
+ * await sock.sendMessage(...)
52
+ *
53
+ * // Pool stats (available on every socket):
54
+ * console.log(sock.pool) // { size, active, idle, totalSockets }
55
+ *
56
+ * // Global pool control:
57
+ * import { resizePool, drainPool, getStats } from 'libzapitu-rf/worker'
58
+ * await resizePool(4) // scale to 4 workers
59
+ * await drainPool() // wait for all sockets to close, then terminate
60
+ */
61
+ const proxy_1 = __importStar(require("./proxy"));
62
+ exports.makeWASocket = proxy_1.default;
63
+ Object.defineProperty(exports, "getStats", { enumerable: true, get: function () { return proxy_1.getStats; } });
64
+ Object.defineProperty(exports, "resizePool", { enumerable: true, get: function () { return proxy_1.resizePool; } });
65
+ Object.defineProperty(exports, "drainPool", { enumerable: true, get: function () { return proxy_1.drainPool; } });
66
+ __exportStar(require("../Types"), exports);
67
+ __exportStar(require("../Utils"), exports);
68
+ __exportStar(require("../Defaults"), exports);
69
+ __exportStar(require("../WABinary"), exports);
70
+ __exportStar(require("../WAM"), exports);
71
+ __exportStar(require("../WAUSync"), exports);
72
+ exports.default = proxy_1.default;
@@ -0,0 +1,24 @@
1
+ import type { UserFacingSocketConfig } from '../Types';
2
+ interface PoolStats {
3
+ /** Total workers in the pool */
4
+ size: number;
5
+ /** Workers currently handling at least one socket */
6
+ active: number;
7
+ /** Workers with zero sockets */
8
+ idle: number;
9
+ /** Total sockets across all workers */
10
+ totalSockets: number;
11
+ }
12
+ declare function getStats(): PoolStats;
13
+ /**
14
+ * Resize the pool. If shrinking, idle workers are terminated first.
15
+ * Active workers are never forcefully killed by resize.
16
+ */
17
+ declare function resizePool(newSize: number): Promise<void>;
18
+ /**
19
+ * Drain the pool: wait until all sockets are closed, then terminate all workers.
20
+ */
21
+ declare function drainPool(): Promise<void>;
22
+ declare const makeWASocket: (config: UserFacingSocketConfig) => any;
23
+ export default makeWASocket;
24
+ export { makeWASocket, getStats, resizePool, drainPool };
@@ -0,0 +1,289 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.makeWASocket = void 0;
4
+ exports.getStats = getStats;
5
+ exports.resizePool = resizePool;
6
+ exports.drainPool = drainPool;
7
+ /**
8
+ * Worker proxy — parent-side pool manager.
9
+ *
10
+ * Maintains a pool of worker_threads children, each running a real
11
+ * makeWASocket. The pool automatically distributes new socket requests
12
+ * across workers and provides lifecycle control.
13
+ *
14
+ * Usage (encapsulated mode):
15
+ * import makeWASocket from 'libzapitu-rf/worker'
16
+ * const sock = makeWASocket({ ... })
17
+ * sock.ev.on('messages.upsert', ...)
18
+ * await sock.sendMessage(...)
19
+ *
20
+ * Pool control (available on the returned socket):
21
+ * sock.pool — { size, active, idle, totalSockets }
22
+ *
23
+ * Global pool control:
24
+ * import { resizePool, drainPool, getStats } from 'libzapitu-rf/worker'
25
+ */
26
+ const worker_threads_1 = require("worker_threads");
27
+ const events_1 = require("events");
28
+ const os_1 = require("os");
29
+ const path_1 = require("path");
30
+ // ---------------------------------------------------------------------------
31
+ // Types
32
+ // ---------------------------------------------------------------------------
33
+ const CALLBACK_CONFIG_KEYS = [
34
+ 'getMessage',
35
+ 'shouldIgnoreJid',
36
+ 'patchMessageBeforeSending',
37
+ 'cachedGroupMetadata',
38
+ 'shouldSyncHistoryMessage',
39
+ 'makeSignalRepository',
40
+ ];
41
+ // ---------------------------------------------------------------------------
42
+ // Globals
43
+ // ---------------------------------------------------------------------------
44
+ let _reqId = 0;
45
+ const nextReqId = () => ++_reqId;
46
+ let _socketId = 0;
47
+ const nextSocketId = () => ++_socketId;
48
+ const workerScript = (0, path_1.join)(__dirname, 'child.js');
49
+ /** Active worker slots */
50
+ const slots = [];
51
+ /** Maximum workers allowed (default: CPU count) */
52
+ let maxWorkers = (0, os_1.cpus)().length;
53
+ // ---------------------------------------------------------------------------
54
+ // Pool management
55
+ // ---------------------------------------------------------------------------
56
+ function getStats() {
57
+ const active = slots.filter(s => s.load > 0).length;
58
+ return {
59
+ size: slots.length,
60
+ active,
61
+ idle: slots.length - active,
62
+ totalSockets: slots.reduce((sum, s) => sum + s.load, 0),
63
+ };
64
+ }
65
+ /**
66
+ * Find the best worker to place a new socket on.
67
+ * Strategy: pick the worker with the lowest load; if all are at capacity
68
+ * and we haven't hit maxWorkers, spawn a new one.
69
+ */
70
+ function acquireSlot() {
71
+ // Find least-loaded existing worker
72
+ let best = slots[0];
73
+ for (let i = 1; i < slots.length; i++) {
74
+ if (slots[i].load < best.load) {
75
+ best = slots[i];
76
+ }
77
+ }
78
+ // If the best worker already has sockets and we can grow, spawn a new one
79
+ if (best && best.load > 0 && slots.length < maxWorkers) {
80
+ return spawnWorker();
81
+ }
82
+ if (!best) {
83
+ return spawnWorker();
84
+ }
85
+ return best;
86
+ }
87
+ function spawnWorker() {
88
+ const worker = new worker_threads_1.Worker(workerScript);
89
+ const slot = {
90
+ worker,
91
+ load: 0,
92
+ emitters: new Map(),
93
+ callbacks: new Map(),
94
+ pending: new Map(),
95
+ };
96
+ // Global message handler — dispatches to the right socket's emitter
97
+ worker.on('message', (msg) => {
98
+ if (!msg || typeof msg !== 'object')
99
+ return;
100
+ const socketId = msg.socketId;
101
+ switch (msg.type) {
102
+ case 'event':
103
+ if (socketId !== undefined) {
104
+ const ev = slot.emitters.get(socketId);
105
+ if (ev)
106
+ ev.emit(msg.event, msg.data);
107
+ }
108
+ break;
109
+ case 'result': {
110
+ const p = slot.pending.get(msg.id);
111
+ if (p) {
112
+ slot.pending.delete(msg.id);
113
+ if (msg.error) {
114
+ p.reject(new Error(msg.error));
115
+ }
116
+ else {
117
+ p.resolve(msg.result);
118
+ }
119
+ }
120
+ break;
121
+ }
122
+ case 'callback-call':
123
+ if (socketId !== undefined) {
124
+ handleCallbackCall(slot, socketId, msg);
125
+ }
126
+ break;
127
+ }
128
+ });
129
+ worker.on('error', () => {
130
+ const idx = slots.indexOf(slot);
131
+ if (idx !== -1)
132
+ slots.splice(idx, 1);
133
+ });
134
+ worker.on('exit', () => {
135
+ const idx = slots.indexOf(slot);
136
+ if (idx !== -1)
137
+ slots.splice(idx, 1);
138
+ });
139
+ slots.push(slot);
140
+ return slot;
141
+ }
142
+ async function handleCallbackCall(slot, socketId, msg) {
143
+ const { id, key, args } = msg;
144
+ const store = slot.callbacks.get(socketId);
145
+ const fn = store === null || store === void 0 ? void 0 : store[key];
146
+ if (!fn) {
147
+ return slot.worker.postMessage({
148
+ type: 'callback-result',
149
+ socketId,
150
+ id,
151
+ error: `No callback registered for "${key}"`,
152
+ });
153
+ }
154
+ try {
155
+ const result = await fn(...args);
156
+ slot.worker.postMessage({ type: 'callback-result', socketId, id, result });
157
+ }
158
+ catch (err) {
159
+ slot.worker.postMessage({
160
+ type: 'callback-result',
161
+ socketId,
162
+ id,
163
+ error: (err === null || err === void 0 ? void 0 : err.message) || String(err),
164
+ });
165
+ }
166
+ }
167
+ /**
168
+ * Resize the pool. If shrinking, idle workers are terminated first.
169
+ * Active workers are never forcefully killed by resize.
170
+ */
171
+ async function resizePool(newSize) {
172
+ if (newSize < 1)
173
+ newSize = 1;
174
+ maxWorkers = newSize;
175
+ // Terminate excess idle workers
176
+ while (slots.length > maxWorkers) {
177
+ const idleSlot = slots.find(s => s.load === 0);
178
+ if (!idleSlot)
179
+ break; // all busy, can't shrink further
180
+ const idx = slots.indexOf(idleSlot);
181
+ slots.splice(idx, 1);
182
+ await idleSlot.worker.terminate();
183
+ }
184
+ }
185
+ /**
186
+ * Drain the pool: wait until all sockets are closed, then terminate all workers.
187
+ */
188
+ async function drainPool() {
189
+ // Wait for all sockets to close (load drops to 0)
190
+ await new Promise(resolve => {
191
+ const check = () => {
192
+ if (slots.every(s => s.load === 0)) {
193
+ resolve();
194
+ }
195
+ else {
196
+ setTimeout(check, 100);
197
+ }
198
+ };
199
+ check();
200
+ });
201
+ // Terminate all workers
202
+ await Promise.all(slots.map(s => s.worker.terminate()));
203
+ slots.length = 0;
204
+ }
205
+ // ---------------------------------------------------------------------------
206
+ // makeWASocket (pool-aware proxy)
207
+ // ---------------------------------------------------------------------------
208
+ const makeWASocket = (config) => {
209
+ const socketId = nextSocketId();
210
+ // ---- Extract non-serializable callbacks ------------------------------
211
+ const callbackStore = {};
212
+ const serializableConfig = { ...config };
213
+ for (const k of CALLBACK_CONFIG_KEYS) {
214
+ if (typeof serializableConfig[k] === 'function') {
215
+ callbackStore[k] = serializableConfig[k];
216
+ serializableConfig[k] = '__proxy_callback__';
217
+ }
218
+ }
219
+ // ---- Acquire a worker slot -------------------------------------------
220
+ const slot = acquireSlot();
221
+ slot.load++;
222
+ slot.emitters.set(socketId, new events_1.EventEmitter());
223
+ slot.callbacks.set(socketId, callbackStore);
224
+ // ---- Send init message to the worker for this socket -----------------
225
+ slot.worker.postMessage({
226
+ type: 'init',
227
+ socketId,
228
+ config: serializableConfig,
229
+ });
230
+ // ---- Public event emitter (local to this socket) ---------------------
231
+ const ev = slot.emitters.get(socketId);
232
+ // ---- RPC helper ------------------------------------------------------
233
+ const rpcCall = (method, args) => {
234
+ const id = nextReqId();
235
+ return new Promise((resolve, reject) => {
236
+ slot.pending.set(id, { resolve, reject });
237
+ slot.worker.postMessage({ type: 'call', socketId, id, method, args });
238
+ });
239
+ };
240
+ // ---- Build the proxy object ------------------------------------------
241
+ const socketProxy = new Proxy({ ev }, {
242
+ get(_target, prop) {
243
+ if (prop === 'ev')
244
+ return ev;
245
+ if (prop === 'then')
246
+ return undefined;
247
+ if (prop === 'pool')
248
+ return getStats();
249
+ if (prop === 'end') {
250
+ return async (...args) => {
251
+ try {
252
+ await rpcCall('end', args);
253
+ }
254
+ catch (_) { /* ok */ }
255
+ cleanupSocket(slot, socketId);
256
+ };
257
+ }
258
+ if (prop === 'logout') {
259
+ return async (...args) => {
260
+ try {
261
+ await rpcCall('logout', args);
262
+ }
263
+ catch (_) { /* ok */ }
264
+ cleanupSocket(slot, socketId);
265
+ };
266
+ }
267
+ return (...args) => rpcCall(prop, args);
268
+ },
269
+ });
270
+ return socketProxy;
271
+ };
272
+ exports.makeWASocket = makeWASocket;
273
+ function cleanupSocket(slot, socketId) {
274
+ slot.emitters.delete(socketId);
275
+ slot.callbacks.delete(socketId);
276
+ slot.load = Math.max(0, slot.load - 1);
277
+ // If the worker is now idle and we're over capacity, terminate it
278
+ if (slot.load === 0 && slots.length > maxWorkers) {
279
+ const idx = slots.indexOf(slot);
280
+ if (idx !== -1) {
281
+ slots.splice(idx, 1);
282
+ slot.worker.terminate();
283
+ }
284
+ }
285
+ }
286
+ // ---------------------------------------------------------------------------
287
+ // Exports
288
+ // ---------------------------------------------------------------------------
289
+ exports.default = makeWASocket;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libzapitu",
3
- "version": "1.0.0-alpha.16",
3
+ "version": "1.0.0-alpha.18",
4
4
  "description": "A WebSockets library for interacting with WhatsApp Web",
5
5
  "keywords": [
6
6
  "whatsapp",
@@ -14,8 +14,26 @@
14
14
  "author": "Zapitu Project",
15
15
  "main": "lib/index.js",
16
16
  "types": "lib/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./lib/index.d.ts",
20
+ "default": "./lib/index.js"
21
+ },
22
+ "./worker": {
23
+ "types": "./lib/worker/index.d.ts",
24
+ "default": "./lib/worker/index.js"
25
+ }
26
+ },
27
+ "typesVersions": {
28
+ "*": {
29
+ "worker": [
30
+ "./lib/worker/index.d.ts"
31
+ ]
32
+ }
33
+ },
17
34
  "files": [
18
35
  "lib/*",
36
+ "lib/worker/*",
19
37
  "WAProto/*",
20
38
  "engine-requirements.js"
21
39
  ],