@jep182/n8n-nodes-whatsthat 0.3.0 → 0.4.2

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/README.md CHANGED
@@ -27,6 +27,7 @@ Use this node to:
27
27
  When pairing is available, the node returns:
28
28
 
29
29
  - `pairingCode`
30
+ - `qrCodeUrl`
30
31
  - `qr`
31
32
  - `qrDataUrl`
32
33
 
@@ -85,15 +86,19 @@ WhatsThat embeds Baileys directly in n8n.
85
86
  ```
86
87
 
87
88
  3. Add `WhatsThat Session`.
88
- 4. Choose `Create Session`, then provide:
89
+ 4. Choose `Ensure Session`, then provide:
89
90
  - `Session ID (Internal)`: a stable unique ID such as `main-phone`
90
91
  - `Label (Visible Name)`: a human-readable name such as `Luca personal phone`
91
92
  - optional `Phone Number For Pairing`: full number with country code, digits only, without `00` or `+`
92
- 5. Run `Connect Session`.
93
- 6. Use the returned `pairingCode` or `qrDataUrl` to connect the device.
93
+ 5. Set `Return When` to `Pairing Is Ready Or Connected` for first-time pairing.
94
+ 6. Use the returned `pairingCode`, `qrCodeUrl`, or `qrDataUrl` to connect the device.
94
95
  7. Use `WhatsThat Targets` to discover and link chats/groups.
95
96
  8. Use `WhatsThat Message` to send messages by alias or raw JID.
96
97
 
98
+ Example workflow:
99
+
100
+ - [`examples/register-number.workflow.json`](./examples/register-number.workflow.json)
101
+
97
102
  ## Media Delivery
98
103
 
99
104
  For `Image` and `Video` messages:
@@ -13,7 +13,7 @@ class WhatsThatSession {
13
13
  icon: 'file:whatsthat.svg',
14
14
  group: ['transform'],
15
15
  version: 1,
16
- description: 'Manage embedded Baileys sessions for WhatsThat',
16
+ description: 'Create, connect, and manage embedded Baileys sessions for WhatsThat',
17
17
  defaults: { name: 'WhatsThat Session' },
18
18
  inputs: ['main'],
19
19
  outputs: ['main'],
@@ -23,10 +23,9 @@ class WhatsThatSession {
23
23
  displayName: 'Operation',
24
24
  name: 'operation',
25
25
  type: 'options',
26
- default: 'create',
26
+ default: 'ensure',
27
27
  options: [
28
- { name: 'Create Session', value: 'create' },
29
- { name: 'Connect Session', value: 'connect' },
28
+ { name: 'Ensure Session', value: 'ensure' },
30
29
  { name: 'List Sessions', value: 'list' },
31
30
  { name: 'Get Session Status', value: 'status' },
32
31
  { name: 'Disconnect Session', value: 'disconnect' },
@@ -51,7 +50,7 @@ class WhatsThatSession {
51
50
  default: '',
52
51
  description: 'Human-readable name shown in results. Example: "Luca personal phone" or "Support number".',
53
52
  displayOptions: {
54
- show: { operation: ['create'] },
53
+ show: { operation: ['ensure'] },
55
54
  },
56
55
  },
57
56
  {
@@ -61,7 +60,34 @@ class WhatsThatSession {
61
60
  default: '',
62
61
  description: 'Optional. Full phone number with country code, digits only, without 00 or +. Example: 393331234567.',
63
62
  displayOptions: {
64
- show: { operation: ['create'] },
63
+ show: { operation: ['ensure'] },
64
+ },
65
+ },
66
+ {
67
+ displayName: 'Return When',
68
+ name: 'waitFor',
69
+ type: 'options',
70
+ default: 'pairing_or_connected',
71
+ options: [
72
+ { name: 'Pairing Is Ready Or Connected', value: 'pairing_or_connected' },
73
+ { name: 'Connected', value: 'connected' },
74
+ ],
75
+ description: 'Return as soon as pairing data is ready, or wait until the session is fully connected.',
76
+ displayOptions: {
77
+ show: { operation: ['ensure'] },
78
+ },
79
+ },
80
+ {
81
+ displayName: 'Timeout Seconds',
82
+ name: 'timeoutSeconds',
83
+ type: 'number',
84
+ typeOptions: {
85
+ minValue: 1,
86
+ },
87
+ default: 20,
88
+ description: 'Maximum time to wait before returning the latest known session status.',
89
+ displayOptions: {
90
+ show: { operation: ['ensure'] },
65
91
  },
66
92
  },
67
93
  ],
@@ -77,19 +103,22 @@ class WhatsThatSession {
77
103
  const rawSessionId = this.getNodeParameter('sessionId', itemIndex, '');
78
104
  const sessionId = operation === 'list' ? (0, validation_1.normalizeSessionId)(rawSessionId) : (0, validation_1.requireSessionId)(rawSessionId);
79
105
  let json;
80
- const label = this.getNodeParameter('label', itemIndex, '').trim();
81
- const phoneNumberForPairing = this.getNodeParameter('phoneNumberForPairing', itemIndex, '').trim();
82
106
  switch (operation) {
83
- case 'create':
84
- json = await runtime_1.registry.ensureSession(access.paths.root, access, {
107
+ case 'ensure': {
108
+ const label = this.getNodeParameter('label', itemIndex, '').trim();
109
+ const phoneNumberForPairing = this.getNodeParameter('phoneNumberForPairing', itemIndex, '').trim();
110
+ const waitFor = this.getNodeParameter('waitFor', itemIndex);
111
+ const timeoutSeconds = this.getNodeParameter('timeoutSeconds', itemIndex, 20);
112
+ json = await runtime_1.registry.ensureConnectedSession(access.paths.root, access, {
85
113
  sessionId,
86
114
  label: label || sessionId,
87
115
  phoneNumberForPairing,
116
+ }, {
117
+ waitFor,
118
+ timeoutMs: timeoutSeconds * 1000,
88
119
  });
89
120
  break;
90
- case 'connect':
91
- json = await runtime_1.registry.connectSession(access.paths.root, access, sessionId);
92
- break;
121
+ }
93
122
  case 'list':
94
123
  json = await runtime_1.registry.listSessions(access);
95
124
  break;
@@ -46,6 +46,14 @@ declare class WhatsThatRegistry extends EventEmitter {
46
46
  phoneNumberForPairing?: string;
47
47
  }): Promise<SessionRecord>;
48
48
  connectSession(storageRoot: string, access: DataAccess, sessionId: string): Promise<SessionRecord>;
49
+ ensureConnectedSession(storageRoot: string, access: DataAccess, input: {
50
+ sessionId: string;
51
+ label: string;
52
+ phoneNumberForPairing?: string;
53
+ }, options?: {
54
+ waitFor?: 'pairing_or_connected' | 'connected';
55
+ timeoutMs?: number;
56
+ }): Promise<SessionRecord>;
49
57
  listSessions(access: DataAccess): Promise<SessionRecord[]>;
50
58
  getSession(access: DataAccess, sessionId: string): Promise<SessionRecord | undefined>;
51
59
  disconnectSession(access: DataAccess, sessionId: string): Promise<SessionRecord | undefined>;
@@ -61,7 +69,10 @@ declare class WhatsThatRegistry extends EventEmitter {
61
69
  private upsertSession;
62
70
  private upsertDiscoveredTarget;
63
71
  private emitRuntime;
72
+ private waitForSessionState;
73
+ private matchesWaitTarget;
64
74
  private required;
75
+ private buildQrCodeUrl;
65
76
  }
66
77
  export declare const registry: WhatsThatRegistry;
67
78
  export declare function extractMessageText(message: proto.IMessage | null | undefined): string | undefined;
@@ -98,10 +98,12 @@ class WhatsThatRegistry extends node_events_1.EventEmitter {
98
98
  if (update.qr) {
99
99
  qrcode_terminal_1.default.generate(update.qr, { small: true });
100
100
  const qrDataUrl = await qrcode_1.default.toDataURL(update.qr);
101
+ const qrCodeUrl = this.buildQrCodeUrl(update.qr);
101
102
  const pairingRecord = {
102
103
  ...current,
103
104
  status: 'pairing',
104
105
  qr: update.qr,
106
+ qrCodeUrl,
105
107
  qrDataUrl,
106
108
  updatedAt: new Date().toISOString(),
107
109
  lastSeenAt: new Date().toISOString(),
@@ -128,6 +130,7 @@ class WhatsThatRegistry extends node_events_1.EventEmitter {
128
130
  status: 'connected',
129
131
  phone: socket.user?.id,
130
132
  qr: undefined,
133
+ qrCodeUrl: undefined,
131
134
  qrDataUrl: undefined,
132
135
  pairingCode: undefined,
133
136
  updatedAt: new Date().toISOString(),
@@ -216,6 +219,17 @@ class WhatsThatRegistry extends node_events_1.EventEmitter {
216
219
  this.sockets.set(sessionId, socket);
217
220
  return (await this.getSession(access, sessionId)) ?? starting;
218
221
  }
222
+ async ensureConnectedSession(storageRoot, access, input, options) {
223
+ await this.ensureSession(storageRoot, access, input);
224
+ const current = await this.getSession(access, input.sessionId);
225
+ if (!current) {
226
+ throw new Error(`Unknown session ${input.sessionId}`);
227
+ }
228
+ if (!this.sockets.has(input.sessionId) && current.status !== 'connected') {
229
+ await this.connectSession(storageRoot, access, input.sessionId);
230
+ }
231
+ return this.waitForSessionState(access, input.sessionId, options?.waitFor ?? 'pairing_or_connected', options?.timeoutMs ?? 20000);
232
+ }
219
233
  async listSessions(access) {
220
234
  const sessions = await access.listSessions();
221
235
  return sessions.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
@@ -448,12 +462,61 @@ class WhatsThatRegistry extends node_events_1.EventEmitter {
448
462
  emitRuntime(event) {
449
463
  this.emit('event', event);
450
464
  }
465
+ async waitForSessionState(access, sessionId, waitFor, timeoutMs) {
466
+ const current = await this.getSession(access, sessionId);
467
+ if (!current) {
468
+ throw new Error(`Unknown session ${sessionId}`);
469
+ }
470
+ if (this.matchesWaitTarget(current, waitFor)) {
471
+ return current;
472
+ }
473
+ return new Promise((resolve) => {
474
+ let settled = false;
475
+ let timeout;
476
+ const cleanup = () => {
477
+ if (timeout)
478
+ clearTimeout(timeout);
479
+ this.off('event', handler);
480
+ };
481
+ const settle = async () => {
482
+ if (settled)
483
+ return;
484
+ settled = true;
485
+ cleanup();
486
+ resolve((await this.getSession(access, sessionId)) ?? current);
487
+ };
488
+ const handler = async (event) => {
489
+ if (event.sessionId !== sessionId)
490
+ return;
491
+ const latest = await this.getSession(access, sessionId);
492
+ if (latest && this.matchesWaitTarget(latest, waitFor)) {
493
+ await settle();
494
+ }
495
+ };
496
+ this.on('event', handler);
497
+ timeout = setTimeout(() => {
498
+ void settle();
499
+ }, timeoutMs);
500
+ });
501
+ }
502
+ matchesWaitTarget(record, waitFor) {
503
+ if (record.status === 'connected') {
504
+ return true;
505
+ }
506
+ if (waitFor === 'pairing_or_connected' && record.status === 'pairing') {
507
+ return true;
508
+ }
509
+ return false;
510
+ }
451
511
  required(value, field) {
452
512
  if (value === undefined || value === null || value === '') {
453
513
  throw new Error(`Missing required field ${field}`);
454
514
  }
455
515
  return value;
456
516
  }
517
+ buildQrCodeUrl(qr) {
518
+ return `https://quickchart.io/qr?text=${encodeURIComponent(qr)}`;
519
+ }
457
520
  }
458
521
  exports.registry = new WhatsThatRegistry();
459
522
  function extractMessageText(message) {
@@ -7,6 +7,7 @@ export interface SessionRecord {
7
7
  phone?: string;
8
8
  pairingCode?: string;
9
9
  qr?: string;
10
+ qrCodeUrl?: string;
10
11
  qrDataUrl?: string;
11
12
  phoneNumberForPairing?: string;
12
13
  lastSeenAt: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jep182/n8n-nodes-whatsthat",
3
- "version": "0.3.0",
3
+ "version": "0.4.2",
4
4
  "description": "n8n community nodes with embedded Baileys runtime for multi-session chat automation",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",