-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcommon.ts
More file actions
279 lines (240 loc) · 8.27 KB
/
Copy pathcommon.ts
File metadata and controls
279 lines (240 loc) · 8.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
// ---------------------------------------------------------------------------
// Retry / reconnection options
// ---------------------------------------------------------------------------
export interface RetryOptions {
/** Initial delay in milliseconds before the first retry. Default `200`. */
readonly initialDelay?: number;
/** Maximum number of attempts including the initial call. Default `4`. */
readonly maxAttempts?: number;
/** Maximum delay in milliseconds between retries. Default `5000`. */
readonly maxDelay?: number;
}
export interface ReconnectOptions {
/** Initial delay in milliseconds before the first reconnect. Default `1000`. */
readonly initialDelay?: number;
/** Maximum number of consecutive reconnect attempts. Default `Infinity`. */
readonly maxAttempts?: number;
/** Maximum delay in milliseconds between retries. Default `30000`. */
readonly maxDelay?: number;
/** Multiplier applied to the delay after each failed attempt. Default `2`. */
readonly multiplier?: number;
/**
* Invoked before each reconnect attempt. `cause` is the error that ended
* the previous attempt, and is undefined when the stream simply ended.
* Without it a caller logging this callback has no way to say *why* the
* stream is reconnecting.
*/
readonly onReconnect?: (attempt: number, cause?: unknown) => void;
/**
* Aborts the reconnect loop. `TypedEventStream.close()` wires this up, so
* callers rarely set it directly. Without it a stream that keeps failing
* before its first event never reaches a yield point, and the loop survives
* `close()` for the life of the process.
*/
readonly signal?: AbortSignal;
}
// ---------------------------------------------------------------------------
// Subscribe / fetch missed
// ---------------------------------------------------------------------------
export interface SubscribeOptions {
/** Resume from a previously saved cursor. */
readonly cursor?: string;
/**
* Invoked on every frame received from the server, including heartbeats
* and cursor-only frames that never surface as events. The server
* heartbeats every ~30s on an otherwise idle stream, so this is the only
* signal that distinguishes a quiet-but-healthy stream from a silently
* dead one — key liveness watchdogs on it. Must not throw.
*/
readonly onActivity?: () => void;
/** Reconnection configuration for automatic reconnects. */
readonly reconnect?: ReconnectOptions;
/**
* Milliseconds without a single frame (heartbeats included) before the
* live stream is declared dead and torn down for a reconnect. The server
* heartbeats every ~30s, so a healthy-but-idle stream never trips this.
* A half-open connection (LB idle timeout, NAT drop) raises no error at
* all — this timeout is what converts that silence into the reconnect +
* missed-event replay path. Default `120000` (4 missed heartbeats).
* Set `0` to disable.
*/
readonly stallTimeoutMs?: number;
}
export interface FetchMissedOptions {
/** The cursor from the last received event. */
readonly cursor: string;
/** Maximum number of events to return. */
readonly limit?: number;
}
export interface FetchMissedResult {
/** Missed events since the given cursor. */
readonly events: readonly import("./events.ts").WhatsAppEvent[];
}
// ---------------------------------------------------------------------------
// Shared domain types
// ---------------------------------------------------------------------------
export interface Contact {
readonly name?: string;
readonly waId: string;
}
export interface WhatsAppApiError {
readonly code: number;
readonly details?: string;
readonly href?: string;
readonly message?: string;
readonly title: string;
}
export interface MessageContext {
readonly forwarded?: boolean;
readonly frequentlyForwarded?: boolean;
readonly from?: string;
readonly id?: string;
readonly referredProduct?: ReferredProduct;
}
export interface ReferredProduct {
readonly catalogId: string;
readonly productRetailerId: string;
}
export interface Referral {
readonly body?: string;
readonly headline?: string;
readonly sourceId?: string;
readonly sourceType: string;
readonly sourceUrl: string;
}
export interface Conversation {
readonly expiration?: Date;
readonly id: string;
readonly originType: string;
}
export interface Pricing {
readonly billable: boolean;
readonly category: string;
readonly pricingModel: string;
readonly type: string;
}
export interface Location {
readonly address?: string;
readonly latitude: number;
readonly longitude: number;
readonly name?: string;
}
// ---------------------------------------------------------------------------
// Inbound media types
// ---------------------------------------------------------------------------
export interface InboundMedia {
readonly caption?: string;
readonly filename?: string;
readonly id: string;
readonly mimeType: string;
readonly sha256?: string;
readonly voice?: boolean;
}
export interface InboundSticker {
readonly animated?: boolean;
readonly id: string;
readonly mimeType: string;
readonly sha256?: string;
}
// ---------------------------------------------------------------------------
// Inbound interactive reply types
// ---------------------------------------------------------------------------
export interface InboundButtonReply {
readonly id: string;
readonly title: string;
}
export interface InboundListReply {
readonly description?: string;
readonly id: string;
readonly title: string;
}
export interface InboundNfmReply {
readonly body?: string;
readonly name?: string;
readonly responseJson: string;
}
export type InboundInteractive =
| { type: "button_reply"; reply: InboundButtonReply }
| { type: "list_reply"; reply: InboundListReply }
| { type: "nfm_reply"; reply: InboundNfmReply };
export interface InboundButton {
readonly payload: string;
readonly text: string;
}
// ---------------------------------------------------------------------------
// Inbound order types
// ---------------------------------------------------------------------------
export interface OrderProductItem {
readonly currency: string;
readonly itemPrice: number;
readonly productRetailerId: string;
readonly quantity: number;
}
export interface Order {
readonly catalogId: string;
readonly productItems: readonly OrderProductItem[];
readonly text?: string;
}
// ---------------------------------------------------------------------------
// System message
// ---------------------------------------------------------------------------
export interface SystemMessage {
readonly body: string;
readonly newWaId?: string;
readonly type: string;
readonly waId?: string;
}
// ---------------------------------------------------------------------------
// Contact card (shared between inbound and outbound)
// ---------------------------------------------------------------------------
export interface ContactCard {
readonly addresses: readonly ContactAddress[];
readonly birthday?: string;
readonly emails: readonly ContactEmail[];
readonly name: ContactName;
readonly org?: ContactOrg;
readonly phones: readonly ContactPhone[];
readonly urls: readonly ContactUrl[];
}
export interface ContactName {
readonly firstName?: string;
readonly formattedName: string;
readonly lastName?: string;
readonly middleName?: string;
readonly prefix?: string;
readonly suffix?: string;
}
export interface ContactPhone {
readonly phone: string;
readonly type?: string;
readonly waId?: string;
}
export interface ContactEmail {
readonly email: string;
readonly type?: string;
}
export interface ContactAddress {
readonly city?: string;
readonly country?: string;
readonly countryCode?: string;
readonly state?: string;
readonly street?: string;
readonly type?: string;
readonly zip?: string;
}
export interface ContactOrg {
readonly company?: string;
readonly department?: string;
readonly title?: string;
}
export interface ContactUrl {
readonly type?: string;
readonly url: string;
}
// ---------------------------------------------------------------------------
// Reaction
// ---------------------------------------------------------------------------
export interface Reaction {
readonly emoji: string;
readonly messageId: string;
}