@ezychat/wa 0.1.56 → 0.1.57

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.
@@ -1,6 +1,6 @@
1
1
  import { EventEmitter } from 'node:events';
2
2
  import type { WaStatus } from '@ezychat/shared';
3
- import { type SendResult, type WaAdapter, type WaAdapterEvents, type WaContactAlias, type WaGroupInfo, type WaIncomingMessage, type WaReaction, type WaReactionTarget, type WaSendFile, type WaSendOptions } from '../types.js';
3
+ import { type SendResult, type WaAdapter, type WaAdapterEvents, type WaCapabilities, type WaContactAlias, type WaGroupInfo, type WaIncomingMessage, type WaReaction, type WaReactionTarget, type WaSendFile, type WaSendOptions, type WaTemplate, type WaTemplateSend } from '../types.js';
4
4
  export interface FakeSentRecord {
5
5
  chatJid: string;
6
6
  text?: string;
@@ -44,11 +44,29 @@ export declare class FakeWaAdapter extends EventEmitter implements WaAdapter {
44
44
  chatJid: string;
45
45
  presence: 'composing' | 'paused';
46
46
  }>;
47
+ /** Every template sent (adapters given `templates`). */
48
+ readonly templateSends: Array<{
49
+ chatJid: string;
50
+ template: WaTemplateSend;
51
+ id: string;
52
+ }>;
53
+ /** What this fake connection can do; absent (a linked phone) unless given. */
54
+ readonly capabilities?: WaCapabilities;
55
+ /** Set when `templates` is given: the approved templates (offline → WaUnavailableError). */
56
+ readonly listTemplates?: () => Promise<WaTemplate[]>;
57
+ /** Set when `templates` is given: records the send like `sendText`. */
58
+ readonly sendTemplate?: (chatJid: string, template: WaTemplateSend) => Promise<SendResult>;
59
+ /** The templates `listTemplates` answers (tests may change them). */
60
+ templates: WaTemplate[];
61
+ /** Number of listTemplates calls, for tests that check caching. */
62
+ templateListCalls: number;
47
63
  /**
48
64
  * `me`: the linked account (default the fake number 60000000000); `idPrefix`: start of every
49
65
  * message id (default `FAKE`), so adapters of different inboxes never share an id.
50
66
  * `awaitScan`: a phone that was never linked — `connect()` shows a QR code and stays there
51
67
  * until `setConnected(true)` (the dev stand-in for the scan).
68
+ * `capabilities`: what the connection reports (e.g. a Business API stand-in); `templates`: its
69
+ * approved templates, which also turn on `listTemplates`/`sendTemplate`.
52
70
  */
53
71
  constructor(opts?: {
54
72
  autoOpen?: boolean;
@@ -58,6 +76,8 @@ export declare class FakeWaAdapter extends EventEmitter implements WaAdapter {
58
76
  };
59
77
  idPrefix?: string;
60
78
  awaitScan?: boolean;
79
+ capabilities?: WaCapabilities;
80
+ templates?: WaTemplate[];
61
81
  });
62
82
  get status(): WaStatus;
63
83
  on<K extends keyof WaAdapterEvents>(ev: K, fn: (...a: WaAdapterEvents[K]) => void): this;
@@ -26,14 +26,49 @@ export class FakeWaAdapter extends EventEmitter {
26
26
  reactions = [];
27
27
  reads = [];
28
28
  presences = [];
29
+ /** Every template sent (adapters given `templates`). */
30
+ templateSends = [];
31
+ /** What this fake connection can do; absent (a linked phone) unless given. */
32
+ capabilities;
33
+ /** Set when `templates` is given: the approved templates (offline → WaUnavailableError). */
34
+ listTemplates;
35
+ /** Set when `templates` is given: records the send like `sendText`. */
36
+ sendTemplate;
37
+ /** The templates `listTemplates` answers (tests may change them). */
38
+ templates = [];
39
+ /** Number of listTemplates calls, for tests that check caching. */
40
+ templateListCalls = 0;
29
41
  /**
30
42
  * `me`: the linked account (default the fake number 60000000000); `idPrefix`: start of every
31
43
  * message id (default `FAKE`), so adapters of different inboxes never share an id.
32
44
  * `awaitScan`: a phone that was never linked — `connect()` shows a QR code and stays there
33
45
  * until `setConnected(true)` (the dev stand-in for the scan).
46
+ * `capabilities`: what the connection reports (e.g. a Business API stand-in); `templates`: its
47
+ * approved templates, which also turn on `listTemplates`/`sendTemplate`.
34
48
  */
35
49
  constructor(opts) {
36
50
  super();
51
+ if (opts?.capabilities)
52
+ this.capabilities = { ...opts.capabilities };
53
+ if (opts?.templates) {
54
+ this.templates = opts.templates.map((t) => ({ ...t, examples: [...t.examples] }));
55
+ this.listTemplates = async () => {
56
+ if (this._status.state !== 'open')
57
+ throw new WaUnavailableError();
58
+ this.templateListCalls++;
59
+ return this.templates.map((t) => ({ ...t, examples: [...t.examples] }));
60
+ };
61
+ this.sendTemplate = async (chatJid, template) => {
62
+ this.beforeSend();
63
+ const id = `${this.idPrefix}-OUT-${FAKE_RUN}-${++this.outCounter}`;
64
+ this.templateSends.push({
65
+ chatJid,
66
+ template: { ...template, params: [...template.params] },
67
+ id,
68
+ });
69
+ return this.afterSend(chatJid, id);
70
+ };
71
+ }
37
72
  this.autoOpen = opts?.autoOpen ?? true;
38
73
  this.awaitScan = opts?.awaitScan ?? false;
39
74
  this.me = { ...(opts?.me ?? FAKE_ME) };
package/dist/types.d.ts CHANGED
@@ -159,8 +159,57 @@ export interface WaSendFile {
159
159
  waveform: Uint8Array;
160
160
  };
161
161
  }
162
+ /**
163
+ * What a connection type can do, so the core shows only what works (never by type name). An adapter
164
+ * without `capabilities` is a linked phone: QR/pairing, history import, groups, no reply window.
165
+ */
166
+ export interface WaCapabilities {
167
+ /** Linked by a QR scan or a pairing code (`requestPairingCode`). */
168
+ qrLink: boolean;
169
+ /** Imports recent history when linked. */
170
+ history: boolean;
171
+ /** Takes part in groups. */
172
+ groups: boolean;
173
+ /**
174
+ * Free-form messages only within this many hours of the customer's last message (WhatsApp
175
+ * Business API: 24); after that only an approved template (`sendTemplate`). null: no window.
176
+ */
177
+ replyWindowHours: number | null;
178
+ /** Lists and sends approved message templates (`listTemplates`, `sendTemplate`). */
179
+ templates: boolean;
180
+ /** Receives messages at a public https address (a webhook over Phone access / the tunnel). */
181
+ needsPublicUrl: boolean;
182
+ }
183
+ /** The capabilities of an adapter that declares none: a phone linked by QR (Baileys, the fake). */
184
+ export declare const LINKED_PHONE_CAPABILITIES: WaCapabilities;
185
+ /** An approved message template of the number's business account. */
186
+ export interface WaTemplate {
187
+ name: string;
188
+ /** Meta language code, e.g. `en`, `ms`, `zh_CN`. */
189
+ language: string;
190
+ category: 'utility' | 'marketing' | 'authentication';
191
+ /** Body text with {{1}}, {{2}} … variables. */
192
+ body: string;
193
+ /** Example value for each body variable, in order (may be shorter than the variable count). */
194
+ examples: string[];
195
+ /** A media header (image/document/video) or buttons: listed, not sent by EzyChat (v1). */
196
+ header: 'text' | 'image' | 'document' | 'video' | null;
197
+ buttons: boolean;
198
+ }
199
+ /** One template message to send: the template and its body variable values, in order. */
200
+ export interface WaTemplateSend {
201
+ name: string;
202
+ language: string;
203
+ params: string[];
204
+ }
162
205
  export interface WaAdapter {
163
206
  readonly status: WaStatus;
207
+ /** What this connection can do; absent: a linked phone (`LINKED_PHONE_CAPABILITIES`). */
208
+ readonly capabilities?: WaCapabilities;
209
+ /** Approved templates (adapters with `capabilities.templates`). Throws WaUnavailableError offline. */
210
+ listTemplates?(): Promise<WaTemplate[]>;
211
+ /** Sends a template message (adapters with `capabilities.templates`). */
212
+ sendTemplate?(chatJid: string, template: WaTemplateSend): Promise<SendResult>;
164
213
  on<K extends keyof WaAdapterEvents>(ev: K, fn: (...a: WaAdapterEvents[K]) => void): this;
165
214
  off<K extends keyof WaAdapterEvents>(ev: K, fn: (...a: WaAdapterEvents[K]) => void): this;
166
215
  /** idempotent */
package/dist/types.js CHANGED
@@ -1,3 +1,12 @@
1
+ /** The capabilities of an adapter that declares none: a phone linked by QR (Baileys, the fake). */
2
+ export const LINKED_PHONE_CAPABILITIES = Object.freeze({
3
+ qrLink: true,
4
+ history: true,
5
+ groups: true,
6
+ replyWindowHours: null,
7
+ templates: false,
8
+ needsPublicUrl: false,
9
+ });
1
10
  export class WaUnavailableError extends Error {
2
11
  code = 'wa_unavailable';
3
12
  constructor(message = 'WhatsApp is not connected') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ezychat/wa",
3
- "version": "0.1.56",
3
+ "version": "0.1.57",
4
4
  "type": "module",
5
5
  "description": "EzyChat core: WhatsApp adapter interface, Baileys adapter and fake adapter.",
6
6
  "license": "MIT",
@@ -37,7 +37,7 @@
37
37
  "typecheck": "tsc --noEmit"
38
38
  },
39
39
  "dependencies": {
40
- "@ezychat/shared": "0.1.56",
40
+ "@ezychat/shared": "0.1.57",
41
41
  "baileys": "^7.0.0-rc14",
42
42
  "pino": "^10.4.0",
43
43
  "qrcode": "^1.5.4"