@vibersmoon/nexalink 0.3.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,72 @@
1
+ # Changelog
2
+
3
+ All notable changes to @vibersmoon/nexalink are documented here.
4
+
5
+ ## [0.3.0] — 2026-09-22
6
+
7
+ ### Added
8
+
9
+ - **Media Engine:** MIME type detection via magic bytes, file validation, temporary file cleanup with automatic removal.
10
+ - **Plugin System:** `PluginRegistry` for extensible plugin lifecycle (setup, teardown, error isolation).
11
+ - **Directory Services:** `GroupService`, `ContactService`, `ChatService`, `PresenceService` for local metadata storage.
12
+ - **Comprehensive Documentation:** Updated README with full API reference, examples, and project structure.
13
+ - **Security Policy:** Detailed SECURITY.md with legal disclaimers, threat model, and vulnerability reporting.
14
+ - **Type-safe Event Listeners:** All event subscriptions use strict TypeScript inference.
15
+
16
+ ### Changed
17
+
18
+ - Client core (`NexaClientCore`) now implements `on`, `once`, `off` methods directly.
19
+ - Connection manager emits detailed transition metadata (state, previousState, reason).
20
+ - All public APIs consolidated in root `src/index.ts` (single import point).
21
+ - Logger outputs structured JSON with timestamp, level, and context.
22
+
23
+ ### Fixed
24
+
25
+ - TypeScript import resolution in `directory/index.ts` (corrected relative paths).
26
+ - ESLint unused import warnings in test files.
27
+ - Build pipeline now generates both ESM and CommonJS with correct type declarations.
28
+
29
+ ### Verified
30
+
31
+ - ✅ 82 unit tests passing (14 test files).
32
+ - ✅ ESLint clean (no errors).
33
+ - ✅ TypeScript `noEmit` clean (no errors).
34
+ - ✅ Build output: ESM, CJS, `.d.ts` declarations all present.
35
+ - ✅ npm audit: 0 production vulnerabilities.
36
+
37
+ ## [0.2.0] — 2026-09-21
38
+
39
+ ### Added
40
+
41
+ - **Messaging Foundation:** `MessageService` with normalization, validation, ID generation.
42
+ - **Event Model:** Type-safe `NexaEventBus` with `on`, `once`, `off` subscriptions.
43
+ - **Connection State Machine:** 8-state FSM (idle, connecting, authenticating, connected, disconnecting, disconnected, error, awaiting-login) with guards.
44
+ - **Session Store:** File-based `FileSessionStore` with optional AES-256-CBC encryption.
45
+ - **Client Phases:** Phase 2 (stub for testing), Phase 3 (full implementation with connection manager).
46
+
47
+ ### Changed
48
+
49
+ - Configuration validation now uses Zod for strict schema enforcement.
50
+ - Connection manager uses exponential backoff with configurable retry limits.
51
+
52
+ ### Tests
53
+
54
+ - 38 unit tests covering errors, logger, config, events, client, connection, messaging.
55
+
56
+ ## [0.1.0] — 2026-09-21
57
+
58
+ ### Added
59
+
60
+ - **Initial Release:** Foundation library with stable module structure.
61
+ - **Error System:** Typed error classes (ConnectionError, AuthenticationError, TimeoutError, ValidationError, UnsupportedFeatureError, ProtocolError, MediaError, PermissionError, RetryableError, ConfigurationError).
62
+ - **Logger:** Structured logging with secret redaction (passwords, tokens, keys, pairing codes).
63
+ - **Configuration:** Zod-backed `NexaClientConfig` with validation and normalization.
64
+ - **Connection Manager:** Exponential backoff retry logic, connection state tracking.
65
+ - **Event Bus:** Async event system with error isolation.
66
+ - **Base Client Types:** `NexaClient` interface, factory function stubs.
67
+
68
+ ### Status
69
+
70
+ - ✅ 21 unit tests passing.
71
+ - ✅ Build pipeline (ESM, CJS, TypeScript declarations).
72
+ - ✅ ESLint and Prettier configured.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Akram
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,308 @@
1
+ # NexaLink
2
+
3
+ Modular, type-safe WhatsApp automation library for Node.js, built from the ground up with security, correctness, and developer experience in mind.
4
+
5
+ **⚠️ Important:** This is an independent library. It is **not** affiliated with WhatsApp or Meta. Using it may violate WhatsApp Terms of Service. See [SECURITY.md](./SECURITY.md) for legal and protocol constraints.
6
+
7
+ ## Status
8
+
9
+ | Area | Status |
10
+ | --- | --- |
11
+ | Package, errors, logger, config, events | ✅ Stable |
12
+ | Connection / authentication | ✅ PHASE 3 complete |
13
+ | Messaging / media | ✅ PHASE 5 complete |
14
+ | Plugins | ✅ PHASE 6 complete |
15
+ | Directory (groups, contacts, chats) | ✅ PHASE 7 complete |
16
+ | Full protocol implementation | ⏳ In progress |
17
+
18
+ **Version:** 0.3.0 — Media, plugins, directory services.
19
+
20
+ ## Requirements
21
+
22
+ - Node.js 20+ (LTS preferred)
23
+ - Linux, Windows, or macOS — no OS-specific dependencies
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ npm install @vibersmoon/nexalink
29
+ ```
30
+
31
+ (Not yet published to npm. For development, clone and build locally.)
32
+
33
+ ## Quick Start
34
+
35
+ ```typescript
36
+ import { createClient } from '@vibersmoon/nexalink';
37
+
38
+ const client = await createClient({
39
+ sessionName: 'my-bot',
40
+ authDir: './auth',
41
+ logLevel: 'info',
42
+ });
43
+
44
+ // Listen for messages
45
+ client.on('message', async (msg) => {
46
+ console.log(`From ${msg.from}: ${msg.text}`);
47
+ await client.sendMessage(msg.from, { text: 'Got it!' });
48
+ });
49
+
50
+ // Listen for connection updates
51
+ client.on('connection.update', (update) => {
52
+ console.log(`State: ${update.state}`);
53
+ });
54
+
55
+ await client.connect();
56
+ ```
57
+
58
+ ## Configuration
59
+
60
+ | Field | Default | Notes |
61
+ | --- | --- | --- |
62
+ | `sessionName` | required | 1–64 characters, unique identifier |
63
+ | `authDir` | `~/.nexalink/auth` | Directory to store encrypted sessions |
64
+ | `logLevel` | `info` | `silent`, `error`, `warn`, `info`, `debug` |
65
+ | `connectionTimeoutMs` | `30000` | WebSocket handshake timeout (ms) |
66
+ | `reconnect` | `true` | Auto-reconnect on disconnect |
67
+ | `maxReconnectAttempts` | `5` | Maximum reconnection retries |
68
+ | `retryBaseDelayMs` | `1000` | Initial exponential backoff delay (ms) |
69
+ | `encryption` | disabled | Set `encryption: true` to enable AES-256-CBC session encryption |
70
+
71
+ ## API
72
+
73
+ ### `createClient(config: NexaClientConfig): Promise<NexaClient>`
74
+
75
+ Factory function to create a new client instance.
76
+
77
+ ### `NexaClient` Interface
78
+
79
+ #### Methods
80
+
81
+ - `connect(): Promise<void>` — Establish WebSocket and authenticate
82
+ - `disconnect(): Promise<void>` — Close connection gracefully
83
+ - `logout(): Promise<void>` — Disconnect and destroy session
84
+ - `sendMessage(jid: string, options: SendMessageOptions): Promise<{ id: string }>` — Send message
85
+ - `on(event: string, listener): () => void` — Register event listener
86
+ - `once(event: string, listener): () => void` — One-time listener
87
+ - `off(event: string, listener): void` — Unsubscribe listener
88
+
89
+ #### Properties
90
+
91
+ - `state: ConnectionState` — Current connection state
92
+ - `config: NexaClientConfig` — Active configuration
93
+
94
+ ### Events
95
+
96
+ #### `connection.update`
97
+
98
+ ```typescript
99
+ client.on('connection.update', (update) => {
100
+ console.log(update.state); // 'connecting', 'connected', 'disconnected', etc.
101
+ console.log(update.previousState);
102
+ console.log(update.reason); // 'client.connect', 'websocket.error', etc.
103
+ });
104
+ ```
105
+
106
+ #### `auth.update`
107
+
108
+ ```typescript
109
+ client.on('auth.update', (update) => {
110
+ console.log(update.sessionLoaded); // boolean
111
+ console.log(update.pairingCode); // string | undefined
112
+ });
113
+ ```
114
+
115
+ #### `message`
116
+
117
+ ```typescript
118
+ client.on('message', (msg) => {
119
+ console.log(msg.id); // Message ID
120
+ console.log(msg.from); // Sender JID (e.g., '628123456789@s.whatsapp.net')
121
+ console.log(msg.text); // Message text
122
+ console.log(msg.timestamp); // Unix timestamp (ms)
123
+ });
124
+ ```
125
+
126
+ ### Error Handling
127
+
128
+ ```typescript
129
+ import {
130
+ NexaError,
131
+ ConnectionError,
132
+ AuthenticationError,
133
+ TimeoutError,
134
+ ValidationError,
135
+ UnsupportedFeatureError,
136
+ } from '@vibersmoon/nexalink';
137
+
138
+ try {
139
+ await client.connect();
140
+ } catch (error) {
141
+ if (error instanceof AuthenticationError) {
142
+ console.error('Auth failed:', error.message);
143
+ } else if (error instanceof ConnectionError) {
144
+ console.error('Connection failed:', error.message);
145
+ }
146
+ }
147
+ ```
148
+
149
+ ### Media Support
150
+
151
+ ```typescript
152
+ import { MediaService } from '@vibersmoon/nexalink';
153
+
154
+ const media = new MediaService({
155
+ mediaDirectory: './media',
156
+ maxBytes: 16 * 1024 * 1024,
157
+ logger: client.logger,
158
+ });
159
+
160
+ const prepared = await media.prepare({
161
+ buffer: imageBuffer,
162
+ filename: 'photo.jpg',
163
+ });
164
+
165
+ console.log(prepared.kind); // 'image', 'video', 'audio', 'document'
166
+ console.log(prepared.mimeType); // 'image/jpeg'
167
+ console.log(prepared.size); // file size in bytes
168
+ ```
169
+
170
+ ### Plugins
171
+
172
+ ```typescript
173
+ import { definePlugin, PluginRegistry } from '@vibersmoon/nexalink';
174
+
175
+ const plugin = definePlugin({
176
+ name: 'auto-reply',
177
+ version: '1.0.0',
178
+ setup(host) {
179
+ host.on('message', async (msg) => {
180
+ console.log('Plugin received:', msg.text);
181
+ });
182
+ },
183
+ teardown() {
184
+ console.log('Cleaning up');
185
+ },
186
+ });
187
+
188
+ const registry = new PluginRegistry(client.logger);
189
+ await registry.use(plugin, client);
190
+
191
+ // When done
192
+ await registry.teardownAll();
193
+ ```
194
+
195
+ ### Directory Services
196
+
197
+ ```typescript
198
+ import {
199
+ GroupService,
200
+ ContactService,
201
+ ChatService,
202
+ PresenceService,
203
+ } from '@vibersmoon/nexalink';
204
+
205
+ const groups = new GroupService(client.logger);
206
+ groups.upsert({
207
+ id: '123-456@g.us',
208
+ subject: 'Team Chat',
209
+ participants: ['628123456789@s.whatsapp.net'],
210
+ });
211
+
212
+ const contacts = new ContactService(client.logger);
213
+ contacts.upsert({
214
+ id: '628123456789@s.whatsapp.net',
215
+ name: 'John Doe',
216
+ });
217
+
218
+ const chats = new ChatService(client.logger);
219
+ chats.upsert({
220
+ id: '628123456789@s.whatsapp.net',
221
+ unreadCount: 5,
222
+ });
223
+
224
+ const presence = new PresenceService(client.logger);
225
+ // Presence updates require active protocol session (PHASE 9+)
226
+ ```
227
+
228
+ ## Development
229
+
230
+ ### Setup
231
+
232
+ ```bash
233
+ git clone https://github.com/akram-fanz/nexalink.git
234
+ cd nexalink
235
+ npm install
236
+ ```
237
+
238
+ ### Scripts
239
+
240
+ ```bash
241
+ npm test # Run full test suite
242
+ npm run test:watch # Watch mode
243
+ npm run test:coverage # Coverage report
244
+ npm run build # Compile ESM, CJS, TypeScript declarations
245
+ npm run lint # ESLint
246
+ npm run format # Prettier
247
+ ```
248
+
249
+ ### Project Structure
250
+
251
+ ```
252
+ nexalink/
253
+ ├── src/
254
+ │ ├── client/ # Client factory and phases
255
+ │ ├── connection/ # Connection manager, state machine, transport
256
+ │ ├── auth/ # Session store, auth provider
257
+ │ ├── messaging/ # Message model, service, validation
258
+ │ ├── media/ # MIME detection, file validation
259
+ │ ├── plugins/ # Plugin registry and API
260
+ │ ├── groups/ # Group service
261
+ │ ├── contacts/ # Contact service
262
+ │ ├── chats/ # Chat service
263
+ │ ├── presence/ # Presence service
264
+ │ ├── events/ # Event bus
265
+ │ ├── errors/ # Error classes
266
+ │ ├── logger.ts # Logger with secret redaction
267
+ │ ├── config.ts # Configuration validation (Zod)
268
+ │ ├── constants/ # Library constants
269
+ │ ├── types/ # Type definitions
270
+ │ └── index.ts # Public API exports
271
+ ├── tests/
272
+ │ └── unit/ # Vitest unit tests (82 tests)
273
+ ├── package.json # v0.3.0
274
+ ├── tsconfig.json # TypeScript configuration
275
+ ├── vitest.config.ts # Vitest configuration
276
+ ├── eslint.config.js # ESLint configuration
277
+ ├── prettier.config.js # Prettier configuration
278
+ ├── README.md # This file
279
+ ├── SECURITY.md # Security policy
280
+ ├── CHANGELOG.md # Version history
281
+ └── LICENSE # MIT
282
+
283
+ ```
284
+
285
+ ## Phase Timeline
286
+
287
+ - ✅ **PHASE 1 (Research & Planning):** Feasibility, protocol analysis, architecture planning
288
+ - ✅ **PHASE 2 (Foundation):** Package, errors, logger, config, events, client stub
289
+ - ✅ **PHASE 3 (Connection & Auth):** Connection manager, state machine, session store, WebSocket transport
290
+ - ✅ **PHASE 4 (Messaging):** Message model, validation, service, sendMessage API
291
+ - ✅ **PHASE 5 (Media):** MIME detection, file validation, temporary file cleanup
292
+ - ✅ **PHASE 6 (Plugins):** Plugin registry, lifecycle hooks, extensibility
293
+ - ✅ **PHASE 7 (Directory):** Groups, contacts, chats, presence services (local metadata only)
294
+ - 🔄 **PHASE 8 (Documentation):** README, API docs, security policy, contribution guide
295
+ - ⏳ **PHASE 9 (Release):** GitHub publish, npm publish, CI/CD setup
296
+
297
+ ## License
298
+
299
+ MIT. See [LICENSE](./LICENSE).
300
+
301
+ ## Contributing
302
+
303
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) (coming soon).
304
+
305
+ ## Support
306
+
307
+ - **Issues:** GitHub Issues: https://github.com/akram-fanz/nexalink/issues
308
+ - **Security:** See [SECURITY.md](./SECURITY.md)
package/SECURITY.md ADDED
@@ -0,0 +1,59 @@
1
+ # Security Policy
2
+
3
+ ## Important Disclaimers
4
+
5
+ ### Legal Status
6
+
7
+ - **Not an Official SDK:** @vibersmoon/nexalink is **not** affiliated with WhatsApp or Meta Platforms.
8
+ - **Terms of Service:** Using this library to automate WhatsApp Web may violate WhatsApp's Terms of Service. Users assume all legal risks.
9
+ - **No Warranty:** This library is provided "as-is" without warranty of any kind. The maintainers are not responsible for account bans, data loss, or legal consequences.
10
+
11
+ ### Protocol Reliability
12
+
13
+ - **Reverse Engineering:** WhatsApp's internal protocol is **not officially documented**. This library reverse-engineers behavior from WhatsApp Web.
14
+ - **Breaking Changes:** WhatsApp updates can break protocol compatibility. Expect occasional maintenance releases.
15
+ - **Detection Risk:** Automated messaging may trigger WhatsApp's abuse detection and result in account restrictions or bans.
16
+
17
+ ## Security Practices
18
+
19
+ ### Data Protection
20
+
21
+ - Session files are equivalent to account credentials. Treat them like passwords.
22
+ - Enable `encryption.enabled` with a strong passphrase to encrypt sessions (AES-256-CBC).
23
+ - Never log private keys, pairing codes, passphrases, or session tokens.
24
+ - Restrict filesystem permissions on `authDirectory` (mode 0o700).
25
+ - Rotate a session by calling `logout()` and pairing again if you suspect theft.
26
+
27
+ ### File Safety
28
+
29
+ - All file paths are validated to prevent directory traversal attacks.
30
+ - Media file access is restricted to the configured `mediaDirectory`.
31
+ - Temporary files are cleaned up automatically after processing.
32
+
33
+ ## Reporting a Vulnerability
34
+
35
+ Do not open a public GitHub issue for credential leaks, remote code execution, or protocol bypasses.
36
+
37
+ Email: `akram-fanz@protonmail.com` with:
38
+
39
+ - Affected version
40
+ - Reproduction steps (without live credentials)
41
+ - Impact assessment
42
+
43
+ We will acknowledge and work toward a fix within 48 hours.
44
+
45
+ ## What We Will Never Ship
46
+
47
+ - Credential harvesting
48
+ - Unauthorized account access helpers
49
+ - Spam / bulk-send tooling
50
+ - CAPTCHA or platform-security bypasses
51
+ - Hidden persistence or telemetry
52
+
53
+ ## Supported Versions
54
+
55
+ | Version | Status | Security |
56
+ | --- | --- | --- |
57
+ | 0.3.x | Current | Actively maintained |
58
+ | 0.2.x | Previous | Security patches only |
59
+ | 0.1.x | Legacy | End of life |
package/dist/index.cjs ADDED
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ChatService: () => ChatService,
24
+ ContactService: () => ContactService,
25
+ GroupService: () => GroupService,
26
+ PresenceService: () => PresenceService
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+
30
+ // src/errors/base.ts
31
+ var NexaError = class extends Error {
32
+ code;
33
+ metadata;
34
+ retryable;
35
+ retryAfterMs;
36
+ constructor(code, message, options = {}) {
37
+ super(message, { cause: options.cause });
38
+ this.name = new.target.name;
39
+ this.code = code;
40
+ this.metadata = Object.freeze({ ...options.metadata ?? {} });
41
+ this.retryable = options.retryable ?? false;
42
+ this.retryAfterMs = options.retryAfterMs;
43
+ Object.setPrototypeOf(this, new.target.prototype);
44
+ }
45
+ /** Returns a safe representation suitable for logging and user-facing errors. */
46
+ toJSON() {
47
+ return {
48
+ name: this.name,
49
+ code: this.code,
50
+ message: this.message,
51
+ retryable: this.retryable,
52
+ retryAfterMs: this.retryAfterMs,
53
+ metadata: this.metadata
54
+ };
55
+ }
56
+ };
57
+
58
+ // src/errors/index.ts
59
+ var ValidationError = class extends NexaError {
60
+ constructor(message, options = {}) {
61
+ super("VALIDATION_ERROR" /* VALIDATION_ERROR */, message, options);
62
+ }
63
+ };
64
+ var UnsupportedFeatureError = class extends NexaError {
65
+ constructor(message, options = {}) {
66
+ super("UNSUPPORTED_FEATURE" /* UNSUPPORTED_FEATURE */, message, options);
67
+ }
68
+ };
69
+
70
+ // src/groups/service.ts
71
+ var GroupService = class {
72
+ groups = /* @__PURE__ */ new Map();
73
+ logger;
74
+ constructor(logger) {
75
+ this.logger = logger;
76
+ }
77
+ upsert(group) {
78
+ if (!group.id || !group.id.endsWith("@g.us")) {
79
+ throw new ValidationError("Invalid group ID format", { metadata: { id: group.id } });
80
+ }
81
+ this.groups.set(group.id, group);
82
+ this.logger.debug("Group upserted", { id: group.id });
83
+ return group;
84
+ }
85
+ get(id) {
86
+ return this.groups.get(id);
87
+ }
88
+ list() {
89
+ return [...this.groups.values()];
90
+ }
91
+ async create(_options) {
92
+ throw new UnsupportedFeatureError("Group creation requires active WebSocket protocol session");
93
+ }
94
+ async addParticipant(_groupId, _participantId) {
95
+ throw new UnsupportedFeatureError("Group mutation requires active WebSocket protocol session");
96
+ }
97
+ };
98
+
99
+ // src/contacts/service.ts
100
+ var ContactService = class {
101
+ contacts = /* @__PURE__ */ new Map();
102
+ logger;
103
+ constructor(logger) {
104
+ this.logger = logger;
105
+ }
106
+ upsert(contact) {
107
+ if (!contact.id || !contact.id.endsWith("@s.whatsapp.net")) {
108
+ throw new ValidationError("Invalid contact ID format", { metadata: { id: contact.id } });
109
+ }
110
+ this.contacts.set(contact.id, contact);
111
+ this.logger.debug("Contact upserted", { id: contact.id });
112
+ return contact;
113
+ }
114
+ get(id) {
115
+ return this.contacts.get(id);
116
+ }
117
+ list() {
118
+ return [...this.contacts.values()];
119
+ }
120
+ };
121
+
122
+ // src/chats/service.ts
123
+ var ChatService = class {
124
+ chats = /* @__PURE__ */ new Map();
125
+ logger;
126
+ constructor(logger) {
127
+ this.logger = logger;
128
+ }
129
+ upsert(chat) {
130
+ this.chats.set(chat.id, chat);
131
+ this.logger.debug("Chat upserted", { id: chat.id });
132
+ return chat;
133
+ }
134
+ get(id) {
135
+ return this.chats.get(id);
136
+ }
137
+ list() {
138
+ return [...this.chats.values()];
139
+ }
140
+ };
141
+
142
+ // src/presence/service.ts
143
+ var PresenceService = class {
144
+ logger;
145
+ constructor(logger) {
146
+ this.logger = logger;
147
+ }
148
+ async sendTyping(_jid) {
149
+ this.logger.debug("Presence simulation requested");
150
+ throw new UnsupportedFeatureError("Presence updates require active WebSocket protocol session");
151
+ }
152
+ };
153
+ // Annotate the CommonJS export names for ESM import in node:
154
+ 0 && (module.exports = {
155
+ ChatService,
156
+ ContactService,
157
+ GroupService,
158
+ PresenceService
159
+ });
@@ -0,0 +1,67 @@
1
+ interface NexaLogger {
2
+ readonly debug: (message: string, data?: unknown) => void;
3
+ readonly info: (message: string, data?: unknown) => void;
4
+ readonly warn: (message: string, data?: unknown) => void;
5
+ readonly error: (message: string, data?: unknown) => void;
6
+ readonly fatal: (message: string, data?: unknown) => void;
7
+ }
8
+
9
+ interface NexaGroup {
10
+ readonly id: string;
11
+ readonly subject: string;
12
+ readonly participants: ReadonlyArray<string>;
13
+ readonly owner?: string;
14
+ readonly createdAt?: number;
15
+ }
16
+ declare class GroupService {
17
+ private readonly groups;
18
+ private readonly logger;
19
+ constructor(logger: NexaLogger);
20
+ upsert(group: NexaGroup): NexaGroup;
21
+ get(id: string): NexaGroup | undefined;
22
+ list(): ReadonlyArray<NexaGroup>;
23
+ create(_options: {
24
+ readonly subject: string;
25
+ readonly participants: ReadonlyArray<string>;
26
+ }): Promise<NexaGroup>;
27
+ addParticipant(_groupId: string, _participantId: string): Promise<void>;
28
+ }
29
+
30
+ interface NexaContact {
31
+ readonly id: string;
32
+ readonly name?: string;
33
+ readonly status?: string;
34
+ readonly profilePictureUrl?: string;
35
+ }
36
+ declare class ContactService {
37
+ private readonly contacts;
38
+ private readonly logger;
39
+ constructor(logger: NexaLogger);
40
+ upsert(contact: NexaContact): NexaContact;
41
+ get(id: string): NexaContact | undefined;
42
+ list(): ReadonlyArray<NexaContact>;
43
+ }
44
+
45
+ interface NexaChat {
46
+ readonly id: string;
47
+ readonly unreadCount?: number;
48
+ readonly lastMessageTimestamp?: number;
49
+ readonly archived?: boolean;
50
+ readonly pinned?: boolean;
51
+ }
52
+ declare class ChatService {
53
+ private readonly chats;
54
+ private readonly logger;
55
+ constructor(logger: NexaLogger);
56
+ upsert(chat: NexaChat): NexaChat;
57
+ get(id: string): NexaChat | undefined;
58
+ list(): ReadonlyArray<NexaChat>;
59
+ }
60
+
61
+ declare class PresenceService {
62
+ private readonly logger;
63
+ constructor(logger: NexaLogger);
64
+ sendTyping(_jid: string): Promise<void>;
65
+ }
66
+
67
+ export { ChatService, ContactService, GroupService, PresenceService };
@@ -0,0 +1,67 @@
1
+ interface NexaLogger {
2
+ readonly debug: (message: string, data?: unknown) => void;
3
+ readonly info: (message: string, data?: unknown) => void;
4
+ readonly warn: (message: string, data?: unknown) => void;
5
+ readonly error: (message: string, data?: unknown) => void;
6
+ readonly fatal: (message: string, data?: unknown) => void;
7
+ }
8
+
9
+ interface NexaGroup {
10
+ readonly id: string;
11
+ readonly subject: string;
12
+ readonly participants: ReadonlyArray<string>;
13
+ readonly owner?: string;
14
+ readonly createdAt?: number;
15
+ }
16
+ declare class GroupService {
17
+ private readonly groups;
18
+ private readonly logger;
19
+ constructor(logger: NexaLogger);
20
+ upsert(group: NexaGroup): NexaGroup;
21
+ get(id: string): NexaGroup | undefined;
22
+ list(): ReadonlyArray<NexaGroup>;
23
+ create(_options: {
24
+ readonly subject: string;
25
+ readonly participants: ReadonlyArray<string>;
26
+ }): Promise<NexaGroup>;
27
+ addParticipant(_groupId: string, _participantId: string): Promise<void>;
28
+ }
29
+
30
+ interface NexaContact {
31
+ readonly id: string;
32
+ readonly name?: string;
33
+ readonly status?: string;
34
+ readonly profilePictureUrl?: string;
35
+ }
36
+ declare class ContactService {
37
+ private readonly contacts;
38
+ private readonly logger;
39
+ constructor(logger: NexaLogger);
40
+ upsert(contact: NexaContact): NexaContact;
41
+ get(id: string): NexaContact | undefined;
42
+ list(): ReadonlyArray<NexaContact>;
43
+ }
44
+
45
+ interface NexaChat {
46
+ readonly id: string;
47
+ readonly unreadCount?: number;
48
+ readonly lastMessageTimestamp?: number;
49
+ readonly archived?: boolean;
50
+ readonly pinned?: boolean;
51
+ }
52
+ declare class ChatService {
53
+ private readonly chats;
54
+ private readonly logger;
55
+ constructor(logger: NexaLogger);
56
+ upsert(chat: NexaChat): NexaChat;
57
+ get(id: string): NexaChat | undefined;
58
+ list(): ReadonlyArray<NexaChat>;
59
+ }
60
+
61
+ declare class PresenceService {
62
+ private readonly logger;
63
+ constructor(logger: NexaLogger);
64
+ sendTyping(_jid: string): Promise<void>;
65
+ }
66
+
67
+ export { ChatService, ContactService, GroupService, PresenceService };
package/dist/index.js ADDED
@@ -0,0 +1,129 @@
1
+ // src/errors/base.ts
2
+ var NexaError = class extends Error {
3
+ code;
4
+ metadata;
5
+ retryable;
6
+ retryAfterMs;
7
+ constructor(code, message, options = {}) {
8
+ super(message, { cause: options.cause });
9
+ this.name = new.target.name;
10
+ this.code = code;
11
+ this.metadata = Object.freeze({ ...options.metadata ?? {} });
12
+ this.retryable = options.retryable ?? false;
13
+ this.retryAfterMs = options.retryAfterMs;
14
+ Object.setPrototypeOf(this, new.target.prototype);
15
+ }
16
+ /** Returns a safe representation suitable for logging and user-facing errors. */
17
+ toJSON() {
18
+ return {
19
+ name: this.name,
20
+ code: this.code,
21
+ message: this.message,
22
+ retryable: this.retryable,
23
+ retryAfterMs: this.retryAfterMs,
24
+ metadata: this.metadata
25
+ };
26
+ }
27
+ };
28
+
29
+ // src/errors/index.ts
30
+ var ValidationError = class extends NexaError {
31
+ constructor(message, options = {}) {
32
+ super("VALIDATION_ERROR" /* VALIDATION_ERROR */, message, options);
33
+ }
34
+ };
35
+ var UnsupportedFeatureError = class extends NexaError {
36
+ constructor(message, options = {}) {
37
+ super("UNSUPPORTED_FEATURE" /* UNSUPPORTED_FEATURE */, message, options);
38
+ }
39
+ };
40
+
41
+ // src/groups/service.ts
42
+ var GroupService = class {
43
+ groups = /* @__PURE__ */ new Map();
44
+ logger;
45
+ constructor(logger) {
46
+ this.logger = logger;
47
+ }
48
+ upsert(group) {
49
+ if (!group.id || !group.id.endsWith("@g.us")) {
50
+ throw new ValidationError("Invalid group ID format", { metadata: { id: group.id } });
51
+ }
52
+ this.groups.set(group.id, group);
53
+ this.logger.debug("Group upserted", { id: group.id });
54
+ return group;
55
+ }
56
+ get(id) {
57
+ return this.groups.get(id);
58
+ }
59
+ list() {
60
+ return [...this.groups.values()];
61
+ }
62
+ async create(_options) {
63
+ throw new UnsupportedFeatureError("Group creation requires active WebSocket protocol session");
64
+ }
65
+ async addParticipant(_groupId, _participantId) {
66
+ throw new UnsupportedFeatureError("Group mutation requires active WebSocket protocol session");
67
+ }
68
+ };
69
+
70
+ // src/contacts/service.ts
71
+ var ContactService = class {
72
+ contacts = /* @__PURE__ */ new Map();
73
+ logger;
74
+ constructor(logger) {
75
+ this.logger = logger;
76
+ }
77
+ upsert(contact) {
78
+ if (!contact.id || !contact.id.endsWith("@s.whatsapp.net")) {
79
+ throw new ValidationError("Invalid contact ID format", { metadata: { id: contact.id } });
80
+ }
81
+ this.contacts.set(contact.id, contact);
82
+ this.logger.debug("Contact upserted", { id: contact.id });
83
+ return contact;
84
+ }
85
+ get(id) {
86
+ return this.contacts.get(id);
87
+ }
88
+ list() {
89
+ return [...this.contacts.values()];
90
+ }
91
+ };
92
+
93
+ // src/chats/service.ts
94
+ var ChatService = class {
95
+ chats = /* @__PURE__ */ new Map();
96
+ logger;
97
+ constructor(logger) {
98
+ this.logger = logger;
99
+ }
100
+ upsert(chat) {
101
+ this.chats.set(chat.id, chat);
102
+ this.logger.debug("Chat upserted", { id: chat.id });
103
+ return chat;
104
+ }
105
+ get(id) {
106
+ return this.chats.get(id);
107
+ }
108
+ list() {
109
+ return [...this.chats.values()];
110
+ }
111
+ };
112
+
113
+ // src/presence/service.ts
114
+ var PresenceService = class {
115
+ logger;
116
+ constructor(logger) {
117
+ this.logger = logger;
118
+ }
119
+ async sendTyping(_jid) {
120
+ this.logger.debug("Presence simulation requested");
121
+ throw new UnsupportedFeatureError("Presence updates require active WebSocket protocol session");
122
+ }
123
+ };
124
+ export {
125
+ ChatService,
126
+ ContactService,
127
+ GroupService,
128
+ PresenceService
129
+ };
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@vibersmoon/nexalink",
3
+ "version": "0.3.0",
4
+ "description": "Unofficial WhatsApp Web automation library for Node.js. Not affiliated with Meta or WhatsApp.",
5
+ "main": "dist/index.cjs",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "require": {
15
+ "types": "./dist/index.d.cts",
16
+ "default": "./dist/index.cjs"
17
+ }
18
+ }
19
+ },
20
+ "type": "module",
21
+ "files": [
22
+ "dist",
23
+ "README.md",
24
+ "LICENSE",
25
+ "CHANGELOG.md",
26
+ "SECURITY.md"
27
+ ],
28
+ "scripts": {
29
+ "dev": "tsx watch src/index.ts",
30
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
31
+ "test": "vitest run",
32
+ "test:watch": "vitest",
33
+ "test:coverage": "vitest run --coverage",
34
+ "lint": "eslint src tests",
35
+ "format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"",
36
+ "typecheck": "tsc --noEmit",
37
+ "clean": "rm -rf dist"
38
+ },
39
+ "keywords": [
40
+ "whatsapp",
41
+ "automation",
42
+ "bot",
43
+ "unofficial",
44
+ "web-api"
45
+ ],
46
+ "author": "Akram",
47
+ "license": "MIT",
48
+ "dependencies": {
49
+ "pino": "^9.6.0",
50
+ "qrcode-terminal": "^0.12.0",
51
+ "ws": "^8.18.1",
52
+ "zod": "^3.23.8"
53
+ },
54
+ "devDependencies": {
55
+ "@types/node": "^22.10.5",
56
+ "@types/qrcode-terminal": "^0.12.2",
57
+ "@types/ws": "^8.5.10",
58
+ "@typescript-eslint/eslint-plugin": "^8.15.0",
59
+ "@typescript-eslint/parser": "^8.15.0",
60
+ "eslint": "^9.15.0",
61
+ "eslint-config-prettier": "^9.1.0",
62
+ "prettier": "^3.3.3",
63
+ "tsup": "^8.4.0",
64
+ "tsx": "^4.19.2",
65
+ "typescript": "^5.6.3",
66
+ "vitest": "^4.1.11"
67
+ },
68
+ "engines": {
69
+ "node": ">=20.0.0"
70
+ },
71
+ "publishConfig": {
72
+ "access": "public"
73
+ }
74
+ }