@funnelchat-hq/sandbox 0.1.0 → 0.2.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 +125 -7
- package/dist/{chunk-PF4CGK6J.js → chunk-M5E6NAXM.js} +2448 -1074
- package/dist/cli.js +3 -1
- package/dist/index.d.ts +92 -2
- package/dist/index.js +1 -1
- package/funnelchat-sandbox.config.example.json +2 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { ConfigError, parseArgs, VERSION, DEFAULT_PORT, DEFAULT_CONFIG_FILE, resolveConfig, createSandbox } from './chunk-
|
|
2
|
+
import { ConfigError, parseArgs, VERSION, DEFAULT_PORT, DEFAULT_CONFIG_FILE, resolveConfig, createSandbox } from './chunk-M5E6NAXM.js';
|
|
3
3
|
|
|
4
4
|
// src/cli.ts
|
|
5
5
|
var HELP = `funnelchat-sandbox ${VERSION}
|
|
@@ -10,6 +10,7 @@ Usage: npx funnelchat-sandbox [options]
|
|
|
10
10
|
Options:
|
|
11
11
|
-p, --port <n> Port (default ${DEFAULT_PORT}; 0 takes a free one).
|
|
12
12
|
--host <host> Where to listen (default 127.0.0.1; in Docker, 0.0.0.0).
|
|
13
|
+
--public-url <url> Base URL of the sign-up links (default: where it listens; in Docker, the published one).
|
|
13
14
|
-c, --config <file> JSON configuration (default ./${DEFAULT_CONFIG_FILE}, if it exists).
|
|
14
15
|
--status-delays <s,d,r> Seconds until sent, delivered and read (default 1,2,3; "-" stops there).
|
|
15
16
|
--retry-delays <a,b,c> Seconds between retries of a webhook (default 60,300,1800).
|
|
@@ -19,6 +20,7 @@ Options:
|
|
|
19
20
|
--no-antiblock Do not apply the spam brakes.
|
|
20
21
|
--no-rate-limit Do not apply the per-number pace.
|
|
21
22
|
--template-approval <s> Seconds until a new template is approved (default 5; "-" leaves it pending).
|
|
23
|
+
--meta-connect Turn on the Meta sign-up by link (off by default, like production).
|
|
22
24
|
-q, --quiet No line per request and per delivery.
|
|
23
25
|
-h, --help This help.
|
|
24
26
|
-v, --version The version.
|
package/dist/index.d.ts
CHANGED
|
@@ -41,6 +41,30 @@ interface SandboxConfig {
|
|
|
41
41
|
templateApprovalSeconds: number | null;
|
|
42
42
|
/** Subscriptions to create at start and after every reset. */
|
|
43
43
|
subscriptions: PresetSubscription[];
|
|
44
|
+
/**
|
|
45
|
+
* The number quota of the account and the Meta sign-up switch; `POST /_sandbox/numbers` changes them while it runs.
|
|
46
|
+
* QR and Meta of the same phone count once; the two example numbers already use two places.
|
|
47
|
+
*/
|
|
48
|
+
numbers: {
|
|
49
|
+
/** Numbers the plan allows: `403 number_limit_reached` beyond it. */
|
|
50
|
+
limit: number;
|
|
51
|
+
/** `false`: the plan has no numbers, `403 numbers_not_included`. */
|
|
52
|
+
included: boolean;
|
|
53
|
+
/** A trial account: one number of the US pool instead of three. */
|
|
54
|
+
trial: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* The Meta sign-up by link. `false` (the default, like production until the domain is registered with Meta):
|
|
57
|
+
* `POST /connect-links` with `type: meta` and its page answer `503 meta_connect_disabled`. Turn it on to test it.
|
|
58
|
+
*/
|
|
59
|
+
metaConnect: boolean;
|
|
60
|
+
/** `POST /connect-links` per minute (accounts: 10); one more answers Laravel's `429 Too Many Attempts.`. */
|
|
61
|
+
linksPerMinute: number;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Where the sign-up page is reachable, for the `url` of a link (`<publicUrl>/connect/<token>`). Never taken from the
|
|
65
|
+
* request's `Host`. `null`: the address the sandbox listens on.
|
|
66
|
+
*/
|
|
67
|
+
publicUrl: string | null;
|
|
44
68
|
/** How many requests and deliveries the panel keeps. */
|
|
45
69
|
historyLimit: number;
|
|
46
70
|
/** One line per request and per delivery on the console. */
|
|
@@ -60,18 +84,65 @@ interface MessageError {
|
|
|
60
84
|
reason: string | null;
|
|
61
85
|
}
|
|
62
86
|
type DeviceKind = 'cloud' | 'qr';
|
|
87
|
+
/** The aggregated health of an official number (accounts `MetaHealthStatusService`). */
|
|
88
|
+
type HealthState = 'available' | 'limited' | 'blocked' | 'unknown';
|
|
63
89
|
interface Device {
|
|
64
90
|
id: number;
|
|
65
91
|
alias: string;
|
|
66
|
-
/** Digits only, without `+`. */
|
|
67
|
-
number: string;
|
|
92
|
+
/** Digits only, without `+`. `null` while a number created by QR has not been scanned. */
|
|
93
|
+
number: string | null;
|
|
68
94
|
/** WhatsAppProviderEnum of accounts: 1 Z-API (QR), 2 WhatsApp Cloud (official). */
|
|
69
95
|
provider: 1 | 2;
|
|
70
96
|
kind: DeviceKind;
|
|
71
97
|
connected: boolean;
|
|
98
|
+
/** Official numbers only: it shares the number with the WhatsApp Business app. */
|
|
99
|
+
coexistence: boolean;
|
|
100
|
+
/** Official numbers only: it came from the pool of US numbers. */
|
|
101
|
+
fromPool: boolean;
|
|
102
|
+
/** Free text of the integrator, up to 255 characters, not unique. */
|
|
103
|
+
externalReference: string | null;
|
|
104
|
+
/** Official numbers only (`null` for QR). */
|
|
105
|
+
health: HealthState | null;
|
|
106
|
+
/** The sign-up link that connected it last: `connect_link_id` of every `device.*` event. */
|
|
107
|
+
lastConnectLinkId: number | null;
|
|
72
108
|
wabaId: string | null;
|
|
73
109
|
phoneNumberId: string | null;
|
|
110
|
+
firstConnectedAt: Date | null;
|
|
111
|
+
createdAt: Date;
|
|
112
|
+
}
|
|
113
|
+
type ConnectLinkStatus = 'pending' | 'completed' | 'failed' | 'expired' | 'revoked';
|
|
114
|
+
/** A sign-up link (contract A1 `connect-links`): it creates (or reconnects) a number when the page completes. */
|
|
115
|
+
interface ConnectLink {
|
|
116
|
+
id: number;
|
|
117
|
+
type: 'meta' | 'qr';
|
|
118
|
+
status: ConnectLinkStatus;
|
|
119
|
+
/** sha256 of the token: the raw token is only in the `url` of the create answer. */
|
|
120
|
+
tokenHash: string;
|
|
121
|
+
allowedConnectionTypes: ('coexistence' | 'dedicated')[] | null;
|
|
122
|
+
provisionUsNumber: boolean;
|
|
123
|
+
/** `qr` only, without `device_id`: the country of the phone (two letters, lower case), where the session opens. */
|
|
124
|
+
country: string | null;
|
|
125
|
+
/** Reconnects this number; otherwise `null` until the link completes, then the number it created. */
|
|
126
|
+
deviceId: number | null;
|
|
127
|
+
reconnect: boolean;
|
|
128
|
+
/** `qr` only: adds QR to this number in coexistence. Not part of the public resource. */
|
|
129
|
+
sameNumberAs: number | null;
|
|
130
|
+
externalReference: string | null;
|
|
131
|
+
returnUrl: string | null;
|
|
132
|
+
errorCode: string | null;
|
|
133
|
+
/** Failed attempts of Facebook or Meta: at the fifth the link ends `failed`. */
|
|
134
|
+
failedAttempts: number;
|
|
135
|
+
expiresAt: Date;
|
|
74
136
|
createdAt: Date;
|
|
137
|
+
completedAt: Date | null;
|
|
138
|
+
}
|
|
139
|
+
/** An answer kept under an `Idempotency-Key` for 24 hours. */
|
|
140
|
+
interface IdempotentAnswer {
|
|
141
|
+
fingerprint: string;
|
|
142
|
+
status: number;
|
|
143
|
+
body: unknown;
|
|
144
|
+
headers: Record<string, string>;
|
|
145
|
+
storedAt: Date;
|
|
75
146
|
}
|
|
76
147
|
interface Contact {
|
|
77
148
|
id: number;
|
|
@@ -287,6 +358,19 @@ declare class Store {
|
|
|
287
358
|
forms: Map<number, FormVersion>;
|
|
288
359
|
formFamilies: Map<number, FormFamily>;
|
|
289
360
|
formRuns: Map<number, FormRun>;
|
|
361
|
+
connectLinks: Map<number, ConnectLink>;
|
|
362
|
+
/** When each `POST /connect-links` arrived in the last minute: the per-minute limit. */
|
|
363
|
+
linkAttempts: number[];
|
|
364
|
+
/** Answers by `Idempotency-Key`, per resource (`devices`, `connect-links`): one account. */
|
|
365
|
+
idempotency: Map<string, IdempotentAnswer>;
|
|
366
|
+
/** The number quota of the account and the switch of the Meta sign-up (`POST /_sandbox/numbers`). */
|
|
367
|
+
numbers: {
|
|
368
|
+
limit: number;
|
|
369
|
+
included: boolean;
|
|
370
|
+
trial: boolean;
|
|
371
|
+
metaConnect: boolean;
|
|
372
|
+
linksPerMinute: number;
|
|
373
|
+
};
|
|
290
374
|
/** Last inbound message per `contactId:deviceId`: the 24-hour window. */
|
|
291
375
|
lastInbound: Map<string, Date>;
|
|
292
376
|
/** API-key sends per contact since its last inbound message: the antiblock counters. */
|
|
@@ -317,6 +401,8 @@ declare class Store {
|
|
|
317
401
|
formFamily: Sequence;
|
|
318
402
|
formVersion: Sequence;
|
|
319
403
|
formRun: Sequence;
|
|
404
|
+
device: Sequence;
|
|
405
|
+
connectLink: Sequence;
|
|
320
406
|
};
|
|
321
407
|
constructor(historyLimit?: number);
|
|
322
408
|
logRequest(entry: Omit<RequestLogEntry, 'id'>): RequestLogEntry;
|
|
@@ -398,7 +484,11 @@ declare class SandboxApp {
|
|
|
398
484
|
readonly webhooks: Webhooks;
|
|
399
485
|
/** Grows on every reset, so work started before a reset does not touch the new state. */
|
|
400
486
|
generation: number;
|
|
487
|
+
/** The address the sandbox listens on, once it does: the host of the sign-up links when `publicUrl` is not set. */
|
|
488
|
+
listenUrl: string | null;
|
|
401
489
|
constructor(config: SandboxConfig, logger?: Logger);
|
|
490
|
+
/** `http://host:port` of the sign-up page: `publicUrl`, or where the sandbox listens. */
|
|
491
|
+
get publicUrl(): string;
|
|
402
492
|
private populate;
|
|
403
493
|
/** `POST /_sandbox/reset`: empty state, the example data again, timers cancelled. */
|
|
404
494
|
reset(): void;
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { DELIVERY_HEADER, EDGE_ERRORS, EVENT_HEADER, FAILURE_REASONS, FORM_TEMPLATES, META_ERRORS, REJECTIONS, REQUEST_ID_HEADER, SEND_FILE_TYPES, SIGNATURE_HEADER, SUPPORTED_EXTENSIONS, VERSION, catalog, createSandbox, defaultConfig, mergeConfig, signature } from './chunk-
|
|
1
|
+
export { DELIVERY_HEADER, EDGE_ERRORS, EVENT_HEADER, FAILURE_REASONS, FORM_TEMPLATES, META_ERRORS, REJECTIONS, REQUEST_ID_HEADER, SEND_FILE_TYPES, SIGNATURE_HEADER, SUPPORTED_EXTENSIONS, VERSION, catalog, createSandbox, defaultConfig, mergeConfig, signature } from './chunk-M5E6NAXM.js';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
"webhooks": { "retryDelays": [5, 15, 30], "timeoutSeconds": 10 },
|
|
5
5
|
"rules": { "window": true, "antiblock": true, "rateLimit": true },
|
|
6
6
|
"templateApprovalSeconds": 5,
|
|
7
|
+
"numbers": { "limit": 5, "included": true, "trial": false, "metaConnect": false, "linksPerMinute": 10 },
|
|
8
|
+
"publicUrl": null,
|
|
7
9
|
"subscriptions": [
|
|
8
10
|
{
|
|
9
11
|
"url": "http://localhost:3000/hooks/funnelchat",
|