whatsapp-pi 1.0.44 → 1.0.45

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whatsapp-pi",
3
- "version": "1.0.44",
3
+ "version": "1.0.45",
4
4
  "type": "module",
5
5
  "description": "WhatsApp integration extension for Pi",
6
6
  "main": "whatsapp-pi.ts",
@@ -136,10 +136,11 @@ export class SessionManager {
136
136
  public async saveConfig() {
137
137
  const tempPath = `${this.configPath}.${process.pid}.${Date.now()}.tmp`;
138
138
  try {
139
+ this.hasAuthState = this.hasAuthState || await this.hasCredentialsFile();
139
140
  const config = {
140
141
  allowList: this.allowList,
141
- blockList: this.blockList,
142
- ignoredNumbers: this.ignoredNumbers,
142
+ blockList: this.blockList,
143
+ ignoredNumbers: this.ignoredNumbers,
143
144
  status: this.status,
144
145
  hasAuthState: this.hasAuthState,
145
146
  openaiKey: this.openaiKey,
@@ -71,12 +71,16 @@ interface BoomLikeError {
71
71
  message?: string;
72
72
  }
73
73
 
74
- export class WhatsAppService {
75
- private socket?: WhatsAppSocketLike;
76
- private sessionManager: SessionManager;
77
- private messageSender: MessageSender;
78
- private isReconnecting = false;
79
- private verboseMode = false;
74
+ export class WhatsAppService {
75
+ private static readonly INITIAL_RECONNECT_DELAY_MS = 5_000;
76
+ private static readonly MAX_RECONNECT_DELAY_MS = 120_000;
77
+
78
+ private socket?: WhatsAppSocketLike;
79
+ private sessionManager: SessionManager;
80
+ private messageSender: MessageSender;
81
+ private isReconnecting = false;
82
+ private reconnectAttempts = 0;
83
+ private verboseMode = false;
80
84
  private onIncomingMessageRecorded?: (message: IncomingMessage) => void | Promise<void>;
81
85
  private saveCreds?: () => Promise<void>;
82
86
  private restoreBaileysConsoleFilter?: () => void;
@@ -164,12 +168,17 @@ export class WhatsAppService {
164
168
  return '';
165
169
  }
166
170
 
167
- private clearReconnectTimeout() {
168
- if (this.reconnectTimeout) {
169
- clearTimeout(this.reconnectTimeout);
170
- this.reconnectTimeout = undefined;
171
- }
172
- }
171
+ private clearReconnectTimeout() {
172
+ if (this.reconnectTimeout) {
173
+ clearTimeout(this.reconnectTimeout);
174
+ this.reconnectTimeout = undefined;
175
+ }
176
+ }
177
+
178
+ private getReconnectDelayMs(): number {
179
+ const delay = WhatsAppService.INITIAL_RECONNECT_DELAY_MS * (2 ** Math.max(0, this.reconnectAttempts - 1));
180
+ return Math.min(delay, WhatsAppService.MAX_RECONNECT_DELAY_MS);
181
+ }
173
182
 
174
183
  private cleanupSocket() {
175
184
  this.clearReconnectTimeout();
@@ -303,10 +312,11 @@ export class WhatsAppService {
303
312
  console.log('WhatsApp connection successfully opened');
304
313
  }
305
314
 
306
- this.isReconnecting = false;
307
- this.clearReconnectTimeout();
308
- await this.saveCreds?.();
309
- await this.sessionManager.markAuthStateAvailable();
315
+ this.isReconnecting = false;
316
+ this.reconnectAttempts = 0;
317
+ this.clearReconnectTimeout();
318
+ await this.saveCreds?.();
319
+ await this.sessionManager.markAuthStateAvailable();
310
320
  this.sessionManager.setStatus('connected');
311
321
  this.onStatusUpdate?.('| WhatsApp: Connected');
312
322
  }
@@ -354,6 +364,7 @@ export class WhatsAppService {
354
364
  }
355
365
  this.cleanupSocket();
356
366
  this.isReconnecting = false;
367
+ this.reconnectAttempts = 0;
357
368
  await this.sessionManager.setStatus('disconnected');
358
369
  if (!isBadMac) {
359
370
  this.onStatusUpdate?.('| WhatsApp: Disconnected');
@@ -361,26 +372,35 @@ export class WhatsAppService {
361
372
  return;
362
373
  }
363
374
 
364
- if (statusCode === DisconnectReason.connectionReplaced) {
365
- if (this.verboseMode) {
366
- console.error('Connection replaced - another instance connected');
367
- }
368
- this.onStatusUpdate?.('| WhatsApp: Conflict (Another Instance)');
369
- return;
370
- }
371
-
372
- if (shouldReconnect && !this.isReconnecting) {
373
- this.isReconnecting = true;
374
- this.onStatusUpdate?.('| WhatsApp: Reconnecting...');
375
- this.clearReconnectTimeout();
376
- this.reconnectTimeout = setTimeout(() => {
377
- this.isReconnecting = false;
378
- void this.start(options);
379
- }, 3000);
380
- } else if (!shouldReconnect) {
381
- this.sessionManager.setStatus('logged-out');
382
- this.onStatusUpdate?.('| WhatsApp: Disconnected');
383
- }
375
+ if (statusCode === DisconnectReason.connectionReplaced) {
376
+ if (this.verboseMode) {
377
+ console.error('Connection replaced - another instance connected');
378
+ }
379
+ this.cleanupSocket();
380
+ this.isReconnecting = false;
381
+ this.reconnectAttempts = 0;
382
+ await this.sessionManager.setStatus('disconnected');
383
+ this.onStatusUpdate?.('| WhatsApp: Conflict (Another Instance)');
384
+ return;
385
+ }
386
+
387
+ if (shouldReconnect && !this.isReconnecting) {
388
+ this.isReconnecting = true;
389
+ this.reconnectAttempts++;
390
+ const reconnectDelayMs = this.getReconnectDelayMs();
391
+ this.onStatusUpdate?.('| WhatsApp: Reconnecting...');
392
+ this.clearReconnectTimeout();
393
+ await this.saveCreds?.();
394
+ this.cleanupSocket();
395
+ this.reconnectTimeout = setTimeout(() => {
396
+ this.isReconnecting = false;
397
+ void this.start(options);
398
+ }, reconnectDelayMs);
399
+ } else if (!shouldReconnect) {
400
+ this.reconnectAttempts = 0;
401
+ this.sessionManager.setStatus('logged-out');
402
+ this.onStatusUpdate?.('| WhatsApp: Disconnected');
403
+ }
384
404
  }
385
405
 
386
406
  private extractText(message: IncomingMessageContent | undefined): string {
package/whatsapp-pi.ts CHANGED
@@ -60,7 +60,7 @@ export default function (pi: ExtensionAPI) {
60
60
  };
61
61
 
62
62
  // Initial status setup
63
- pi.on("session_start", async (_event, ctx) => {
63
+ pi.on("session_start", async (_event, ctx) => {
64
64
  _ctx = ctx;
65
65
  // Check verbose mode
66
66
  const isVerboseFlagSet = process.argv.includes("--verbose");
@@ -86,10 +86,10 @@ export default function (pi: ExtensionAPI) {
86
86
  await whatsappService.stop();
87
87
  }
88
88
  };
89
- whatsappService.setIncomingMessageRecorder(async (message) => {
90
- await recentsService.recordMessage({
91
- messageId: message.id,
92
- senderNumber: `+${message.remoteJid.split('@')[0]}`,
89
+ whatsappService.setIncomingMessageRecorder(async (message) => {
90
+ await recentsService.recordMessage({
91
+ messageId: message.id,
92
+ senderNumber: `+${message.remoteJid.split('@')[0]}`,
93
93
  senderName: message.pushName,
94
94
  text: message.text || '',
95
95
  direction: 'incoming',
@@ -97,20 +97,20 @@ export default function (pi: ExtensionAPI) {
97
97
  });
98
98
  });
99
99
 
100
- const savedStateEntry = [...ctx.sessionManager.getEntries()]
101
- .reverse()
102
- .find(entry => entry.type === "custom" && entry.customType === "whatsapp-state");
103
- const isWhatsappPiOn = pi.getFlag("whatsapp-pi-online") === true;
104
- const registered = await sessionManager.isRegistered();
105
-
106
- if (savedStateEntry) {
107
- const data = (savedStateEntry as { data?: any }).data;
108
- if (data.status) {
109
- const restoredStatus = data.status === 'connected' && !(isWhatsappPiOn && registered)
110
- ? 'disconnected'
111
- : data.status;
112
- await sessionManager.setStatus(restoredStatus);
113
- }
100
+ const savedStateEntry = [...ctx.sessionManager.getEntries()]
101
+ .reverse()
102
+ .find(entry => entry.type === "custom" && entry.customType === "whatsapp-state");
103
+ const isWhatsappPiOn = pi.getFlag("whatsapp-pi-online") === true;
104
+ const registered = await sessionManager.isRegistered();
105
+
106
+ if (savedStateEntry) {
107
+ const data = (savedStateEntry as { data?: any }).data;
108
+ if (data.status) {
109
+ const restoredStatus = data.status === 'connected' && !(isWhatsappPiOn && registered)
110
+ ? 'disconnected'
111
+ : data.status;
112
+ await sessionManager.setStatus(restoredStatus);
113
+ }
114
114
  if (Array.isArray(data.allowList)) {
115
115
  for (const n of data.allowList) {
116
116
  const num = typeof n === "string" ? n : n.number;
@@ -118,10 +118,10 @@ export default function (pi: ExtensionAPI) {
118
118
  await sessionManager.addNumber(num, name);
119
119
  }
120
120
  }
121
- }
122
-
123
- if (isWhatsappPiOn && registered) {
124
- ctx.ui.setStatus('whatsapp', '| WhatsApp: Auto-connecting...');
121
+ }
122
+
123
+ if (isWhatsappPiOn && registered) {
124
+ ctx.ui.setStatus('whatsapp', '| WhatsApp: Auto-connecting...');
125
125
 
126
126
  // Retry logic (max 3 attempts, 3s delay)
127
127
  let attempts = 0;
@@ -131,7 +131,7 @@ export default function (pi: ExtensionAPI) {
131
131
  attempts++;
132
132
  try {
133
133
  await whatsappService.start({ allowPairingOnAuthFailure: false });
134
- } catch {
134
+ } catch {
135
135
  if (attempts < maxAttempts) {
136
136
  ctx.ui.notify(`WhatsApp: Connection attempt ${attempts} failed. Retrying...`, 'warning');
137
137
  setTimeout(tryConnect, 3000);
@@ -143,11 +143,11 @@ export default function (pi: ExtensionAPI) {
143
143
  };
144
144
 
145
145
  await tryConnect();
146
- } else if (isWhatsappPiOn) {
147
- ctx.ui.notify('WhatsApp: Auto-connect requested, but no saved WhatsApp credentials were found. Use Connect WhatsApp once to scan the QR code.', 'warning');
148
- } else {
149
- ctx.ui.notify('WhatsApp: Use Connect / Reconnect WhatsApp. QR code will appear only if pairing is needed.', 'info');
150
- }
146
+ } else if (isWhatsappPiOn) {
147
+ ctx.ui.notify('WhatsApp: Auto-connect requested, but no saved WhatsApp credentials were found. Use Connect WhatsApp once to scan the QR code.', 'warning');
148
+ } else {
149
+ ctx.ui.notify('WhatsApp: Use Connect / Reconnect WhatsApp. QR code will appear only if pairing is needed.', 'info');
150
+ }
151
151
 
152
152
  ctx.ui.notify('WhatsApp: Session reset via /new is now fully supported.', 'info');
153
153
 
@@ -157,7 +157,7 @@ export default function (pi: ExtensionAPI) {
157
157
  if (code !== 0 && code !== 99) { // 99 is a common exit code for -v in some versions
158
158
  throw new Error(`pdftotext returned code ${code}`);
159
159
  }
160
- } catch {
160
+ } catch {
161
161
  ctx.ui.notify('WhatsApp: pdftotext not found. PDF document support will be limited to storage only.', 'warning');
162
162
  logger.warn('[WhatsApp-Pi] Warning: pdftotext not found in system PATH.');
163
163
  }
@@ -333,7 +333,7 @@ export default function (pi: ExtensionAPI) {
333
333
  } else {
334
334
  ctx.ui.notify(`Failed to send WhatsApp reply`, 'error');
335
335
  }
336
- } catch {
336
+ } catch {
337
337
  ctx.ui.notify(`Failed to send WhatsApp reply`, 'error');
338
338
  }
339
339
  }