Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions packages/next-whatsapp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output
/coverage.lcov

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env

# next.js build output
.next

dist/

*/package-lock.json

# IDE
.vscode
.idea
80 changes: 80 additions & 0 deletions packages/next-whatsapp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# @opensourceframework/next-whatsapp

WhatsApp integration for Next.js applications using whatsapp-web.js.

This package is extracted from the [itsalive](https://github.com/riceharvest/itsalive) project.

## Installation

```bash
pnpm add @opensourceframework/next-whatsapp
```

## Usage

### Basic Connection

```typescript
import { WhatsAppService } from '@opensourceframework/next-whatsapp';

// Connect and get QR code
const qrCode = await WhatsAppService.connect();
// Display QR code to user for scanning

// Check status
const status = WhatsAppService.getStatus();
console.log(`Connected: ${status.connected}, Tracking: ${status.tracking}`);
```

### Tracking Mode

Send periodic messages to track sleep patterns or for automated check-ins:

```typescript
// Start tracking with a list of contacts
await WhatsAppService.startTracking(['+1234567890', '+0987654321'], 15); // every 15 minutes

// Stop tracking
WhatsAppService.stopTracking();
```

### Low-Level Control

For more control, use the individual functions:

```typescript
import { getClient, initializeClient, startTracking, stopTracking, getStatus } from '@opensourceframework/next-whatsapp';

// Initialize the client
await initializeClient();

// Get the raw client for event listeners
const client = getClient();

client.on('message', (msg) => {
console.log('Received message:', msg.body);
});

// Start tracking with custom contacts
await startTracking(['+1234567890'], 15);
```

## Development

```bash
# Install dependencies
pnpm install

# Build
pnpm build

# Run tests
pnpm test

# Watch mode
pnpm dev
```

## License

MIT
23 changes: 23 additions & 0 deletions packages/next-whatsapp/llms.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# @opensourceframework/next-whatsapp - AI Summary

WhatsApp integration for Next.js applications using whatsapp-web.js. This package provides a simple API to connect to WhatsApp, send automated messages, and track delivery status. Extracted and maintained from the itsalive project in the OpenSource Framework monorepo.

## Maintainer Intent

- Provide a stable, well-maintained wrapper around whatsapp-web.js.
- Keep the API simple and intuitive for Next.js developers.
- Handle reconnection logic and rate limiting automatically.
- Allow flexible event handling for custom use cases.

## Installation

```bash
npm install @opensourceframework/next-whatsapp
# or
pnpm add @opensourceframework/next-whatsapp
```

## Links

- Documentation: https://github.com/riceharvest/opensourceframework/tree/main/packages/next-whatsapp#readme
- npm: https://www.npmjs.com/package/@opensourceframework/next-whatsapp
65 changes: 65 additions & 0 deletions packages/next-whatsapp/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"type": "module",
"name": "@opensourceframework/next-whatsapp",
"version": "0.1.0",
"description": "WhatsApp integration for Next.js applications using whatsapp-web.js",
"keywords": [
"nextjs",
"whatsapp",
"whatsapp-web",
"messaging"
],
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"files": [
"dist",
"llms.txt"
],
"sideEffects": false,
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"lint": "eslint . --ignore-pattern examples",
"typecheck": "tsc --noEmit",
"test": "vitest run --passWithNoTests",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage"
},
"repository": {
"type": "git",
"url": "git+https://github.com/riceharvest/opensourceframework.git",
"directory": "packages/next-whatsapp"
},
"author": "OpenSource Framework Contributors",
"license": "MIT",
"bugs": {
"url": "https://github.com/riceharvest/opensourceframework/issues?q=is%3Aissue+is%3Aopen+next-whatsapp"
},
"homepage": "https://github.com/riceharvest/opensourceframework/tree/main/packages/next-whatsapp#readme",
"devDependencies": {
"@types/node": "^25.4.0",
"@types/qrcode": "^1.5.6",
"eslint": "^10.0.3",
"tsup": "^8.5.1",
"typescript": "^5.9.3",
"vitest": "^2.1.9",
"@vitest/coverage-v8": "^2.1.9"
},
"dependencies": {
"qrcode": "^1.5.3",
"whatsapp-web.js": "^1.23.0"
},
"engines": {
"node": ">=18.0.0"
},
"publishConfig": {
"access": "public"
}
}
133 changes: 133 additions & 0 deletions packages/next-whatsapp/src/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { Client, LocalAuth, MessageAck } from 'whatsapp-web.js';

let client: Client | null = null;
let isTracking = false;
let trackingInterval: NodeJS.Timeout | null = null;
let reconnectAttempts = 0;
const maxReconnectAttempts = 5;
const reconnectDelay = 5000; // 5 seconds

export const getClient = (): Client => {
if (!client) {
client = new Client({
authStrategy: new LocalAuth(),
puppeteer: {
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox']
}
});

client.on('ready', () => {
console.log('WhatsApp client is ready!');
reconnectAttempts = 0; // Reset on successful connection
});

client.on('disconnected', (reason) => {
console.warn('WhatsApp client disconnected:', reason);
if (reconnectAttempts < maxReconnectAttempts) {
reconnectAttempts++;
console.info(`Attempting to reconnect (${reconnectAttempts}/${maxReconnectAttempts})...`);
setTimeout(() => {
initializeClient().catch((error) => {
console.error('Reconnection failed:', error);
});
}, reconnectDelay);
} else {
console.error('Max reconnection attempts reached. Please reconnect manually.');
}
});
}
return client;
}

export async function initializeClient(): Promise<Client> {
const whatsappClient = getClient();
await whatsappClient.initialize();
return whatsappClient;
}

async function sendMessageWithRetry(
clientInstance: Client,
chatId: string,
retryCount = 0,
maxRetries = 3
): Promise<void> {
if (!clientInstance.info) {
throw new Error('WhatsApp client not ready');
}

try {
// Send a zero-width space character as a ping
await clientInstance.sendMessage(chatId, '\u200B');
} catch (error: any) {
console.error('Error sending message:', error);
if (
error.message?.includes('rate limit') ||
error.message?.includes('too many requests')
) {
if (retryCount < maxRetries) {
const delay = Math.pow(2, retryCount) * 1000;
console.warn(`Rate limited, retrying in ${delay}ms...`);
await new Promise((resolve) => setTimeout(resolve, delay));
return sendMessageWithRetry(clientInstance, chatId, retryCount + 1, maxRetries);
} else {
throw new Error('Max retries exceeded due to rate limiting', { cause: error });
}
} else {
// Ignore other errors (like invalid contact) to keep loop running
console.warn(`Failed to send to ${chatId}: ${error.message}`);
}
}
}

export async function startTracking(contacts: string[], intervalMinutes: number = 15): Promise<void> {
if (isTracking) return;

const whatsappClient = getClient();
if (!whatsappClient.info) {
throw new Error('WhatsApp client not ready');
}

isTracking = true;

// Initial run
await runTrackingCycle(whatsappClient, contacts);

trackingInterval = setInterval(async () => {
await runTrackingCycle(whatsappClient, contacts);
}, intervalMinutes * 60 * 1000);
}

async function runTrackingCycle(whatsappClient: Client, contacts: string[]): Promise<void> {
try {
console.info(`Starting tracking cycle for ${contacts.length} contacts`);
for (const contact of contacts) {
const chatId = `${contact}@c.us`;
await sendMessageWithRetry(whatsappClient, chatId);
await new Promise((r) => setTimeout(r, 2000)); // Delay to be polite
}
} catch (error) {
console.error('Failed tracking cycle:', error);
}
}

export function stopTracking(): void {
if (trackingInterval) {
clearInterval(trackingInterval);
trackingInterval = null;
}
isTracking = false;
}

export function getStatus(): { connected: boolean; tracking: boolean } {
return {
connected: client?.info ? true : false,
tracking: isTracking,
};
}

// Optional: expose client events for advanced usage
export function onMessageAck(callback: (message: any, ack: MessageAck) => void): void {
const clientInstance = getClient();
clientInstance.on('message_ack', callback);
}
15 changes: 15 additions & 0 deletions packages/next-whatsapp/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Main service class for easy static access
export { WhatsAppService } from './service';

// Individual functions for more granular control
export {
getClient,
initializeClient,
startTracking,
stopTracking,
getStatus,
onMessageAck,
} from './client';

// Re-export types if needed
export type { Client } from 'whatsapp-web.js';
Loading
Loading