@jep182/n8n-nodes-whatsthat 0.2.0 → 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/README.md CHANGED
@@ -73,8 +73,7 @@ Use this node to listen for:
73
73
  WhatsThat embeds Baileys directly in n8n.
74
74
 
75
75
  - session auth files are stored on disk
76
- - session metadata and linked targets can use n8n Data Tables when available
77
- - if Data Tables are not available, WhatsThat falls back to local JSON storage
76
+ - session metadata and linked targets are stored as local JSON files under the runtime storage path
78
77
 
79
78
  ## Quick Start
80
79
 
@@ -87,9 +86,9 @@ WhatsThat embeds Baileys directly in n8n.
87
86
 
88
87
  3. Add `WhatsThat Session`.
89
88
  4. Choose `Create Session`, then provide:
90
- - `Session ID`
91
- - `Label`
92
- - optional `Phone Number For Pairing`
89
+ - `Session ID (Internal)`: a stable unique ID such as `main-phone`
90
+ - `Label (Visible Name)`: a human-readable name such as `Luca personal phone`
91
+ - optional `Phone Number For Pairing`: full number with country code, digits only, without `00` or `+`
93
92
  5. Run `Connect Session`.
94
93
  6. Use the returned `pairingCode` or `qrDataUrl` to connect the device.
95
94
  7. Use `WhatsThat Targets` to discover and link chats/groups.
@@ -5,7 +5,7 @@ class WhatsThatRuntime {
5
5
  constructor() {
6
6
  this.name = 'whatsThatRuntime';
7
7
  this.displayName = 'WhatsThat Runtime';
8
- this.documentationUrl = 'https://github.com/jep182/whatsapp-bot';
8
+ this.documentationUrl = 'https://github.com/jepgambardella/n8n-nodes-whatsthat';
9
9
  this.properties = [
10
10
  {
11
11
  displayName: 'Storage Path',
@@ -13,14 +13,7 @@ class WhatsThatRuntime {
13
13
  type: 'string',
14
14
  default: '=/home/node/.n8n/whatsthat',
15
15
  required: true,
16
- description: 'Root folder used for session files and local fallback data',
17
- },
18
- {
19
- displayName: 'Use Data Tables',
20
- name: 'useDataTables',
21
- type: 'boolean',
22
- default: true,
23
- description: 'Use n8n Data Tables when available, with filesystem fallback',
16
+ description: 'Root folder used for session auth files and metadata',
24
17
  },
25
18
  ];
26
19
  }
@@ -4,12 +4,13 @@ exports.WhatsThatMessage = void 0;
4
4
  const n8n_workflow_1 = require("n8n-workflow");
5
5
  const access_1 = require("../../shared/access");
6
6
  const runtime_1 = require("../../shared/runtime");
7
+ const validation_1 = require("../../shared/validation");
7
8
  function buildPayload(context, itemIndex) {
8
9
  const messageType = context.getNodeParameter('messageType', itemIndex);
9
10
  const targetMode = context.getNodeParameter('targetMode', itemIndex);
10
11
  const deliveryMode = context.getNodeParameter('deliveryMode', itemIndex, 'native');
11
12
  const payload = {
12
- sessionId: context.getNodeParameter('sessionId', itemIndex),
13
+ sessionId: (0, validation_1.requireSessionId)(context.getNodeParameter('sessionId', itemIndex)),
13
14
  type: messageType,
14
15
  };
15
16
  if (targetMode === 'alias') {
@@ -72,7 +73,14 @@ class WhatsThatMessage {
72
73
  outputs: ['main'],
73
74
  credentials: [{ name: 'whatsThatRuntime', required: true }],
74
75
  properties: [
75
- { displayName: 'Session ID', name: 'sessionId', type: 'string', default: '' },
76
+ {
77
+ displayName: 'Session ID (Internal)',
78
+ name: 'sessionId',
79
+ type: 'string',
80
+ default: '',
81
+ required: true,
82
+ description: 'The unique session ID created in the WhatsThat Session node.',
83
+ },
76
84
  {
77
85
  displayName: 'Target Mode',
78
86
  name: 'targetMode',
@@ -4,6 +4,7 @@ exports.WhatsThatSession = void 0;
4
4
  const n8n_workflow_1 = require("n8n-workflow");
5
5
  const access_1 = require("../../shared/access");
6
6
  const runtime_1 = require("../../shared/runtime");
7
+ const validation_1 = require("../../shared/validation");
7
8
  class WhatsThatSession {
8
9
  constructor() {
9
10
  this.description = {
@@ -33,19 +34,22 @@ class WhatsThatSession {
33
34
  ],
34
35
  },
35
36
  {
36
- displayName: 'Session ID',
37
+ displayName: 'Session ID (Internal)',
37
38
  name: 'sessionId',
38
39
  type: 'string',
39
40
  default: '',
41
+ required: true,
42
+ description: 'Required unique ID for this session. Use a stable internal value such as "main-phone" or "support-team".',
40
43
  displayOptions: {
41
44
  hide: { operation: ['list'] },
42
45
  },
43
46
  },
44
47
  {
45
- displayName: 'Label',
48
+ displayName: 'Label (Visible Name)',
46
49
  name: 'label',
47
50
  type: 'string',
48
51
  default: '',
52
+ description: 'Human-readable name shown in results. Example: "Luca personal phone" or "Support number".',
49
53
  displayOptions: {
50
54
  show: { operation: ['create'] },
51
55
  },
@@ -55,7 +59,7 @@ class WhatsThatSession {
55
59
  name: 'phoneNumberForPairing',
56
60
  type: 'string',
57
61
  default: '',
58
- description: 'Optional. When provided, WhatsThat will request a pairing code when possible',
62
+ description: 'Optional. Full phone number with country code, digits only, without 00 or +. Example: 393331234567.',
59
63
  displayOptions: {
60
64
  show: { operation: ['create'] },
61
65
  },
@@ -70,14 +74,17 @@ class WhatsThatSession {
70
74
  for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
71
75
  try {
72
76
  const operation = this.getNodeParameter('operation', itemIndex);
73
- const sessionId = this.getNodeParameter('sessionId', itemIndex, '');
77
+ const rawSessionId = this.getNodeParameter('sessionId', itemIndex, '');
78
+ const sessionId = operation === 'list' ? (0, validation_1.normalizeSessionId)(rawSessionId) : (0, validation_1.requireSessionId)(rawSessionId);
74
79
  let json;
80
+ const label = this.getNodeParameter('label', itemIndex, '').trim();
81
+ const phoneNumberForPairing = this.getNodeParameter('phoneNumberForPairing', itemIndex, '').trim();
75
82
  switch (operation) {
76
83
  case 'create':
77
84
  json = await runtime_1.registry.ensureSession(access.paths.root, access, {
78
85
  sessionId,
79
- label: this.getNodeParameter('label', itemIndex) || sessionId,
80
- phoneNumberForPairing: this.getNodeParameter('phoneNumberForPairing', itemIndex, ''),
86
+ label: label || sessionId,
87
+ phoneNumberForPairing,
81
88
  });
82
89
  break;
83
90
  case 'connect':
@@ -4,6 +4,7 @@ exports.WhatsThatTargets = void 0;
4
4
  const n8n_workflow_1 = require("n8n-workflow");
5
5
  const access_1 = require("../../shared/access");
6
6
  const runtime_1 = require("../../shared/runtime");
7
+ const validation_1 = require("../../shared/validation");
7
8
  class WhatsThatTargets {
8
9
  constructor() {
9
10
  this.description = {
@@ -31,10 +32,12 @@ class WhatsThatTargets {
31
32
  ],
32
33
  },
33
34
  {
34
- displayName: 'Session ID',
35
+ displayName: 'Session ID (Internal)',
35
36
  name: 'sessionId',
36
37
  type: 'string',
37
38
  default: '',
39
+ required: true,
40
+ description: 'The unique session ID created in the WhatsThat Session node.',
38
41
  },
39
42
  {
40
43
  displayName: 'Target JID',
@@ -64,7 +67,7 @@ class WhatsThatTargets {
64
67
  for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
65
68
  try {
66
69
  const operation = this.getNodeParameter('operation', itemIndex);
67
- const sessionId = this.getNodeParameter('sessionId', itemIndex);
70
+ const sessionId = (0, validation_1.requireSessionId)(this.getNodeParameter('sessionId', itemIndex));
68
71
  let json;
69
72
  switch (operation) {
70
73
  case 'listDiscovered':
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WhatsThatTrigger = void 0;
4
4
  const runtime_1 = require("../../shared/runtime");
5
+ const validation_1 = require("../../shared/validation");
5
6
  class WhatsThatTrigger {
6
7
  constructor() {
7
8
  this.description = {
@@ -16,7 +17,14 @@ class WhatsThatTrigger {
16
17
  outputs: ['main'],
17
18
  credentials: [{ name: 'whatsThatRuntime', required: true }],
18
19
  properties: [
19
- { displayName: 'Session ID', name: 'sessionId', type: 'string', default: '' },
20
+ {
21
+ displayName: 'Session ID (Internal)',
22
+ name: 'sessionId',
23
+ type: 'string',
24
+ default: '',
25
+ required: true,
26
+ description: 'The unique session ID created in the WhatsThat Session node.',
27
+ },
20
28
  {
21
29
  displayName: 'Event',
22
30
  name: 'eventName',
@@ -38,7 +46,7 @@ class WhatsThatTrigger {
38
46
  };
39
47
  }
40
48
  async trigger() {
41
- const sessionId = this.getNodeParameter('sessionId');
49
+ const sessionId = (0, validation_1.requireSessionId)(this.getNodeParameter('sessionId'));
42
50
  const eventName = this.getNodeParameter('eventName');
43
51
  const handler = (event) => {
44
52
  if (event.sessionId !== sessionId)
@@ -1,8 +1,7 @@
1
- import type { IDataObject, IExecuteFunctions, ILoadOptionsFunctions, IDataTableProjectAggregateService, IDataTableProjectService } from 'n8n-workflow';
1
+ import type { IDataObject, IExecuteFunctions, ILoadOptionsFunctions } from 'n8n-workflow';
2
2
  export type NodeContext = IExecuteFunctions | ILoadOptionsFunctions;
3
3
  export interface RuntimeConfig {
4
4
  storagePath: string;
5
- useDataTables: boolean;
6
5
  }
7
6
  export declare function getRuntimeConfig(context: NodeContext): Promise<RuntimeConfig>;
8
7
  export declare function runtimePaths(config: RuntimeConfig): {
@@ -10,6 +9,4 @@ export declare function runtimePaths(config: RuntimeConfig): {
10
9
  authRoot: string;
11
10
  tablesFallback: string;
12
11
  };
13
- export declare function getAggregateProxy(context: NodeContext): Promise<IDataTableProjectAggregateService | null>;
14
- export declare function getTableProxy(context: NodeContext, tableId: string): Promise<IDataTableProjectService | null>;
15
12
  export declare function asDataObject(value: unknown): IDataObject;
@@ -5,8 +5,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getRuntimeConfig = getRuntimeConfig;
7
7
  exports.runtimePaths = runtimePaths;
8
- exports.getAggregateProxy = getAggregateProxy;
9
- exports.getTableProxy = getTableProxy;
10
8
  exports.asDataObject = asDataObject;
11
9
  const node_path_1 = __importDefault(require("node:path"));
12
10
  async function getRuntimeConfig(context) {
@@ -14,7 +12,6 @@ async function getRuntimeConfig(context) {
14
12
  const storagePath = String(credentials.storagePath || '/home/node/.n8n/whatsthat');
15
13
  return {
16
14
  storagePath,
17
- useDataTables: Boolean(credentials.useDataTables ?? true),
18
15
  };
19
16
  }
20
17
  function runtimePaths(config) {
@@ -24,18 +21,6 @@ function runtimePaths(config) {
24
21
  tablesFallback: node_path_1.default.join(config.storagePath, 'tables'),
25
22
  };
26
23
  }
27
- async function getAggregateProxy(context) {
28
- if (!context.helpers.getDataTableAggregateProxy) {
29
- return null;
30
- }
31
- return context.helpers.getDataTableAggregateProxy();
32
- }
33
- async function getTableProxy(context, tableId) {
34
- if (!context.helpers.getDataTableProxy) {
35
- return null;
36
- }
37
- return context.helpers.getDataTableProxy(tableId);
38
- }
39
24
  function asDataObject(value) {
40
25
  return value;
41
26
  }
@@ -7,6 +7,6 @@ type StorePayloadMap = {
7
7
  discovered_targets: DiscoveredTarget[];
8
8
  dedup: DedupRecord[];
9
9
  };
10
- export declare function listRecords<T extends TableName>(context: NodeContext, config: RuntimeConfig, tableName: T): Promise<StorePayloadMap[T]>;
11
- export declare function saveRecords<T extends TableName>(context: NodeContext, config: RuntimeConfig, tableName: T, data: StorePayloadMap[T]): Promise<void>;
10
+ export declare function listRecords<T extends TableName>(_context: NodeContext, config: RuntimeConfig, tableName: T): Promise<StorePayloadMap[T]>;
11
+ export declare function saveRecords<T extends TableName>(_context: NodeContext, config: RuntimeConfig, tableName: T, data: StorePayloadMap[T]): Promise<void>;
12
12
  export {};
@@ -8,94 +8,14 @@ exports.saveRecords = saveRecords;
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
9
  const context_1 = require("./context");
10
10
  const fs_1 = require("./fs");
11
- const tableSchemas = {
12
- sessions: [
13
- { name: 'sessionId', type: 'string' },
14
- { name: 'label', type: 'string' },
15
- { name: 'status', type: 'string' },
16
- { name: 'phone', type: 'string' },
17
- { name: 'pairingCode', type: 'string' },
18
- { name: 'qr', type: 'string' },
19
- { name: 'qrDataUrl', type: 'string' },
20
- { name: 'phoneNumberForPairing', type: 'string' },
21
- { name: 'lastSeenAt', type: 'string' },
22
- { name: 'createdAt', type: 'string' },
23
- { name: 'updatedAt', type: 'string' },
24
- { name: 'lastDisconnectReason', type: 'string' },
25
- ],
26
- linked_targets: [
27
- { name: 'alias', type: 'string' },
28
- { name: 'sessionId', type: 'string' },
29
- { name: 'jid', type: 'string' },
30
- { name: 'displayName', type: 'string' },
31
- { name: 'targetType', type: 'string' },
32
- { name: 'lastSeenAt', type: 'string' },
33
- { name: 'createdAt', type: 'string' },
34
- ],
35
- discovered_targets: [
36
- { name: 'sessionId', type: 'string' },
37
- { name: 'jid', type: 'string' },
38
- { name: 'displayName', type: 'string' },
39
- { name: 'targetType', type: 'string' },
40
- { name: 'lastSeenAt', type: 'string' },
41
- ],
42
- dedup: [
43
- { name: 'sessionId', type: 'string' },
44
- { name: 'messageId', type: 'string' },
45
- { name: 'expiresAt', type: 'string' },
46
- ],
47
- };
48
- async function ensureTable(context, tableName) {
49
- const aggregate = await (0, context_1.getAggregateProxy)(context);
50
- if (!aggregate) {
51
- return null;
52
- }
53
- const existing = await aggregate.getManyAndCount({ filter: { name: tableName } });
54
- const found = existing.data.find((table) => table.name === tableName);
55
- if (found) {
56
- return found.id;
57
- }
58
- const created = await aggregate.createDataTable({
59
- name: tableName,
60
- columns: tableSchemas[tableName],
61
- });
62
- return created.id;
63
- }
64
11
  async function fallbackFile(config, tableName) {
65
12
  const filePath = node_path_1.default.join((0, context_1.runtimePaths)(config).tablesFallback, `${tableName}.json`);
66
13
  await (0, fs_1.ensureDir)(node_path_1.default.dirname(filePath));
67
14
  return filePath;
68
15
  }
69
- async function listRecords(context, config, tableName) {
70
- if (config.useDataTables) {
71
- const tableId = await ensureTable(context, tableName);
72
- if (tableId) {
73
- const table = await (0, context_1.getTableProxy)(context, tableId);
74
- if (table) {
75
- const rows = await table.getManyRowsAndCount({});
76
- return rows.data;
77
- }
78
- }
79
- }
16
+ async function listRecords(_context, config, tableName) {
80
17
  return (0, fs_1.readJson)(await fallbackFile(config, tableName), []);
81
18
  }
82
- async function saveRecords(context, config, tableName, data) {
83
- if (config.useDataTables) {
84
- const tableId = await ensureTable(context, tableName);
85
- if (tableId) {
86
- const table = await (0, context_1.getTableProxy)(context, tableId);
87
- if (table) {
88
- await table.deleteDataTable();
89
- const recreatedTableId = await ensureTable(context, tableName);
90
- const recreated = recreatedTableId
91
- ? await (0, context_1.getTableProxy)(context, recreatedTableId)
92
- : null;
93
- if (recreated && data.length > 0) {
94
- await recreated.insertRows(data, 'count');
95
- }
96
- return;
97
- }
98
- }
99
- }
19
+ async function saveRecords(_context, config, tableName, data) {
100
20
  await (0, fs_1.writeJson)(await fallbackFile(config, tableName), data);
101
21
  }
@@ -0,0 +1,2 @@
1
+ export declare function normalizeSessionId(sessionId: string): string;
2
+ export declare function requireSessionId(sessionId: string): string;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeSessionId = normalizeSessionId;
4
+ exports.requireSessionId = requireSessionId;
5
+ function normalizeSessionId(sessionId) {
6
+ return sessionId.trim();
7
+ }
8
+ function requireSessionId(sessionId) {
9
+ const normalized = normalizeSessionId(sessionId);
10
+ if (!normalized) {
11
+ throw new Error('Session ID is required. Use a stable internal identifier such as "main-phone" or "support-team".');
12
+ }
13
+ return normalized;
14
+ }
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@jep182/n8n-nodes-whatsthat",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
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",
7
7
  "types": "dist/index.d.ts",
8
- "homepage": "https://github.com/jep182/whatsapp-bot",
8
+ "homepage": "https://github.com/jepgambardella/n8n-nodes-whatsthat",
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "git+https://github.com/jep182/whatsapp-bot.git"
11
+ "url": "git+https://github.com/jepgambardella/n8n-nodes-whatsthat.git"
12
12
  },
13
13
  "bugs": {
14
- "url": "https://github.com/jep182/whatsapp-bot/issues"
14
+ "url": "https://github.com/jepgambardella/n8n-nodes-whatsthat/issues"
15
15
  },
16
16
  "author": "jep182",
17
17
  "keywords": [