@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
|
@@ -4,6 +4,7 @@ import { createHmac, randomUUID, randomBytes, createHash } from 'crypto';
|
|
|
4
4
|
import { Ajv2020 } from 'ajv/dist/2020.js';
|
|
5
5
|
import addFormatsModule from 'ajv-formats';
|
|
6
6
|
import { createServer } from 'http';
|
|
7
|
+
import { deflateSync } from 'zlib';
|
|
7
8
|
|
|
8
9
|
// src/config.ts
|
|
9
10
|
function isoSeconds(date) {
|
|
@@ -81,6 +82,8 @@ function defaultConfig() {
|
|
|
81
82
|
rules: { window: true, antiblock: true, rateLimit: true },
|
|
82
83
|
templateApprovalSeconds: 5,
|
|
83
84
|
subscriptions: [],
|
|
85
|
+
numbers: { limit: 5, included: true, trial: false, metaConnect: false, linksPerMinute: 10 },
|
|
86
|
+
publicUrl: null,
|
|
84
87
|
historyLimit: 200,
|
|
85
88
|
log: true
|
|
86
89
|
};
|
|
@@ -157,6 +160,24 @@ function mergeConfig(base, input) {
|
|
|
157
160
|
return structuredClone(s);
|
|
158
161
|
});
|
|
159
162
|
}
|
|
163
|
+
if (input.numbers !== void 0) {
|
|
164
|
+
const n = input.numbers;
|
|
165
|
+
if (!isRecord(n)) throw new ConfigError("numbers must be an object {limit, included, trial, metaConnect, linksPerMinute}.");
|
|
166
|
+
for (const key of ["limit", "linksPerMinute"]) {
|
|
167
|
+
if (n[key] === void 0) continue;
|
|
168
|
+
if (!Number.isInteger(n[key]) || n[key] < 0) throw new ConfigError(`numbers.${key} must be an integer, 0 or more.`);
|
|
169
|
+
out.numbers[key] = n[key];
|
|
170
|
+
}
|
|
171
|
+
for (const key of ["included", "trial", "metaConnect"]) {
|
|
172
|
+
if (n[key] !== void 0) out.numbers[key] = Boolean(n[key]);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (input.publicUrl !== void 0) {
|
|
176
|
+
if (input.publicUrl !== null && (typeof input.publicUrl !== "string" || !/^https?:\/\/[^/]+/.test(input.publicUrl))) {
|
|
177
|
+
throw new ConfigError("publicUrl must be an http(s) URL, like https://sandbox.example.com, or null.");
|
|
178
|
+
}
|
|
179
|
+
out.publicUrl = input.publicUrl === null ? null : input.publicUrl.replace(/\/+$/, "");
|
|
180
|
+
}
|
|
160
181
|
if (input.historyLimit !== void 0) {
|
|
161
182
|
if (!Number.isInteger(input.historyLimit) || input.historyLimit < 1) {
|
|
162
183
|
throw new ConfigError("historyLimit must be an integer greater than 0.");
|
|
@@ -229,6 +250,9 @@ function parseArgs(argv) {
|
|
|
229
250
|
case "--host":
|
|
230
251
|
o.host = value();
|
|
231
252
|
break;
|
|
253
|
+
case "--public-url":
|
|
254
|
+
o.publicUrl = value();
|
|
255
|
+
break;
|
|
232
256
|
case "--status-delays": {
|
|
233
257
|
const [sent, delivered, read] = parseSecondsList(value(), "--status-delays", true);
|
|
234
258
|
if (sent === null || sent === void 0) throw new ConfigError("--status-delays: the first one (sent) must be a number.");
|
|
@@ -260,6 +284,9 @@ function parseArgs(argv) {
|
|
|
260
284
|
o.templateApprovalSeconds = n ?? null;
|
|
261
285
|
break;
|
|
262
286
|
}
|
|
287
|
+
case "--meta-connect":
|
|
288
|
+
o.numbers = { ...o.numbers, metaConnect: true };
|
|
289
|
+
break;
|
|
263
290
|
case "-q":
|
|
264
291
|
case "--quiet":
|
|
265
292
|
o.log = false;
|
|
@@ -17489,42 +17516,6 @@ var openapi_default = {
|
|
|
17489
17516
|
"x-fc-proposito": "Usalo para listar los grupos de tu cuenta con filtros por tipo, estado, n\xFAmero o sin campa\xF1a asignada, por ejemplo para sincronizarlos con tu sistema.",
|
|
17490
17517
|
"x-fc-domain": "communities",
|
|
17491
17518
|
"x-fc-permission": "communities:read"
|
|
17492
|
-
},
|
|
17493
|
-
post: {
|
|
17494
|
-
operationId: "pendingSandboxGroupActions.groups.post",
|
|
17495
|
-
summary: "Group action by API (off)",
|
|
17496
|
-
description: "It exists in communities but is off in production: any key gets `403 route_not_available`.\n\n**Pending of the spec**: it is in production, missing from the portal's snapshot.",
|
|
17497
|
-
tags: [
|
|
17498
|
-
"GroupActions"
|
|
17499
|
-
],
|
|
17500
|
-
"x-fc-domain": "communities",
|
|
17501
|
-
"x-fc-permission": "communities:operate",
|
|
17502
|
-
"x-fc-pending": "communities-group-actions",
|
|
17503
|
-
responses: {
|
|
17504
|
-
"403": {
|
|
17505
|
-
description: "route_not_available: the group actions by API are off in production.",
|
|
17506
|
-
content: {
|
|
17507
|
-
"application/json": {
|
|
17508
|
-
schema: {
|
|
17509
|
-
type: "object",
|
|
17510
|
-
properties: {
|
|
17511
|
-
error: {
|
|
17512
|
-
type: "string",
|
|
17513
|
-
const: "route_not_available"
|
|
17514
|
-
},
|
|
17515
|
-
message: {
|
|
17516
|
-
type: "string"
|
|
17517
|
-
}
|
|
17518
|
-
},
|
|
17519
|
-
required: [
|
|
17520
|
-
"error",
|
|
17521
|
-
"message"
|
|
17522
|
-
]
|
|
17523
|
-
}
|
|
17524
|
-
}
|
|
17525
|
-
}
|
|
17526
|
-
}
|
|
17527
|
-
}
|
|
17528
17519
|
}
|
|
17529
17520
|
},
|
|
17530
17521
|
"/api/v1/devices/{device}/groups": {
|
|
@@ -18602,104 +18593,281 @@ var openapi_default = {
|
|
|
18602
18593
|
"x-fc-proposito": "Sirve para ver los mensajes que se enviaron a un grupo y de qu\xE9 env\xEDo programado sali\xF3 cada uno.",
|
|
18603
18594
|
"x-fc-domain": "communities",
|
|
18604
18595
|
"x-fc-permission": "communities:read"
|
|
18605
|
-
}
|
|
18606
|
-
|
|
18607
|
-
|
|
18608
|
-
|
|
18609
|
-
|
|
18596
|
+
}
|
|
18597
|
+
},
|
|
18598
|
+
"/api/v1/groups/{group}/scheduled-messages": {
|
|
18599
|
+
get: {
|
|
18600
|
+
operationId: "group.scheduledMessages",
|
|
18601
|
+
description: "**Permiso requerido:** `communities:read` (Leer)\n\n**Con API key:** la respuesta siempre viene paginada. Tama\xF1o de p\xE1gina: `perPage` (por defecto 25, m\xE1ximo 100; un valor mayor, menor que 1 o no entero responde `422`). Orden: `orderBy` acepta `id`, `name`, `created_at`, `updated_at`; direcci\xF3n `orderByMethod`: `asc` o `desc`. Otro valor responde `422`.",
|
|
18602
|
+
summary: "Listar los env\xEDos programados que incluyen un grupo",
|
|
18610
18603
|
tags: [
|
|
18611
|
-
"
|
|
18604
|
+
"Grupos"
|
|
18612
18605
|
],
|
|
18613
|
-
"x-fc-domain": "communities",
|
|
18614
|
-
"x-fc-permission": "communities:operate",
|
|
18615
|
-
"x-fc-pending": "communities-group-actions",
|
|
18616
18606
|
parameters: [
|
|
18617
18607
|
{
|
|
18618
18608
|
name: "group",
|
|
18619
18609
|
in: "path",
|
|
18620
18610
|
required: true,
|
|
18611
|
+
description: "The group uid",
|
|
18612
|
+
schema: {
|
|
18613
|
+
type: "string"
|
|
18614
|
+
}
|
|
18615
|
+
},
|
|
18616
|
+
{
|
|
18617
|
+
name: "name",
|
|
18618
|
+
in: "query",
|
|
18619
|
+
schema: {
|
|
18620
|
+
type: "string"
|
|
18621
|
+
}
|
|
18622
|
+
},
|
|
18623
|
+
{
|
|
18624
|
+
name: "orderBy",
|
|
18625
|
+
in: "query",
|
|
18626
|
+
schema: {
|
|
18627
|
+
type: "string"
|
|
18628
|
+
}
|
|
18629
|
+
},
|
|
18630
|
+
{
|
|
18631
|
+
name: "orderByMethod",
|
|
18632
|
+
in: "query",
|
|
18621
18633
|
schema: {
|
|
18622
18634
|
type: "string"
|
|
18623
18635
|
}
|
|
18636
|
+
},
|
|
18637
|
+
{
|
|
18638
|
+
name: "perPage",
|
|
18639
|
+
in: "query",
|
|
18640
|
+
schema: {
|
|
18641
|
+
type: "integer"
|
|
18642
|
+
}
|
|
18624
18643
|
}
|
|
18625
18644
|
],
|
|
18626
18645
|
responses: {
|
|
18627
|
-
"
|
|
18628
|
-
description: "
|
|
18646
|
+
"200": {
|
|
18647
|
+
description: "Paginated set of `ScheduledMessageResource`",
|
|
18629
18648
|
content: {
|
|
18630
18649
|
"application/json": {
|
|
18631
18650
|
schema: {
|
|
18632
18651
|
type: "object",
|
|
18633
18652
|
properties: {
|
|
18634
|
-
|
|
18635
|
-
type: "
|
|
18636
|
-
|
|
18653
|
+
data: {
|
|
18654
|
+
type: "array",
|
|
18655
|
+
items: {
|
|
18656
|
+
$ref: "#/components/schemas/ScheduledMessageResource"
|
|
18657
|
+
}
|
|
18637
18658
|
},
|
|
18638
|
-
|
|
18639
|
-
type: "
|
|
18659
|
+
links: {
|
|
18660
|
+
type: "object",
|
|
18661
|
+
properties: {
|
|
18662
|
+
first: {
|
|
18663
|
+
type: [
|
|
18664
|
+
"string",
|
|
18665
|
+
"null"
|
|
18666
|
+
]
|
|
18667
|
+
},
|
|
18668
|
+
last: {
|
|
18669
|
+
type: [
|
|
18670
|
+
"string",
|
|
18671
|
+
"null"
|
|
18672
|
+
]
|
|
18673
|
+
},
|
|
18674
|
+
prev: {
|
|
18675
|
+
type: [
|
|
18676
|
+
"string",
|
|
18677
|
+
"null"
|
|
18678
|
+
]
|
|
18679
|
+
},
|
|
18680
|
+
next: {
|
|
18681
|
+
type: [
|
|
18682
|
+
"string",
|
|
18683
|
+
"null"
|
|
18684
|
+
]
|
|
18685
|
+
}
|
|
18686
|
+
},
|
|
18687
|
+
required: [
|
|
18688
|
+
"first",
|
|
18689
|
+
"last",
|
|
18690
|
+
"prev",
|
|
18691
|
+
"next"
|
|
18692
|
+
]
|
|
18693
|
+
},
|
|
18694
|
+
meta: {
|
|
18695
|
+
type: "object",
|
|
18696
|
+
properties: {
|
|
18697
|
+
current_page: {
|
|
18698
|
+
type: "integer",
|
|
18699
|
+
minimum: 1
|
|
18700
|
+
},
|
|
18701
|
+
from: {
|
|
18702
|
+
type: [
|
|
18703
|
+
"integer",
|
|
18704
|
+
"null"
|
|
18705
|
+
],
|
|
18706
|
+
minimum: 1
|
|
18707
|
+
},
|
|
18708
|
+
last_page: {
|
|
18709
|
+
type: "integer",
|
|
18710
|
+
minimum: 1
|
|
18711
|
+
},
|
|
18712
|
+
links: {
|
|
18713
|
+
type: "array",
|
|
18714
|
+
description: "Generated paginator links.",
|
|
18715
|
+
items: {
|
|
18716
|
+
type: "object",
|
|
18717
|
+
properties: {
|
|
18718
|
+
url: {
|
|
18719
|
+
type: [
|
|
18720
|
+
"string",
|
|
18721
|
+
"null"
|
|
18722
|
+
]
|
|
18723
|
+
},
|
|
18724
|
+
label: {
|
|
18725
|
+
type: "string"
|
|
18726
|
+
},
|
|
18727
|
+
active: {
|
|
18728
|
+
type: "boolean"
|
|
18729
|
+
}
|
|
18730
|
+
},
|
|
18731
|
+
required: [
|
|
18732
|
+
"url",
|
|
18733
|
+
"label",
|
|
18734
|
+
"active"
|
|
18735
|
+
]
|
|
18736
|
+
}
|
|
18737
|
+
},
|
|
18738
|
+
path: {
|
|
18739
|
+
type: [
|
|
18740
|
+
"string",
|
|
18741
|
+
"null"
|
|
18742
|
+
],
|
|
18743
|
+
description: "Base path for paginator generated URLs."
|
|
18744
|
+
},
|
|
18745
|
+
per_page: {
|
|
18746
|
+
type: "integer",
|
|
18747
|
+
description: "Number of items shown per page.",
|
|
18748
|
+
minimum: 0
|
|
18749
|
+
},
|
|
18750
|
+
to: {
|
|
18751
|
+
type: [
|
|
18752
|
+
"integer",
|
|
18753
|
+
"null"
|
|
18754
|
+
],
|
|
18755
|
+
description: "Number of the last item in the slice.",
|
|
18756
|
+
minimum: 1
|
|
18757
|
+
},
|
|
18758
|
+
total: {
|
|
18759
|
+
type: "integer",
|
|
18760
|
+
description: "Total number of items being paginated.",
|
|
18761
|
+
minimum: 0
|
|
18762
|
+
}
|
|
18763
|
+
},
|
|
18764
|
+
required: [
|
|
18765
|
+
"current_page",
|
|
18766
|
+
"from",
|
|
18767
|
+
"last_page",
|
|
18768
|
+
"links",
|
|
18769
|
+
"path",
|
|
18770
|
+
"per_page",
|
|
18771
|
+
"to",
|
|
18772
|
+
"total"
|
|
18773
|
+
]
|
|
18640
18774
|
}
|
|
18641
18775
|
},
|
|
18642
18776
|
required: [
|
|
18643
|
-
"
|
|
18644
|
-
"
|
|
18777
|
+
"data",
|
|
18778
|
+
"links",
|
|
18779
|
+
"meta"
|
|
18645
18780
|
]
|
|
18646
18781
|
}
|
|
18647
18782
|
}
|
|
18648
18783
|
}
|
|
18784
|
+
},
|
|
18785
|
+
"401": {
|
|
18786
|
+
$ref: "#/components/responses/CommunitiesAuthenticationException"
|
|
18787
|
+
},
|
|
18788
|
+
"403": {
|
|
18789
|
+
$ref: "#/components/responses/CommunitiesAuthorizationException"
|
|
18790
|
+
},
|
|
18791
|
+
"404": {
|
|
18792
|
+
$ref: "#/components/responses/CommunitiesModelNotFoundException"
|
|
18793
|
+
},
|
|
18794
|
+
"422": {
|
|
18795
|
+
$ref: "#/components/responses/CommunitiesValidationException"
|
|
18649
18796
|
}
|
|
18650
|
-
}
|
|
18797
|
+
},
|
|
18798
|
+
"x-fc-proposito": "Usalo para saber qu\xE9 env\xEDos programados incluyen a un grupo, por ejemplo antes de hacerle cambios.",
|
|
18799
|
+
"x-fc-domain": "communities",
|
|
18800
|
+
"x-fc-permission": "communities:read"
|
|
18651
18801
|
}
|
|
18652
18802
|
},
|
|
18653
|
-
"/api/v1/
|
|
18803
|
+
"/api/v1/campaigns/{campaign}/participants": {
|
|
18654
18804
|
get: {
|
|
18655
|
-
operationId: "
|
|
18656
|
-
description: "**Permiso requerido:** `communities:read` (Leer)\n\n**Con API key:** la respuesta siempre viene paginada. Tama\xF1o de p\xE1gina: `perPage` (por defecto 25, m\xE1ximo 100; un valor mayor, menor que 1 o no entero responde `422`). Orden: `orderBy` acepta `
|
|
18657
|
-
summary: "Listar los
|
|
18805
|
+
operationId: "campaign.participants",
|
|
18806
|
+
description: "**Permiso requerido:** `communities:read` (Leer)\n\n**Con API key:** la respuesta siempre viene paginada. Tama\xF1o de p\xE1gina: `perPage` (por defecto 25, m\xE1ximo 100; un valor mayor, menor que 1 o no entero responde `422`). Orden: `orderBy` acepta `updated_at`, `id`, `created_at`; direcci\xF3n `orderByMethod`: `asc` o `desc`. Otro valor responde `422`.",
|
|
18807
|
+
summary: "Listar los participantes de una campa\xF1a",
|
|
18658
18808
|
tags: [
|
|
18659
|
-
"
|
|
18809
|
+
"Participantes"
|
|
18660
18810
|
],
|
|
18661
18811
|
parameters: [
|
|
18662
18812
|
{
|
|
18663
|
-
name: "
|
|
18813
|
+
name: "campaign",
|
|
18664
18814
|
in: "path",
|
|
18665
18815
|
required: true,
|
|
18666
|
-
description: "The
|
|
18816
|
+
description: "The campaign ID",
|
|
18667
18817
|
schema: {
|
|
18668
|
-
type: "
|
|
18818
|
+
type: "integer"
|
|
18669
18819
|
}
|
|
18670
18820
|
},
|
|
18671
18821
|
{
|
|
18672
|
-
name: "
|
|
18822
|
+
name: "phone",
|
|
18673
18823
|
in: "query",
|
|
18674
18824
|
schema: {
|
|
18675
|
-
type:
|
|
18825
|
+
type: [
|
|
18826
|
+
"string",
|
|
18827
|
+
"null"
|
|
18828
|
+
]
|
|
18676
18829
|
}
|
|
18677
18830
|
},
|
|
18678
18831
|
{
|
|
18679
18832
|
name: "orderBy",
|
|
18680
18833
|
in: "query",
|
|
18681
18834
|
schema: {
|
|
18682
|
-
type:
|
|
18835
|
+
type: [
|
|
18836
|
+
"string",
|
|
18837
|
+
"null"
|
|
18838
|
+
]
|
|
18683
18839
|
}
|
|
18684
18840
|
},
|
|
18685
18841
|
{
|
|
18686
18842
|
name: "orderByMethod",
|
|
18687
18843
|
in: "query",
|
|
18688
18844
|
schema: {
|
|
18689
|
-
type:
|
|
18845
|
+
type: [
|
|
18846
|
+
"string",
|
|
18847
|
+
"null"
|
|
18848
|
+
],
|
|
18849
|
+
enum: [
|
|
18850
|
+
"asc",
|
|
18851
|
+
"desc",
|
|
18852
|
+
null
|
|
18853
|
+
]
|
|
18690
18854
|
}
|
|
18691
18855
|
},
|
|
18692
18856
|
{
|
|
18693
18857
|
name: "perPage",
|
|
18694
18858
|
in: "query",
|
|
18695
18859
|
schema: {
|
|
18696
|
-
type:
|
|
18860
|
+
type: [
|
|
18861
|
+
"integer",
|
|
18862
|
+
"null"
|
|
18863
|
+
],
|
|
18864
|
+
minimum: 1
|
|
18697
18865
|
}
|
|
18698
18866
|
}
|
|
18699
18867
|
],
|
|
18700
18868
|
responses: {
|
|
18701
18869
|
"200": {
|
|
18702
|
-
description: "Paginated set of `
|
|
18870
|
+
description: "Paginated set of `ParticipantResource`",
|
|
18703
18871
|
content: {
|
|
18704
18872
|
"application/json": {
|
|
18705
18873
|
schema: {
|
|
@@ -18708,7 +18876,7 @@ var openapi_default = {
|
|
|
18708
18876
|
data: {
|
|
18709
18877
|
type: "array",
|
|
18710
18878
|
items: {
|
|
18711
|
-
$ref: "#/components/schemas/
|
|
18879
|
+
$ref: "#/components/schemas/ParticipantResource"
|
|
18712
18880
|
}
|
|
18713
18881
|
},
|
|
18714
18882
|
links: {
|
|
@@ -18850,16 +19018,16 @@ var openapi_default = {
|
|
|
18850
19018
|
$ref: "#/components/responses/CommunitiesValidationException"
|
|
18851
19019
|
}
|
|
18852
19020
|
},
|
|
18853
|
-
"x-fc-proposito": "
|
|
19021
|
+
"x-fc-proposito": "Sirve para listar los participantes de una campa\xF1a, con b\xFAsqueda por tel\xE9fono y si son administradores.",
|
|
18854
19022
|
"x-fc-domain": "communities",
|
|
18855
19023
|
"x-fc-permission": "communities:read"
|
|
18856
19024
|
}
|
|
18857
19025
|
},
|
|
18858
|
-
"/api/v1/campaigns/{campaign}/participants": {
|
|
19026
|
+
"/api/v1/campaigns/{campaign}/campaign-participants": {
|
|
18859
19027
|
get: {
|
|
18860
|
-
operationId: "campaign.
|
|
18861
|
-
description: "**Permiso requerido:** `communities:read` (Leer)\n\n**Con API key:** la respuesta siempre viene paginada. Tama\xF1o de p\xE1gina: `perPage` (por defecto 25, m\xE1ximo 100; un valor mayor, menor que 1 o no entero responde `422`). Orden: `orderBy` acepta `
|
|
18862
|
-
summary: "Listar los participantes
|
|
19028
|
+
operationId: "campaign.campaignParticipants",
|
|
19029
|
+
description: "**Permiso requerido:** `communities:read` (Leer)\n\n**Con API key:** la respuesta siempre viene paginada. Tama\xF1o de p\xE1gina: `perPage` (por defecto 25, m\xE1ximo 100; un valor mayor, menor que 1 o no entero responde `422`). Orden: `orderBy` acepta `joined_at`, `left_at`; direcci\xF3n `orderByMethod`: `asc` o `desc`. Otro valor responde `422`.",
|
|
19030
|
+
summary: "Listar los participantes con su grupo actual",
|
|
18863
19031
|
tags: [
|
|
18864
19032
|
"Participantes"
|
|
18865
19033
|
],
|
|
@@ -18874,12 +19042,27 @@ var openapi_default = {
|
|
|
18874
19042
|
}
|
|
18875
19043
|
},
|
|
18876
19044
|
{
|
|
18877
|
-
name: "
|
|
19045
|
+
name: "search",
|
|
19046
|
+
in: "query",
|
|
19047
|
+
schema: {
|
|
19048
|
+
type: [
|
|
19049
|
+
"string",
|
|
19050
|
+
"null"
|
|
19051
|
+
]
|
|
19052
|
+
}
|
|
19053
|
+
},
|
|
19054
|
+
{
|
|
19055
|
+
name: "status",
|
|
18878
19056
|
in: "query",
|
|
18879
19057
|
schema: {
|
|
18880
19058
|
type: [
|
|
18881
19059
|
"string",
|
|
18882
19060
|
"null"
|
|
19061
|
+
],
|
|
19062
|
+
enum: [
|
|
19063
|
+
"active",
|
|
19064
|
+
"exited",
|
|
19065
|
+
null
|
|
18883
19066
|
]
|
|
18884
19067
|
}
|
|
18885
19068
|
},
|
|
@@ -18890,6 +19073,11 @@ var openapi_default = {
|
|
|
18890
19073
|
type: [
|
|
18891
19074
|
"string",
|
|
18892
19075
|
"null"
|
|
19076
|
+
],
|
|
19077
|
+
enum: [
|
|
19078
|
+
"joined_at",
|
|
19079
|
+
"left_at",
|
|
19080
|
+
null
|
|
18893
19081
|
]
|
|
18894
19082
|
}
|
|
18895
19083
|
},
|
|
@@ -18904,6 +19092,8 @@ var openapi_default = {
|
|
|
18904
19092
|
enum: [
|
|
18905
19093
|
"asc",
|
|
18906
19094
|
"desc",
|
|
19095
|
+
"ASC",
|
|
19096
|
+
"DESC",
|
|
18907
19097
|
null
|
|
18908
19098
|
]
|
|
18909
19099
|
}
|
|
@@ -18918,11 +19108,84 @@ var openapi_default = {
|
|
|
18918
19108
|
],
|
|
18919
19109
|
minimum: 1
|
|
18920
19110
|
}
|
|
19111
|
+
},
|
|
19112
|
+
{
|
|
19113
|
+
name: "start_date",
|
|
19114
|
+
in: "query",
|
|
19115
|
+
schema: {
|
|
19116
|
+
type: [
|
|
19117
|
+
"string",
|
|
19118
|
+
"null"
|
|
19119
|
+
],
|
|
19120
|
+
format: "date-time"
|
|
19121
|
+
}
|
|
19122
|
+
},
|
|
19123
|
+
{
|
|
19124
|
+
name: "end_date",
|
|
19125
|
+
in: "query",
|
|
19126
|
+
schema: {
|
|
19127
|
+
type: [
|
|
19128
|
+
"string",
|
|
19129
|
+
"null"
|
|
19130
|
+
],
|
|
19131
|
+
format: "date-time"
|
|
19132
|
+
}
|
|
19133
|
+
},
|
|
19134
|
+
{
|
|
19135
|
+
name: "timezone",
|
|
19136
|
+
in: "query",
|
|
19137
|
+
schema: {
|
|
19138
|
+
type: [
|
|
19139
|
+
"string",
|
|
19140
|
+
"null"
|
|
19141
|
+
]
|
|
19142
|
+
}
|
|
19143
|
+
},
|
|
19144
|
+
{
|
|
19145
|
+
name: "date_rule",
|
|
19146
|
+
in: "query",
|
|
19147
|
+
schema: {
|
|
19148
|
+
type: [
|
|
19149
|
+
"integer",
|
|
19150
|
+
"null"
|
|
19151
|
+
],
|
|
19152
|
+
enum: [
|
|
19153
|
+
"1",
|
|
19154
|
+
"2",
|
|
19155
|
+
"3",
|
|
19156
|
+
null
|
|
19157
|
+
]
|
|
19158
|
+
}
|
|
19159
|
+
},
|
|
19160
|
+
{
|
|
19161
|
+
name: "selection_type",
|
|
19162
|
+
in: "query",
|
|
19163
|
+
schema: {
|
|
19164
|
+
type: [
|
|
19165
|
+
"string",
|
|
19166
|
+
"null"
|
|
19167
|
+
],
|
|
19168
|
+
enum: [
|
|
19169
|
+
"all",
|
|
19170
|
+
"none",
|
|
19171
|
+
null
|
|
19172
|
+
]
|
|
19173
|
+
}
|
|
19174
|
+
},
|
|
19175
|
+
{
|
|
19176
|
+
name: "groups_uids[]",
|
|
19177
|
+
in: "query",
|
|
19178
|
+
schema: {
|
|
19179
|
+
type: "array",
|
|
19180
|
+
items: {
|
|
19181
|
+
type: "string"
|
|
19182
|
+
}
|
|
19183
|
+
}
|
|
18921
19184
|
}
|
|
18922
19185
|
],
|
|
18923
19186
|
responses: {
|
|
18924
19187
|
"200": {
|
|
18925
|
-
description: "Paginated set of `
|
|
19188
|
+
description: "Paginated set of `CampaignParticipantResource`",
|
|
18926
19189
|
content: {
|
|
18927
19190
|
"application/json": {
|
|
18928
19191
|
schema: {
|
|
@@ -18931,7 +19194,7 @@ var openapi_default = {
|
|
|
18931
19194
|
data: {
|
|
18932
19195
|
type: "array",
|
|
18933
19196
|
items: {
|
|
18934
|
-
$ref: "#/components/schemas/
|
|
19197
|
+
$ref: "#/components/schemas/CampaignParticipantResource"
|
|
18935
19198
|
}
|
|
18936
19199
|
},
|
|
18937
19200
|
links: {
|
|
@@ -19073,16 +19336,16 @@ var openapi_default = {
|
|
|
19073
19336
|
$ref: "#/components/responses/CommunitiesValidationException"
|
|
19074
19337
|
}
|
|
19075
19338
|
},
|
|
19076
|
-
"x-fc-proposito": "
|
|
19339
|
+
"x-fc-proposito": "Usalo para saber en qu\xE9 grupo est\xE1 cada participante de una campa\xF1a, cu\xE1ndo entr\xF3 o sali\xF3 y si sigue activo. Ideal para sincronizar miembros con tu CRM.",
|
|
19077
19340
|
"x-fc-domain": "communities",
|
|
19078
19341
|
"x-fc-permission": "communities:read"
|
|
19079
19342
|
}
|
|
19080
19343
|
},
|
|
19081
|
-
"/api/v1/campaigns/{campaign}/
|
|
19344
|
+
"/api/v1/campaigns/{campaign}/participant-events": {
|
|
19082
19345
|
get: {
|
|
19083
|
-
operationId: "campaign.
|
|
19084
|
-
description: "**Permiso requerido:** `communities:read` (Leer)\n\n**Con API key:** la respuesta siempre viene paginada. Tama\xF1o de p\xE1gina: `perPage` (por defecto 25, m\xE1ximo 100; un valor mayor, menor que 1 o no entero responde `422`).
|
|
19085
|
-
summary: "Listar
|
|
19346
|
+
operationId: "campaign.participantEvents",
|
|
19347
|
+
description: "**Permiso requerido:** `communities:read` (Leer)\n\n**Con API key:** la respuesta siempre viene paginada. Tama\xF1o de p\xE1gina: `perPage` (por defecto 25, m\xE1ximo 100; un valor mayor, menor que 1 o no entero responde `422`).",
|
|
19348
|
+
summary: "Listar entradas y salidas de una campa\xF1a",
|
|
19086
19349
|
tags: [
|
|
19087
19350
|
"Participantes"
|
|
19088
19351
|
],
|
|
@@ -19099,30 +19362,16 @@ var openapi_default = {
|
|
|
19099
19362
|
{
|
|
19100
19363
|
name: "search",
|
|
19101
19364
|
in: "query",
|
|
19102
|
-
schema: {
|
|
19103
|
-
type: [
|
|
19104
|
-
"string",
|
|
19105
|
-
"null"
|
|
19106
|
-
]
|
|
19107
|
-
}
|
|
19108
|
-
},
|
|
19109
|
-
{
|
|
19110
|
-
name: "status",
|
|
19111
|
-
in: "query",
|
|
19112
19365
|
schema: {
|
|
19113
19366
|
type: [
|
|
19114
19367
|
"string",
|
|
19115
19368
|
"null"
|
|
19116
19369
|
],
|
|
19117
|
-
|
|
19118
|
-
"active",
|
|
19119
|
-
"exited",
|
|
19120
|
-
null
|
|
19121
|
-
]
|
|
19370
|
+
maxLength: 50
|
|
19122
19371
|
}
|
|
19123
19372
|
},
|
|
19124
19373
|
{
|
|
19125
|
-
name: "
|
|
19374
|
+
name: "event_type",
|
|
19126
19375
|
in: "query",
|
|
19127
19376
|
schema: {
|
|
19128
19377
|
type: [
|
|
@@ -19130,27 +19379,22 @@ var openapi_default = {
|
|
|
19130
19379
|
"null"
|
|
19131
19380
|
],
|
|
19132
19381
|
enum: [
|
|
19133
|
-
"
|
|
19134
|
-
"
|
|
19382
|
+
"all",
|
|
19383
|
+
"join",
|
|
19384
|
+
"leave",
|
|
19135
19385
|
null
|
|
19136
19386
|
]
|
|
19137
19387
|
}
|
|
19138
19388
|
},
|
|
19139
19389
|
{
|
|
19140
|
-
name: "
|
|
19390
|
+
name: "page",
|
|
19141
19391
|
in: "query",
|
|
19142
19392
|
schema: {
|
|
19143
19393
|
type: [
|
|
19144
|
-
"
|
|
19394
|
+
"integer",
|
|
19145
19395
|
"null"
|
|
19146
19396
|
],
|
|
19147
|
-
|
|
19148
|
-
"asc",
|
|
19149
|
-
"desc",
|
|
19150
|
-
"ASC",
|
|
19151
|
-
"DESC",
|
|
19152
|
-
null
|
|
19153
|
-
]
|
|
19397
|
+
minimum: 1
|
|
19154
19398
|
}
|
|
19155
19399
|
},
|
|
19156
19400
|
{
|
|
@@ -19161,7 +19405,8 @@ var openapi_default = {
|
|
|
19161
19405
|
"integer",
|
|
19162
19406
|
"null"
|
|
19163
19407
|
],
|
|
19164
|
-
minimum: 1
|
|
19408
|
+
minimum: 1,
|
|
19409
|
+
maximum: 100
|
|
19165
19410
|
}
|
|
19166
19411
|
},
|
|
19167
19412
|
{
|
|
@@ -19193,21 +19438,22 @@ var openapi_default = {
|
|
|
19193
19438
|
type: [
|
|
19194
19439
|
"string",
|
|
19195
19440
|
"null"
|
|
19196
|
-
]
|
|
19441
|
+
],
|
|
19442
|
+
maxLength: 64
|
|
19197
19443
|
}
|
|
19198
19444
|
},
|
|
19199
19445
|
{
|
|
19200
|
-
name: "
|
|
19446
|
+
name: "date_mode",
|
|
19201
19447
|
in: "query",
|
|
19202
19448
|
schema: {
|
|
19203
19449
|
type: [
|
|
19204
|
-
"
|
|
19450
|
+
"string",
|
|
19205
19451
|
"null"
|
|
19206
19452
|
],
|
|
19207
19453
|
enum: [
|
|
19208
|
-
"
|
|
19209
|
-
"
|
|
19210
|
-
"
|
|
19454
|
+
"both",
|
|
19455
|
+
"joins_only",
|
|
19456
|
+
"leaves_only",
|
|
19211
19457
|
null
|
|
19212
19458
|
]
|
|
19213
19459
|
}
|
|
@@ -19233,14 +19479,30 @@ var openapi_default = {
|
|
|
19233
19479
|
schema: {
|
|
19234
19480
|
type: "array",
|
|
19235
19481
|
items: {
|
|
19236
|
-
type: "string"
|
|
19482
|
+
type: "string",
|
|
19483
|
+
maxLength: 50
|
|
19237
19484
|
}
|
|
19238
19485
|
}
|
|
19486
|
+
},
|
|
19487
|
+
{
|
|
19488
|
+
name: "participant_filter",
|
|
19489
|
+
in: "query",
|
|
19490
|
+
schema: {
|
|
19491
|
+
type: [
|
|
19492
|
+
"string",
|
|
19493
|
+
"null"
|
|
19494
|
+
],
|
|
19495
|
+
enum: [
|
|
19496
|
+
"all",
|
|
19497
|
+
"active",
|
|
19498
|
+
null
|
|
19499
|
+
]
|
|
19500
|
+
}
|
|
19239
19501
|
}
|
|
19240
19502
|
],
|
|
19241
19503
|
responses: {
|
|
19242
19504
|
"200": {
|
|
19243
|
-
description: "Paginated set of `
|
|
19505
|
+
description: "Paginated set of `ParticipantEventResource`",
|
|
19244
19506
|
content: {
|
|
19245
19507
|
"application/json": {
|
|
19246
19508
|
schema: {
|
|
@@ -19249,7 +19511,7 @@ var openapi_default = {
|
|
|
19249
19511
|
data: {
|
|
19250
19512
|
type: "array",
|
|
19251
19513
|
items: {
|
|
19252
|
-
$ref: "#/components/schemas/
|
|
19514
|
+
$ref: "#/components/schemas/ParticipantEventResource"
|
|
19253
19515
|
}
|
|
19254
19516
|
},
|
|
19255
19517
|
links: {
|
|
@@ -19391,173 +19653,90 @@ var openapi_default = {
|
|
|
19391
19653
|
$ref: "#/components/responses/CommunitiesValidationException"
|
|
19392
19654
|
}
|
|
19393
19655
|
},
|
|
19394
|
-
"x-fc-proposito": "
|
|
19656
|
+
"x-fc-proposito": "Sirve para obtener el registro de entradas y salidas de toda una campa\xF1a, con fecha, grupo y pa\xEDs, filtrando por tipo de evento, fechas o grupos.",
|
|
19395
19657
|
"x-fc-domain": "communities",
|
|
19396
19658
|
"x-fc-permission": "communities:read"
|
|
19397
19659
|
}
|
|
19398
19660
|
},
|
|
19399
|
-
"/api/v1/
|
|
19661
|
+
"/api/v1/groups/participants": {
|
|
19400
19662
|
get: {
|
|
19401
|
-
operationId: "
|
|
19402
|
-
description: "**Permiso requerido:** `communities:read` (Leer)\n\n**Con API key:** la respuesta siempre viene paginada. Tama\xF1o de p\xE1gina: `perPage` (por defecto 25, m\xE1ximo 100; un valor mayor, menor que 1 o no entero responde `422`).",
|
|
19403
|
-
summary: "Listar
|
|
19663
|
+
operationId: "group.participants",
|
|
19664
|
+
description: "**Permiso requerido:** `communities:read` (Leer)\n\n**Con API key:** la respuesta siempre viene paginada. Tama\xF1o de p\xE1gina: `perPage` (por defecto 25, m\xE1ximo 100; un valor mayor, menor que 1 o no entero responde `422`). Orden: `orderBy` acepta `created_at`, `id`, `updated_at`; direcci\xF3n `orderDirection`: `asc` o `desc`. Otro valor responde `422`.",
|
|
19665
|
+
summary: "Listar los participantes de un grupo",
|
|
19404
19666
|
tags: [
|
|
19405
19667
|
"Participantes"
|
|
19406
19668
|
],
|
|
19407
19669
|
parameters: [
|
|
19408
19670
|
{
|
|
19409
|
-
name: "
|
|
19410
|
-
in: "
|
|
19671
|
+
name: "groupUid",
|
|
19672
|
+
in: "query",
|
|
19411
19673
|
required: true,
|
|
19412
|
-
description: "The campaign ID",
|
|
19413
19674
|
schema: {
|
|
19414
|
-
type: "
|
|
19675
|
+
type: "string"
|
|
19415
19676
|
}
|
|
19416
19677
|
},
|
|
19417
19678
|
{
|
|
19418
|
-
name: "
|
|
19679
|
+
name: "phone",
|
|
19419
19680
|
in: "query",
|
|
19420
19681
|
schema: {
|
|
19421
|
-
type:
|
|
19422
|
-
"string",
|
|
19423
|
-
"null"
|
|
19424
|
-
],
|
|
19425
|
-
maxLength: 50
|
|
19682
|
+
type: "string"
|
|
19426
19683
|
}
|
|
19427
19684
|
},
|
|
19428
19685
|
{
|
|
19429
|
-
name: "
|
|
19686
|
+
name: "isAdmin",
|
|
19430
19687
|
in: "query",
|
|
19431
19688
|
schema: {
|
|
19432
|
-
type:
|
|
19433
|
-
"string",
|
|
19434
|
-
"null"
|
|
19435
|
-
],
|
|
19689
|
+
type: "string",
|
|
19436
19690
|
enum: [
|
|
19437
|
-
"
|
|
19438
|
-
"
|
|
19439
|
-
"
|
|
19440
|
-
|
|
19691
|
+
"true",
|
|
19692
|
+
"false",
|
|
19693
|
+
"1",
|
|
19694
|
+
"0"
|
|
19441
19695
|
]
|
|
19442
19696
|
}
|
|
19443
19697
|
},
|
|
19444
19698
|
{
|
|
19445
|
-
name: "
|
|
19446
|
-
in: "query",
|
|
19447
|
-
schema: {
|
|
19448
|
-
type: [
|
|
19449
|
-
"integer",
|
|
19450
|
-
"null"
|
|
19451
|
-
],
|
|
19452
|
-
minimum: 1
|
|
19453
|
-
}
|
|
19454
|
-
},
|
|
19455
|
-
{
|
|
19456
|
-
name: "perPage",
|
|
19457
|
-
in: "query",
|
|
19458
|
-
schema: {
|
|
19459
|
-
type: [
|
|
19460
|
-
"integer",
|
|
19461
|
-
"null"
|
|
19462
|
-
],
|
|
19463
|
-
minimum: 1,
|
|
19464
|
-
maximum: 100
|
|
19465
|
-
}
|
|
19466
|
-
},
|
|
19467
|
-
{
|
|
19468
|
-
name: "start_date",
|
|
19469
|
-
in: "query",
|
|
19470
|
-
schema: {
|
|
19471
|
-
type: [
|
|
19472
|
-
"string",
|
|
19473
|
-
"null"
|
|
19474
|
-
],
|
|
19475
|
-
format: "date-time"
|
|
19476
|
-
}
|
|
19477
|
-
},
|
|
19478
|
-
{
|
|
19479
|
-
name: "end_date",
|
|
19480
|
-
in: "query",
|
|
19481
|
-
schema: {
|
|
19482
|
-
type: [
|
|
19483
|
-
"string",
|
|
19484
|
-
"null"
|
|
19485
|
-
],
|
|
19486
|
-
format: "date-time"
|
|
19487
|
-
}
|
|
19488
|
-
},
|
|
19489
|
-
{
|
|
19490
|
-
name: "timezone",
|
|
19491
|
-
in: "query",
|
|
19492
|
-
schema: {
|
|
19493
|
-
type: [
|
|
19494
|
-
"string",
|
|
19495
|
-
"null"
|
|
19496
|
-
],
|
|
19497
|
-
maxLength: 64
|
|
19498
|
-
}
|
|
19499
|
-
},
|
|
19500
|
-
{
|
|
19501
|
-
name: "date_mode",
|
|
19699
|
+
name: "eligibleForAdmin",
|
|
19502
19700
|
in: "query",
|
|
19503
19701
|
schema: {
|
|
19504
|
-
type:
|
|
19505
|
-
"string",
|
|
19506
|
-
"null"
|
|
19507
|
-
],
|
|
19702
|
+
type: "string",
|
|
19508
19703
|
enum: [
|
|
19509
|
-
"
|
|
19510
|
-
"
|
|
19511
|
-
"
|
|
19512
|
-
|
|
19704
|
+
"true",
|
|
19705
|
+
"false",
|
|
19706
|
+
"1",
|
|
19707
|
+
"0"
|
|
19513
19708
|
]
|
|
19514
19709
|
}
|
|
19515
19710
|
},
|
|
19516
19711
|
{
|
|
19517
|
-
name: "
|
|
19712
|
+
name: "orderBy",
|
|
19518
19713
|
in: "query",
|
|
19519
19714
|
schema: {
|
|
19520
|
-
type:
|
|
19521
|
-
"string",
|
|
19522
|
-
"null"
|
|
19523
|
-
],
|
|
19524
|
-
enum: [
|
|
19525
|
-
"all",
|
|
19526
|
-
"none",
|
|
19527
|
-
null
|
|
19528
|
-
]
|
|
19715
|
+
type: "string"
|
|
19529
19716
|
}
|
|
19530
19717
|
},
|
|
19531
19718
|
{
|
|
19532
|
-
name: "
|
|
19719
|
+
name: "orderDirection",
|
|
19533
19720
|
in: "query",
|
|
19534
19721
|
schema: {
|
|
19535
|
-
type: "
|
|
19536
|
-
|
|
19537
|
-
|
|
19538
|
-
|
|
19539
|
-
|
|
19722
|
+
type: "string",
|
|
19723
|
+
enum: [
|
|
19724
|
+
"asc",
|
|
19725
|
+
"desc"
|
|
19726
|
+
]
|
|
19540
19727
|
}
|
|
19541
19728
|
},
|
|
19542
19729
|
{
|
|
19543
|
-
name: "
|
|
19730
|
+
name: "perPage",
|
|
19544
19731
|
in: "query",
|
|
19545
19732
|
schema: {
|
|
19546
|
-
type:
|
|
19547
|
-
"string",
|
|
19548
|
-
"null"
|
|
19549
|
-
],
|
|
19550
|
-
enum: [
|
|
19551
|
-
"all",
|
|
19552
|
-
"active",
|
|
19553
|
-
null
|
|
19554
|
-
]
|
|
19733
|
+
type: "integer"
|
|
19555
19734
|
}
|
|
19556
19735
|
}
|
|
19557
19736
|
],
|
|
19558
19737
|
responses: {
|
|
19559
19738
|
"200": {
|
|
19560
|
-
description: "Paginated set of `
|
|
19739
|
+
description: "Paginated set of `ParticipantResource`",
|
|
19561
19740
|
content: {
|
|
19562
19741
|
"application/json": {
|
|
19563
19742
|
schema: {
|
|
@@ -19566,241 +19745,7 @@ var openapi_default = {
|
|
|
19566
19745
|
data: {
|
|
19567
19746
|
type: "array",
|
|
19568
19747
|
items: {
|
|
19569
|
-
$ref: "#/components/schemas/
|
|
19570
|
-
}
|
|
19571
|
-
},
|
|
19572
|
-
links: {
|
|
19573
|
-
type: "object",
|
|
19574
|
-
properties: {
|
|
19575
|
-
first: {
|
|
19576
|
-
type: [
|
|
19577
|
-
"string",
|
|
19578
|
-
"null"
|
|
19579
|
-
]
|
|
19580
|
-
},
|
|
19581
|
-
last: {
|
|
19582
|
-
type: [
|
|
19583
|
-
"string",
|
|
19584
|
-
"null"
|
|
19585
|
-
]
|
|
19586
|
-
},
|
|
19587
|
-
prev: {
|
|
19588
|
-
type: [
|
|
19589
|
-
"string",
|
|
19590
|
-
"null"
|
|
19591
|
-
]
|
|
19592
|
-
},
|
|
19593
|
-
next: {
|
|
19594
|
-
type: [
|
|
19595
|
-
"string",
|
|
19596
|
-
"null"
|
|
19597
|
-
]
|
|
19598
|
-
}
|
|
19599
|
-
},
|
|
19600
|
-
required: [
|
|
19601
|
-
"first",
|
|
19602
|
-
"last",
|
|
19603
|
-
"prev",
|
|
19604
|
-
"next"
|
|
19605
|
-
]
|
|
19606
|
-
},
|
|
19607
|
-
meta: {
|
|
19608
|
-
type: "object",
|
|
19609
|
-
properties: {
|
|
19610
|
-
current_page: {
|
|
19611
|
-
type: "integer",
|
|
19612
|
-
minimum: 1
|
|
19613
|
-
},
|
|
19614
|
-
from: {
|
|
19615
|
-
type: [
|
|
19616
|
-
"integer",
|
|
19617
|
-
"null"
|
|
19618
|
-
],
|
|
19619
|
-
minimum: 1
|
|
19620
|
-
},
|
|
19621
|
-
last_page: {
|
|
19622
|
-
type: "integer",
|
|
19623
|
-
minimum: 1
|
|
19624
|
-
},
|
|
19625
|
-
links: {
|
|
19626
|
-
type: "array",
|
|
19627
|
-
description: "Generated paginator links.",
|
|
19628
|
-
items: {
|
|
19629
|
-
type: "object",
|
|
19630
|
-
properties: {
|
|
19631
|
-
url: {
|
|
19632
|
-
type: [
|
|
19633
|
-
"string",
|
|
19634
|
-
"null"
|
|
19635
|
-
]
|
|
19636
|
-
},
|
|
19637
|
-
label: {
|
|
19638
|
-
type: "string"
|
|
19639
|
-
},
|
|
19640
|
-
active: {
|
|
19641
|
-
type: "boolean"
|
|
19642
|
-
}
|
|
19643
|
-
},
|
|
19644
|
-
required: [
|
|
19645
|
-
"url",
|
|
19646
|
-
"label",
|
|
19647
|
-
"active"
|
|
19648
|
-
]
|
|
19649
|
-
}
|
|
19650
|
-
},
|
|
19651
|
-
path: {
|
|
19652
|
-
type: [
|
|
19653
|
-
"string",
|
|
19654
|
-
"null"
|
|
19655
|
-
],
|
|
19656
|
-
description: "Base path for paginator generated URLs."
|
|
19657
|
-
},
|
|
19658
|
-
per_page: {
|
|
19659
|
-
type: "integer",
|
|
19660
|
-
description: "Number of items shown per page.",
|
|
19661
|
-
minimum: 0
|
|
19662
|
-
},
|
|
19663
|
-
to: {
|
|
19664
|
-
type: [
|
|
19665
|
-
"integer",
|
|
19666
|
-
"null"
|
|
19667
|
-
],
|
|
19668
|
-
description: "Number of the last item in the slice.",
|
|
19669
|
-
minimum: 1
|
|
19670
|
-
},
|
|
19671
|
-
total: {
|
|
19672
|
-
type: "integer",
|
|
19673
|
-
description: "Total number of items being paginated.",
|
|
19674
|
-
minimum: 0
|
|
19675
|
-
}
|
|
19676
|
-
},
|
|
19677
|
-
required: [
|
|
19678
|
-
"current_page",
|
|
19679
|
-
"from",
|
|
19680
|
-
"last_page",
|
|
19681
|
-
"links",
|
|
19682
|
-
"path",
|
|
19683
|
-
"per_page",
|
|
19684
|
-
"to",
|
|
19685
|
-
"total"
|
|
19686
|
-
]
|
|
19687
|
-
}
|
|
19688
|
-
},
|
|
19689
|
-
required: [
|
|
19690
|
-
"data",
|
|
19691
|
-
"links",
|
|
19692
|
-
"meta"
|
|
19693
|
-
]
|
|
19694
|
-
}
|
|
19695
|
-
}
|
|
19696
|
-
}
|
|
19697
|
-
},
|
|
19698
|
-
"401": {
|
|
19699
|
-
$ref: "#/components/responses/CommunitiesAuthenticationException"
|
|
19700
|
-
},
|
|
19701
|
-
"403": {
|
|
19702
|
-
$ref: "#/components/responses/CommunitiesAuthorizationException"
|
|
19703
|
-
},
|
|
19704
|
-
"404": {
|
|
19705
|
-
$ref: "#/components/responses/CommunitiesModelNotFoundException"
|
|
19706
|
-
},
|
|
19707
|
-
"422": {
|
|
19708
|
-
$ref: "#/components/responses/CommunitiesValidationException"
|
|
19709
|
-
}
|
|
19710
|
-
},
|
|
19711
|
-
"x-fc-proposito": "Sirve para obtener el registro de entradas y salidas de toda una campa\xF1a, con fecha, grupo y pa\xEDs, filtrando por tipo de evento, fechas o grupos.",
|
|
19712
|
-
"x-fc-domain": "communities",
|
|
19713
|
-
"x-fc-permission": "communities:read"
|
|
19714
|
-
}
|
|
19715
|
-
},
|
|
19716
|
-
"/api/v1/groups/participants": {
|
|
19717
|
-
get: {
|
|
19718
|
-
operationId: "group.participants",
|
|
19719
|
-
description: "**Permiso requerido:** `communities:read` (Leer)\n\n**Con API key:** la respuesta siempre viene paginada. Tama\xF1o de p\xE1gina: `perPage` (por defecto 25, m\xE1ximo 100; un valor mayor, menor que 1 o no entero responde `422`). Orden: `orderBy` acepta `created_at`, `id`, `updated_at`; direcci\xF3n `orderDirection`: `asc` o `desc`. Otro valor responde `422`.",
|
|
19720
|
-
summary: "Listar los participantes de un grupo",
|
|
19721
|
-
tags: [
|
|
19722
|
-
"Participantes"
|
|
19723
|
-
],
|
|
19724
|
-
parameters: [
|
|
19725
|
-
{
|
|
19726
|
-
name: "groupUid",
|
|
19727
|
-
in: "query",
|
|
19728
|
-
required: true,
|
|
19729
|
-
schema: {
|
|
19730
|
-
type: "string"
|
|
19731
|
-
}
|
|
19732
|
-
},
|
|
19733
|
-
{
|
|
19734
|
-
name: "phone",
|
|
19735
|
-
in: "query",
|
|
19736
|
-
schema: {
|
|
19737
|
-
type: "string"
|
|
19738
|
-
}
|
|
19739
|
-
},
|
|
19740
|
-
{
|
|
19741
|
-
name: "isAdmin",
|
|
19742
|
-
in: "query",
|
|
19743
|
-
schema: {
|
|
19744
|
-
type: "string",
|
|
19745
|
-
enum: [
|
|
19746
|
-
"true",
|
|
19747
|
-
"false",
|
|
19748
|
-
"1",
|
|
19749
|
-
"0"
|
|
19750
|
-
]
|
|
19751
|
-
}
|
|
19752
|
-
},
|
|
19753
|
-
{
|
|
19754
|
-
name: "eligibleForAdmin",
|
|
19755
|
-
in: "query",
|
|
19756
|
-
schema: {
|
|
19757
|
-
type: "string",
|
|
19758
|
-
enum: [
|
|
19759
|
-
"true",
|
|
19760
|
-
"false",
|
|
19761
|
-
"1",
|
|
19762
|
-
"0"
|
|
19763
|
-
]
|
|
19764
|
-
}
|
|
19765
|
-
},
|
|
19766
|
-
{
|
|
19767
|
-
name: "orderBy",
|
|
19768
|
-
in: "query",
|
|
19769
|
-
schema: {
|
|
19770
|
-
type: "string"
|
|
19771
|
-
}
|
|
19772
|
-
},
|
|
19773
|
-
{
|
|
19774
|
-
name: "orderDirection",
|
|
19775
|
-
in: "query",
|
|
19776
|
-
schema: {
|
|
19777
|
-
type: "string",
|
|
19778
|
-
enum: [
|
|
19779
|
-
"asc",
|
|
19780
|
-
"desc"
|
|
19781
|
-
]
|
|
19782
|
-
}
|
|
19783
|
-
},
|
|
19784
|
-
{
|
|
19785
|
-
name: "perPage",
|
|
19786
|
-
in: "query",
|
|
19787
|
-
schema: {
|
|
19788
|
-
type: "integer"
|
|
19789
|
-
}
|
|
19790
|
-
}
|
|
19791
|
-
],
|
|
19792
|
-
responses: {
|
|
19793
|
-
"200": {
|
|
19794
|
-
description: "Paginated set of `ParticipantResource`",
|
|
19795
|
-
content: {
|
|
19796
|
-
"application/json": {
|
|
19797
|
-
schema: {
|
|
19798
|
-
type: "object",
|
|
19799
|
-
properties: {
|
|
19800
|
-
data: {
|
|
19801
|
-
type: "array",
|
|
19802
|
-
items: {
|
|
19803
|
-
$ref: "#/components/schemas/ParticipantResource"
|
|
19748
|
+
$ref: "#/components/schemas/ParticipantResource"
|
|
19804
19749
|
}
|
|
19805
19750
|
},
|
|
19806
19751
|
links: {
|
|
@@ -24830,7 +24775,164 @@ var openapi_default = {
|
|
|
24830
24775
|
},
|
|
24831
24776
|
summary: "Listar n\xFAmeros",
|
|
24832
24777
|
"x-fc-proposito": "Sirve para listar tus n\xFAmeros de WhatsApp con su proveedor, si est\xE1n conectados y activos y su estado de salud, y obtener los IDs que usan las dem\xE1s operaciones.",
|
|
24833
|
-
"x-fc-domain": "accounts"
|
|
24778
|
+
"x-fc-domain": "accounts",
|
|
24779
|
+
parameters: [
|
|
24780
|
+
{
|
|
24781
|
+
name: "external_reference",
|
|
24782
|
+
in: "query",
|
|
24783
|
+
required: false,
|
|
24784
|
+
description: "Only the numbers with exactly this reference.",
|
|
24785
|
+
schema: {
|
|
24786
|
+
type: "string",
|
|
24787
|
+
maxLength: 255
|
|
24788
|
+
},
|
|
24789
|
+
"x-fc-pending": "accounts-alta-de-numeros"
|
|
24790
|
+
}
|
|
24791
|
+
]
|
|
24792
|
+
},
|
|
24793
|
+
post: {
|
|
24794
|
+
operationId: "pendingSandboxDevices.store",
|
|
24795
|
+
summary: "Create a number by QR",
|
|
24796
|
+
description: "Creates a number of the account to connect by QR, with `connected: false`; ask for its code with `GET /devices/{device}/qr`. Same path and same rejections as the panel: the quota counts phones (QR and Meta of the same phone count once). `same_number_as` adds QR to an official number in coexistence without taking a place. An official number is created only with a sign-up link.\n\n**Pending of the spec**: from the contract of the sign-up by API (22-10, A1), while accounts writes it; its OpenAPI will be the final source.\n\nAmendment v0.1 (23-10): as accounts did it in `feat/alta-de-numeros-api` `52d49b319`. The pool keeps at most one unconnected QR number per account: another one answers `409 pending_number_exists` with its `device_id`. Without an active plan, `403` without `error`.\n\nOptional `country` (two letters, any case): the country of the phone that will scan the QR, where the WhatsApp session is opened from.",
|
|
24797
|
+
tags: [
|
|
24798
|
+
"Device"
|
|
24799
|
+
],
|
|
24800
|
+
"x-fc-domain": "accounts",
|
|
24801
|
+
"x-fc-permission": "accounts:operate",
|
|
24802
|
+
"x-fc-pending": "accounts-alta-de-numeros",
|
|
24803
|
+
parameters: [
|
|
24804
|
+
{
|
|
24805
|
+
name: "Idempotency-Key",
|
|
24806
|
+
in: "header",
|
|
24807
|
+
required: false,
|
|
24808
|
+
description: "Up to 255 characters, kept 24 hours. The same key with the same body gives back the same answer without creating anything; with another body, `422 idempotency_key_mismatch`; while the first call runs, `409 operation_in_progress`.",
|
|
24809
|
+
schema: {
|
|
24810
|
+
type: "string",
|
|
24811
|
+
maxLength: 255
|
|
24812
|
+
}
|
|
24813
|
+
}
|
|
24814
|
+
],
|
|
24815
|
+
requestBody: {
|
|
24816
|
+
required: true,
|
|
24817
|
+
content: {
|
|
24818
|
+
"application/json": {
|
|
24819
|
+
schema: {
|
|
24820
|
+
type: "object",
|
|
24821
|
+
properties: {
|
|
24822
|
+
connection: {
|
|
24823
|
+
type: "string",
|
|
24824
|
+
enum: [
|
|
24825
|
+
"qr"
|
|
24826
|
+
],
|
|
24827
|
+
description: "For now the only value."
|
|
24828
|
+
},
|
|
24829
|
+
alias: {
|
|
24830
|
+
type: [
|
|
24831
|
+
"string",
|
|
24832
|
+
"null"
|
|
24833
|
+
],
|
|
24834
|
+
maxLength: 255
|
|
24835
|
+
},
|
|
24836
|
+
external_reference: {
|
|
24837
|
+
type: [
|
|
24838
|
+
"string",
|
|
24839
|
+
"null"
|
|
24840
|
+
],
|
|
24841
|
+
maxLength: 255,
|
|
24842
|
+
description: "Free text to find your customer: not unique. It comes back in the number, in `GET /devices?external_reference=` and in every `device.*` event."
|
|
24843
|
+
},
|
|
24844
|
+
same_number_as: {
|
|
24845
|
+
type: [
|
|
24846
|
+
"integer",
|
|
24847
|
+
"null"
|
|
24848
|
+
],
|
|
24849
|
+
description: "An official number in coexistence that gets QR too."
|
|
24850
|
+
},
|
|
24851
|
+
country: {
|
|
24852
|
+
type: [
|
|
24853
|
+
"string",
|
|
24854
|
+
"null"
|
|
24855
|
+
],
|
|
24856
|
+
pattern: "^[A-Za-z]{2}$",
|
|
24857
|
+
description: "The country of the phone that will scan the QR (ISO 3166-1 alpha-2, two letters, any case): the WhatsApp session is opened from there. Without it, the default."
|
|
24858
|
+
}
|
|
24859
|
+
},
|
|
24860
|
+
required: [
|
|
24861
|
+
"connection"
|
|
24862
|
+
]
|
|
24863
|
+
}
|
|
24864
|
+
}
|
|
24865
|
+
}
|
|
24866
|
+
},
|
|
24867
|
+
responses: {
|
|
24868
|
+
"201": {
|
|
24869
|
+
description: "Created, not connected yet",
|
|
24870
|
+
content: {
|
|
24871
|
+
"application/json": {
|
|
24872
|
+
schema: {
|
|
24873
|
+
type: "object",
|
|
24874
|
+
properties: {
|
|
24875
|
+
data: {
|
|
24876
|
+
$ref: "#/components/schemas/DeviceResource"
|
|
24877
|
+
}
|
|
24878
|
+
},
|
|
24879
|
+
required: [
|
|
24880
|
+
"data"
|
|
24881
|
+
]
|
|
24882
|
+
}
|
|
24883
|
+
}
|
|
24884
|
+
},
|
|
24885
|
+
headers: {
|
|
24886
|
+
"Idempotent-Replayed": {
|
|
24887
|
+
description: "`true` when the answer is a replay of the same `Idempotency-Key`.",
|
|
24888
|
+
schema: {
|
|
24889
|
+
type: "string"
|
|
24890
|
+
}
|
|
24891
|
+
}
|
|
24892
|
+
}
|
|
24893
|
+
},
|
|
24894
|
+
"403": {
|
|
24895
|
+
description: "`numbers_not_included`, `number_limit_reached` (with `limit`, `used` and `pending_device_ids`) or `connect_not_allowed`",
|
|
24896
|
+
content: {
|
|
24897
|
+
"application/json": {
|
|
24898
|
+
schema: {
|
|
24899
|
+
$ref: "#/components/schemas/PendingSandboxAccountsError"
|
|
24900
|
+
}
|
|
24901
|
+
}
|
|
24902
|
+
}
|
|
24903
|
+
},
|
|
24904
|
+
"409": {
|
|
24905
|
+
description: "`pending_number_exists` (with `device_id`) or `operation_in_progress`",
|
|
24906
|
+
content: {
|
|
24907
|
+
"application/json": {
|
|
24908
|
+
schema: {
|
|
24909
|
+
$ref: "#/components/schemas/PendingSandboxAccountsError"
|
|
24910
|
+
}
|
|
24911
|
+
}
|
|
24912
|
+
}
|
|
24913
|
+
},
|
|
24914
|
+
"422": {
|
|
24915
|
+
description: "`invalid_request` (with `errors`), `invalid_reference_device`, `qr_requires_coexistence`, `method_already_present` (with `device_id`), `number_suspended_by_plan`, `idempotency_key_mismatch` or `invalid_idempotency_key`",
|
|
24916
|
+
content: {
|
|
24917
|
+
"application/json": {
|
|
24918
|
+
schema: {
|
|
24919
|
+
$ref: "#/components/schemas/PendingSandboxAccountsError"
|
|
24920
|
+
}
|
|
24921
|
+
}
|
|
24922
|
+
}
|
|
24923
|
+
},
|
|
24924
|
+
"500": {
|
|
24925
|
+
description: "`provider_error`",
|
|
24926
|
+
content: {
|
|
24927
|
+
"application/json": {
|
|
24928
|
+
schema: {
|
|
24929
|
+
$ref: "#/components/schemas/PendingSandboxAccountsError"
|
|
24930
|
+
}
|
|
24931
|
+
}
|
|
24932
|
+
}
|
|
24933
|
+
}
|
|
24934
|
+
},
|
|
24935
|
+
"x-fc-validation-error": "invalid_request"
|
|
24834
24936
|
}
|
|
24835
24937
|
},
|
|
24836
24938
|
"/api/v1/devices/{device}/me": {
|
|
@@ -25161,6 +25263,14 @@ var openapi_default = {
|
|
|
25161
25263
|
capi_consent: {
|
|
25162
25264
|
type: "boolean",
|
|
25163
25265
|
description: "Meta Conversions API (CAPI) opt-in consent \u2014 capi-meta-connection Inc 3\n(design.md D3). Optional: only meaningful on the Meta branch, and even\nthere it is a no-op unless the `capi` feature flag is enabled for the\naccount (defense in depth, see updateMetaDevice below)."
|
|
25266
|
+
},
|
|
25267
|
+
external_reference: {
|
|
25268
|
+
type: [
|
|
25269
|
+
"string",
|
|
25270
|
+
"null"
|
|
25271
|
+
],
|
|
25272
|
+
maxLength: 255,
|
|
25273
|
+
description: "Free text to find your customer; `null` clears it."
|
|
25164
25274
|
}
|
|
25165
25275
|
}
|
|
25166
25276
|
}
|
|
@@ -25338,7 +25448,8 @@ var openapi_default = {
|
|
|
25338
25448
|
"x-fc-destructive": false,
|
|
25339
25449
|
summary: "Actualizar un n\xFAmero",
|
|
25340
25450
|
"x-fc-proposito": "Sirve para actualizar los datos de un n\xFAmero (alias, color, imagen) o completar sus datos de WhatsApp Business, como WABA y phone number ID, y el consentimiento de Conversions API.",
|
|
25341
|
-
"x-fc-domain": "accounts"
|
|
25451
|
+
"x-fc-domain": "accounts",
|
|
25452
|
+
"x-fc-pending": "accounts-alta-de-numeros"
|
|
25342
25453
|
},
|
|
25343
25454
|
delete: {
|
|
25344
25455
|
operationId: "device.destroy",
|
|
@@ -25359,14 +25470,7 @@ var openapi_default = {
|
|
|
25359
25470
|
],
|
|
25360
25471
|
responses: {
|
|
25361
25472
|
"200": {
|
|
25362
|
-
description: ""
|
|
25363
|
-
content: {
|
|
25364
|
-
"application/json": {
|
|
25365
|
-
schema: {
|
|
25366
|
-
type: "object"
|
|
25367
|
-
}
|
|
25368
|
-
}
|
|
25369
|
-
}
|
|
25473
|
+
description: "Deleted: an empty body, as today. Emits `device.deleted`."
|
|
25370
25474
|
},
|
|
25371
25475
|
"401": {
|
|
25372
25476
|
$ref: "#/components/responses/AccountsAuthenticationException"
|
|
@@ -25383,7 +25487,8 @@ var openapi_default = {
|
|
|
25383
25487
|
"x-fc-destructive": true,
|
|
25384
25488
|
summary: "Eliminar un n\xFAmero",
|
|
25385
25489
|
"x-fc-proposito": "Usalo para dar de baja un n\xFAmero de la cuenta de forma definitiva. Es irreversible y requiere el permiso `accounts:operate`.",
|
|
25386
|
-
"x-fc-domain": "accounts"
|
|
25490
|
+
"x-fc-domain": "accounts",
|
|
25491
|
+
"x-fc-pending-response": "accounts-alta-de-numeros"
|
|
25387
25492
|
}
|
|
25388
25493
|
},
|
|
25389
25494
|
"/api/v1/devices/{device}/reset": {
|
|
@@ -29603,143 +29708,84 @@ var openapi_default = {
|
|
|
29603
29708
|
}
|
|
29604
29709
|
}
|
|
29605
29710
|
},
|
|
29606
|
-
"/api/v1/
|
|
29711
|
+
"/api/v1/devices/{device}/qr": {
|
|
29607
29712
|
get: {
|
|
29608
|
-
operationId: "
|
|
29609
|
-
summary: "
|
|
29610
|
-
description: "
|
|
29713
|
+
operationId: "pendingSandboxDevices.qr",
|
|
29714
|
+
summary: "The QR code of a number",
|
|
29715
|
+
description: "Creates the instance if needed and gives its QR code. Only for QR numbers: an official one answers `422 provider_unsupported`. Scanning it gives control of a WhatsApp, so it asks for `accounts:operate`.\n\n**Pending of the spec**: from the contract of the sign-up by API (22-10, A1), while accounts writes it; its OpenAPI will be the final source.\n\nAmendment v0.1 (23-10): as accounts did it in `feat/alta-de-numeros-api` `52d49b319`. `connected` is a boolean here (`0`/`1` in the number).\n\naccounts #1335 (25-10, in production): `expires_in` is 5 with a code, the cadence of the panel. Ask for it again every 5 seconds and show the new one at once: WhatsApp changes the code every few seconds and an old one does not link. The answer carries `Cache-Control: no-store`.",
|
|
29611
29716
|
tags: [
|
|
29612
|
-
"
|
|
29717
|
+
"Device"
|
|
29613
29718
|
],
|
|
29614
|
-
"x-fc-domain": "
|
|
29615
|
-
"x-fc-permission": "
|
|
29616
|
-
"x-fc-pending": "
|
|
29719
|
+
"x-fc-domain": "accounts",
|
|
29720
|
+
"x-fc-permission": "accounts:operate",
|
|
29721
|
+
"x-fc-pending": "accounts-alta-de-numeros",
|
|
29617
29722
|
parameters: [
|
|
29618
29723
|
{
|
|
29619
|
-
name: "
|
|
29724
|
+
name: "device",
|
|
29620
29725
|
in: "path",
|
|
29621
29726
|
required: true,
|
|
29622
29727
|
schema: {
|
|
29623
|
-
type: "
|
|
29728
|
+
type: "integer"
|
|
29624
29729
|
}
|
|
29625
29730
|
}
|
|
29626
29731
|
],
|
|
29627
29732
|
responses: {
|
|
29733
|
+
"200": {
|
|
29734
|
+
description: "OK",
|
|
29735
|
+
content: {
|
|
29736
|
+
"application/json": {
|
|
29737
|
+
schema: {
|
|
29738
|
+
$ref: "#/components/schemas/PendingSandboxDeviceQr"
|
|
29739
|
+
}
|
|
29740
|
+
}
|
|
29741
|
+
}
|
|
29742
|
+
},
|
|
29628
29743
|
"403": {
|
|
29629
|
-
description: "
|
|
29744
|
+
description: "`number_limit_reached`, `connect_not_allowed` or `payment_required`",
|
|
29630
29745
|
content: {
|
|
29631
29746
|
"application/json": {
|
|
29632
29747
|
schema: {
|
|
29633
|
-
|
|
29634
|
-
properties: {
|
|
29635
|
-
error: {
|
|
29636
|
-
type: "string",
|
|
29637
|
-
const: "route_not_available"
|
|
29638
|
-
},
|
|
29639
|
-
message: {
|
|
29640
|
-
type: "string"
|
|
29641
|
-
}
|
|
29642
|
-
},
|
|
29643
|
-
required: [
|
|
29644
|
-
"error",
|
|
29645
|
-
"message"
|
|
29646
|
-
]
|
|
29748
|
+
$ref: "#/components/schemas/PendingSandboxAccountsError"
|
|
29647
29749
|
}
|
|
29648
29750
|
}
|
|
29649
29751
|
}
|
|
29650
|
-
}
|
|
29651
|
-
|
|
29652
|
-
|
|
29653
|
-
|
|
29654
|
-
|
|
29655
|
-
|
|
29656
|
-
|
|
29657
|
-
|
|
29658
|
-
|
|
29659
|
-
tags: [
|
|
29660
|
-
"GroupActions"
|
|
29661
|
-
],
|
|
29662
|
-
"x-fc-domain": "communities",
|
|
29663
|
-
"x-fc-permission": "communities:operate",
|
|
29664
|
-
"x-fc-pending": "communities-group-actions",
|
|
29665
|
-
parameters: [
|
|
29666
|
-
{
|
|
29667
|
-
name: "group",
|
|
29668
|
-
in: "path",
|
|
29669
|
-
required: true,
|
|
29670
|
-
schema: {
|
|
29671
|
-
type: "string"
|
|
29752
|
+
},
|
|
29753
|
+
"404": {
|
|
29754
|
+
description: "It does not exist in the account",
|
|
29755
|
+
content: {
|
|
29756
|
+
"application/json": {
|
|
29757
|
+
schema: {
|
|
29758
|
+
$ref: "#/components/schemas/PendingSandboxModelNotFound"
|
|
29759
|
+
}
|
|
29760
|
+
}
|
|
29672
29761
|
}
|
|
29673
|
-
}
|
|
29674
|
-
|
|
29675
|
-
|
|
29676
|
-
"403": {
|
|
29677
|
-
description: "route_not_available: the group actions by API are off in production.",
|
|
29762
|
+
},
|
|
29763
|
+
"409": {
|
|
29764
|
+
description: "`trial_number_blocked`, `qr_requires_coexistence`, `number_suspended_by_plan`, `device_inactive` or `provisioning_quota_exceeded`",
|
|
29678
29765
|
content: {
|
|
29679
29766
|
"application/json": {
|
|
29680
29767
|
schema: {
|
|
29681
|
-
|
|
29682
|
-
properties: {
|
|
29683
|
-
error: {
|
|
29684
|
-
type: "string",
|
|
29685
|
-
const: "route_not_available"
|
|
29686
|
-
},
|
|
29687
|
-
message: {
|
|
29688
|
-
type: "string"
|
|
29689
|
-
}
|
|
29690
|
-
},
|
|
29691
|
-
required: [
|
|
29692
|
-
"error",
|
|
29693
|
-
"message"
|
|
29694
|
-
]
|
|
29768
|
+
$ref: "#/components/schemas/PendingSandboxAccountsError"
|
|
29695
29769
|
}
|
|
29696
29770
|
}
|
|
29697
29771
|
}
|
|
29698
|
-
}
|
|
29699
|
-
|
|
29700
|
-
|
|
29701
|
-
|
|
29702
|
-
|
|
29703
|
-
|
|
29704
|
-
|
|
29705
|
-
|
|
29706
|
-
|
|
29707
|
-
tags: [
|
|
29708
|
-
"GroupActions"
|
|
29709
|
-
],
|
|
29710
|
-
"x-fc-domain": "communities",
|
|
29711
|
-
"x-fc-permission": "communities:operate",
|
|
29712
|
-
"x-fc-pending": "communities-group-actions",
|
|
29713
|
-
parameters: [
|
|
29714
|
-
{
|
|
29715
|
-
name: "group",
|
|
29716
|
-
in: "path",
|
|
29717
|
-
required: true,
|
|
29718
|
-
schema: {
|
|
29719
|
-
type: "string"
|
|
29772
|
+
},
|
|
29773
|
+
"422": {
|
|
29774
|
+
description: "`provider_unsupported`",
|
|
29775
|
+
content: {
|
|
29776
|
+
"application/json": {
|
|
29777
|
+
schema: {
|
|
29778
|
+
$ref: "#/components/schemas/PendingSandboxAccountsError"
|
|
29779
|
+
}
|
|
29780
|
+
}
|
|
29720
29781
|
}
|
|
29721
|
-
}
|
|
29722
|
-
|
|
29723
|
-
|
|
29724
|
-
"403": {
|
|
29725
|
-
description: "route_not_available: the group actions by API are off in production.",
|
|
29782
|
+
},
|
|
29783
|
+
"502": {
|
|
29784
|
+
description: "`provider_error`",
|
|
29726
29785
|
content: {
|
|
29727
29786
|
"application/json": {
|
|
29728
29787
|
schema: {
|
|
29729
|
-
|
|
29730
|
-
properties: {
|
|
29731
|
-
error: {
|
|
29732
|
-
type: "string",
|
|
29733
|
-
const: "route_not_available"
|
|
29734
|
-
},
|
|
29735
|
-
message: {
|
|
29736
|
-
type: "string"
|
|
29737
|
-
}
|
|
29738
|
-
},
|
|
29739
|
-
required: [
|
|
29740
|
-
"error",
|
|
29741
|
-
"message"
|
|
29742
|
-
]
|
|
29788
|
+
$ref: "#/components/schemas/PendingSandboxAccountsError"
|
|
29743
29789
|
}
|
|
29744
29790
|
}
|
|
29745
29791
|
}
|
|
@@ -29747,527 +29793,385 @@ var openapi_default = {
|
|
|
29747
29793
|
}
|
|
29748
29794
|
}
|
|
29749
29795
|
},
|
|
29750
|
-
"/api/v1/
|
|
29751
|
-
|
|
29752
|
-
operationId: "
|
|
29753
|
-
summary: "
|
|
29754
|
-
description: "
|
|
29796
|
+
"/api/v1/connect-links": {
|
|
29797
|
+
get: {
|
|
29798
|
+
operationId: "pendingSandboxConnectLinks.index",
|
|
29799
|
+
summary: "List the sign-up links",
|
|
29800
|
+
description: "Paginated like the other lists (`perPage`, 25 by default, up to 100), newest first (by id). Filters by `status` and `external_reference`.\n\nAmendment v0.1 (23-10): as accounts did it in `feat/alta-de-numeros-api` `52d49b319`.",
|
|
29755
29801
|
tags: [
|
|
29756
|
-
"
|
|
29802
|
+
"ConnectLink"
|
|
29757
29803
|
],
|
|
29758
|
-
"x-fc-domain": "
|
|
29759
|
-
"x-fc-permission": "
|
|
29760
|
-
"x-fc-pending": "
|
|
29804
|
+
"x-fc-domain": "accounts",
|
|
29805
|
+
"x-fc-permission": "accounts:read",
|
|
29806
|
+
"x-fc-pending": "accounts-alta-de-numeros",
|
|
29761
29807
|
parameters: [
|
|
29762
29808
|
{
|
|
29763
|
-
name: "
|
|
29764
|
-
in: "
|
|
29765
|
-
required:
|
|
29809
|
+
name: "status",
|
|
29810
|
+
in: "query",
|
|
29811
|
+
required: false,
|
|
29766
29812
|
schema: {
|
|
29767
|
-
type: "string"
|
|
29813
|
+
type: "string",
|
|
29814
|
+
enum: [
|
|
29815
|
+
"pending",
|
|
29816
|
+
"completed",
|
|
29817
|
+
"failed",
|
|
29818
|
+
"expired",
|
|
29819
|
+
"revoked"
|
|
29820
|
+
]
|
|
29821
|
+
}
|
|
29822
|
+
},
|
|
29823
|
+
{
|
|
29824
|
+
name: "external_reference",
|
|
29825
|
+
in: "query",
|
|
29826
|
+
required: false,
|
|
29827
|
+
schema: {
|
|
29828
|
+
type: "string",
|
|
29829
|
+
maxLength: 255
|
|
29830
|
+
}
|
|
29831
|
+
},
|
|
29832
|
+
{
|
|
29833
|
+
name: "page",
|
|
29834
|
+
in: "query",
|
|
29835
|
+
required: false,
|
|
29836
|
+
schema: {
|
|
29837
|
+
type: "integer",
|
|
29838
|
+
minimum: 1
|
|
29839
|
+
}
|
|
29840
|
+
},
|
|
29841
|
+
{
|
|
29842
|
+
name: "perPage",
|
|
29843
|
+
in: "query",
|
|
29844
|
+
required: false,
|
|
29845
|
+
schema: {
|
|
29846
|
+
type: "integer",
|
|
29847
|
+
minimum: 1,
|
|
29848
|
+
maximum: 100
|
|
29768
29849
|
}
|
|
29769
29850
|
}
|
|
29770
29851
|
],
|
|
29771
29852
|
responses: {
|
|
29772
|
-
"
|
|
29773
|
-
description: "
|
|
29853
|
+
"200": {
|
|
29854
|
+
description: "OK",
|
|
29774
29855
|
content: {
|
|
29775
29856
|
"application/json": {
|
|
29776
29857
|
schema: {
|
|
29777
29858
|
type: "object",
|
|
29778
29859
|
properties: {
|
|
29779
|
-
|
|
29780
|
-
type: "
|
|
29781
|
-
|
|
29860
|
+
data: {
|
|
29861
|
+
type: "array",
|
|
29862
|
+
items: {
|
|
29863
|
+
$ref: "#/components/schemas/PendingSandboxConnectLink"
|
|
29864
|
+
}
|
|
29782
29865
|
},
|
|
29783
|
-
|
|
29784
|
-
type: "
|
|
29866
|
+
links: {
|
|
29867
|
+
type: "object"
|
|
29868
|
+
},
|
|
29869
|
+
meta: {
|
|
29870
|
+
type: "object"
|
|
29785
29871
|
}
|
|
29786
|
-
}
|
|
29787
|
-
required: [
|
|
29788
|
-
"error",
|
|
29789
|
-
"message"
|
|
29790
|
-
]
|
|
29872
|
+
}
|
|
29791
29873
|
}
|
|
29792
29874
|
}
|
|
29793
29875
|
}
|
|
29794
29876
|
}
|
|
29795
|
-
}
|
|
29796
|
-
|
|
29797
|
-
|
|
29798
|
-
"/api/v1/groups/{group}/admins/remove": {
|
|
29877
|
+
},
|
|
29878
|
+
"x-fc-validation-error": "invalid_request"
|
|
29879
|
+
},
|
|
29799
29880
|
post: {
|
|
29800
|
-
operationId: "
|
|
29801
|
-
summary: "
|
|
29802
|
-
description:
|
|
29881
|
+
operationId: "pendingSandboxConnectLinks.store",
|
|
29882
|
+
summary: "Create a sign-up link",
|
|
29883
|
+
description: 'A link of the account that creates the number when your customer completes the page (`url`), or reconnects the number of `device_id` without taking a new place. The quota is checked now (except for a reconnection) and again at the end. Up to 20 open links per account. With `type: meta` while the Meta sign-up is off, `503 meta_connect_disabled`.\n\n**Pending of the spec**: from the contract of the sign-up by API (22-10, A1), while accounts writes it; its OpenAPI will be the final source.\n\nAmendment v0.1 (23-10): as accounts did it in `feat/alta-de-numeros-api` `52d49b319`. Body rules: `allowed_connection_types` required with `meta` and prohibited with `qr` (one or two distinct values); `provision_us_number` prohibited with `qr`; `same_number_as` prohibited with `meta` and excluding `device_id`; `return_url` `https`, up to 2048. Up to 10 creations per minute: one more answers Laravel\'s `429 {"message": "Too Many Attempts."}`. The answer carries `Cache-Control: no-store`.\n\nAmendment v0.2 (23-10, accounts `8852b3fad`): `provision_us_number: true` needs `dedicated` among `allowed_connection_types` and does not go with `device_id` (a pool number is always dedicated and new); otherwise `422 invalid_request`. A Meta link survives failures of Facebook or Meta: it stays `pending` so your customer retries, and ends `failed` (`facebook_auth_failed` or `meta_error`) at the fifth; `quota_exceeded`, `trial_number_blocked` and `number_not_owned` end it at the first.\n\nIn production (25-10): the Meta sign-up stays off until the domain is registered with Meta, so `type: meta` answers `503 meta_connect_disabled`; the sandbox starts with it off too. Optional `country` (two letters, `qr` only and not with `device_id`): the country of the phone, which the link returns in lower case.',
|
|
29803
29884
|
tags: [
|
|
29804
|
-
"
|
|
29885
|
+
"ConnectLink"
|
|
29805
29886
|
],
|
|
29806
|
-
"x-fc-domain": "
|
|
29807
|
-
"x-fc-permission": "
|
|
29808
|
-
"x-fc-pending": "
|
|
29887
|
+
"x-fc-domain": "accounts",
|
|
29888
|
+
"x-fc-permission": "accounts:operate",
|
|
29889
|
+
"x-fc-pending": "accounts-alta-de-numeros",
|
|
29809
29890
|
parameters: [
|
|
29810
29891
|
{
|
|
29811
|
-
name: "
|
|
29812
|
-
in: "
|
|
29813
|
-
required:
|
|
29892
|
+
name: "Idempotency-Key",
|
|
29893
|
+
in: "header",
|
|
29894
|
+
required: false,
|
|
29895
|
+
description: "Up to 255 characters, kept 24 hours. The same key with the same body gives back the same answer without creating anything; with another body, `422 idempotency_key_mismatch`; while the first call runs, `409 operation_in_progress`.",
|
|
29814
29896
|
schema: {
|
|
29815
|
-
type: "string"
|
|
29897
|
+
type: "string",
|
|
29898
|
+
maxLength: 255
|
|
29816
29899
|
}
|
|
29817
29900
|
}
|
|
29818
29901
|
],
|
|
29819
|
-
|
|
29820
|
-
|
|
29821
|
-
|
|
29822
|
-
|
|
29823
|
-
|
|
29824
|
-
|
|
29825
|
-
|
|
29826
|
-
|
|
29827
|
-
|
|
29902
|
+
requestBody: {
|
|
29903
|
+
required: true,
|
|
29904
|
+
content: {
|
|
29905
|
+
"application/json": {
|
|
29906
|
+
schema: {
|
|
29907
|
+
type: "object",
|
|
29908
|
+
properties: {
|
|
29909
|
+
type: {
|
|
29910
|
+
type: "string",
|
|
29911
|
+
enum: [
|
|
29912
|
+
"meta",
|
|
29913
|
+
"qr"
|
|
29914
|
+
]
|
|
29915
|
+
},
|
|
29916
|
+
allowed_connection_types: {
|
|
29917
|
+
type: [
|
|
29918
|
+
"array",
|
|
29919
|
+
"null"
|
|
29920
|
+
],
|
|
29921
|
+
items: {
|
|
29828
29922
|
type: "string",
|
|
29829
|
-
|
|
29923
|
+
enum: [
|
|
29924
|
+
"coexistence",
|
|
29925
|
+
"dedicated"
|
|
29926
|
+
]
|
|
29830
29927
|
},
|
|
29831
|
-
|
|
29832
|
-
|
|
29833
|
-
|
|
29928
|
+
minItems: 1,
|
|
29929
|
+
maxItems: 2,
|
|
29930
|
+
uniqueItems: true,
|
|
29931
|
+
description: "Required with `type: meta`, forbidden with `qr`. With both, your customer chooses."
|
|
29834
29932
|
},
|
|
29835
|
-
|
|
29836
|
-
"
|
|
29837
|
-
"
|
|
29838
|
-
|
|
29839
|
-
|
|
29933
|
+
provision_us_number: {
|
|
29934
|
+
type: "boolean",
|
|
29935
|
+
description: "`meta` only (prohibited with `qr`): offer a US number of the pool (3 per account, 1 on trial accounts). Needs `dedicated` among `allowed_connection_types` and does not go with `device_id`."
|
|
29936
|
+
},
|
|
29937
|
+
device_id: {
|
|
29938
|
+
type: [
|
|
29939
|
+
"integer",
|
|
29940
|
+
"null"
|
|
29941
|
+
],
|
|
29942
|
+
description: "Reconnect this number. `type` must match its method."
|
|
29943
|
+
},
|
|
29944
|
+
same_number_as: {
|
|
29945
|
+
type: [
|
|
29946
|
+
"integer",
|
|
29947
|
+
"null"
|
|
29948
|
+
],
|
|
29949
|
+
description: "`qr` only (prohibited with `meta`), and not with `device_id`: add QR to a number in coexistence."
|
|
29950
|
+
},
|
|
29951
|
+
country: {
|
|
29952
|
+
type: [
|
|
29953
|
+
"string",
|
|
29954
|
+
"null"
|
|
29955
|
+
],
|
|
29956
|
+
pattern: "^[A-Za-z]{2}$",
|
|
29957
|
+
description: "`qr` only (prohibited with `meta`), and not with `device_id`: the country of the phone (ISO 3166-1 alpha-2, two letters, any case), where the WhatsApp session is opened from. The link returns it in lower case."
|
|
29958
|
+
},
|
|
29959
|
+
external_reference: {
|
|
29960
|
+
type: [
|
|
29961
|
+
"string",
|
|
29962
|
+
"null"
|
|
29963
|
+
],
|
|
29964
|
+
maxLength: 255
|
|
29965
|
+
},
|
|
29966
|
+
return_url: {
|
|
29967
|
+
type: [
|
|
29968
|
+
"string",
|
|
29969
|
+
"null"
|
|
29970
|
+
],
|
|
29971
|
+
format: "uri",
|
|
29972
|
+
maxLength: 2048,
|
|
29973
|
+
description: "`https` only. Gets `connect_link_id`, `status`, `device_id` and `error_code`, nothing else."
|
|
29974
|
+
},
|
|
29975
|
+
expires_in_minutes: {
|
|
29976
|
+
type: "integer",
|
|
29977
|
+
minimum: 30,
|
|
29978
|
+
maximum: 10080,
|
|
29979
|
+
description: "1440 by default."
|
|
29980
|
+
}
|
|
29981
|
+
},
|
|
29982
|
+
required: [
|
|
29983
|
+
"type"
|
|
29984
|
+
]
|
|
29840
29985
|
}
|
|
29841
29986
|
}
|
|
29842
29987
|
}
|
|
29843
|
-
}
|
|
29844
|
-
}
|
|
29845
|
-
},
|
|
29846
|
-
"/api/v1/groups/{group}/name": {
|
|
29847
|
-
put: {
|
|
29848
|
-
operationId: "pendingSandboxGroupActions.groups.group.name.put",
|
|
29849
|
-
summary: "Group action by API (off)",
|
|
29850
|
-
description: "It exists in communities but is off in production: any key gets `403 route_not_available`.\n\n**Pending of the spec**: it is in production, missing from the portal's snapshot.",
|
|
29851
|
-
tags: [
|
|
29852
|
-
"GroupActions"
|
|
29853
|
-
],
|
|
29854
|
-
"x-fc-domain": "communities",
|
|
29855
|
-
"x-fc-permission": "communities:operate",
|
|
29856
|
-
"x-fc-pending": "communities-group-actions",
|
|
29857
|
-
parameters: [
|
|
29858
|
-
{
|
|
29859
|
-
name: "group",
|
|
29860
|
-
in: "path",
|
|
29861
|
-
required: true,
|
|
29862
|
-
schema: {
|
|
29863
|
-
type: "string"
|
|
29864
|
-
}
|
|
29865
|
-
}
|
|
29866
|
-
],
|
|
29988
|
+
},
|
|
29867
29989
|
responses: {
|
|
29868
|
-
"
|
|
29869
|
-
description: "
|
|
29990
|
+
"201": {
|
|
29991
|
+
description: "Created",
|
|
29870
29992
|
content: {
|
|
29871
29993
|
"application/json": {
|
|
29872
29994
|
schema: {
|
|
29873
29995
|
type: "object",
|
|
29874
29996
|
properties: {
|
|
29875
|
-
|
|
29876
|
-
|
|
29877
|
-
const: "route_not_available"
|
|
29878
|
-
},
|
|
29879
|
-
message: {
|
|
29880
|
-
type: "string"
|
|
29997
|
+
data: {
|
|
29998
|
+
$ref: "#/components/schemas/PendingSandboxConnectLinkCreated"
|
|
29881
29999
|
}
|
|
29882
30000
|
},
|
|
29883
30001
|
required: [
|
|
29884
|
-
"
|
|
29885
|
-
"message"
|
|
30002
|
+
"data"
|
|
29886
30003
|
]
|
|
29887
30004
|
}
|
|
29888
30005
|
}
|
|
30006
|
+
},
|
|
30007
|
+
headers: {
|
|
30008
|
+
"Cache-Control": {
|
|
30009
|
+
schema: {
|
|
30010
|
+
type: "string",
|
|
30011
|
+
const: "no-store"
|
|
30012
|
+
}
|
|
30013
|
+
},
|
|
30014
|
+
"Idempotent-Replayed": {
|
|
30015
|
+
description: "`true` when the answer is a replay of the same `Idempotency-Key`.",
|
|
30016
|
+
schema: {
|
|
30017
|
+
type: "string"
|
|
30018
|
+
}
|
|
30019
|
+
}
|
|
29889
30020
|
}
|
|
29890
|
-
}
|
|
29891
|
-
}
|
|
29892
|
-
}
|
|
29893
|
-
},
|
|
29894
|
-
"/api/v1/groups/{group}/description": {
|
|
29895
|
-
put: {
|
|
29896
|
-
operationId: "pendingSandboxGroupActions.groups.group.description.put",
|
|
29897
|
-
summary: "Group action by API (off)",
|
|
29898
|
-
description: "It exists in communities but is off in production: any key gets `403 route_not_available`.\n\n**Pending of the spec**: it is in production, missing from the portal's snapshot.",
|
|
29899
|
-
tags: [
|
|
29900
|
-
"GroupActions"
|
|
29901
|
-
],
|
|
29902
|
-
"x-fc-domain": "communities",
|
|
29903
|
-
"x-fc-permission": "communities:operate",
|
|
29904
|
-
"x-fc-pending": "communities-group-actions",
|
|
29905
|
-
parameters: [
|
|
29906
|
-
{
|
|
29907
|
-
name: "group",
|
|
29908
|
-
in: "path",
|
|
29909
|
-
required: true,
|
|
29910
|
-
schema: {
|
|
29911
|
-
type: "string"
|
|
29912
|
-
}
|
|
29913
|
-
}
|
|
29914
|
-
],
|
|
29915
|
-
responses: {
|
|
30021
|
+
},
|
|
29916
30022
|
"403": {
|
|
29917
|
-
description: "
|
|
30023
|
+
description: "`numbers_not_included` or `number_limit_reached`",
|
|
29918
30024
|
content: {
|
|
29919
30025
|
"application/json": {
|
|
29920
30026
|
schema: {
|
|
29921
|
-
|
|
29922
|
-
properties: {
|
|
29923
|
-
error: {
|
|
29924
|
-
type: "string",
|
|
29925
|
-
const: "route_not_available"
|
|
29926
|
-
},
|
|
29927
|
-
message: {
|
|
29928
|
-
type: "string"
|
|
29929
|
-
}
|
|
29930
|
-
},
|
|
29931
|
-
required: [
|
|
29932
|
-
"error",
|
|
29933
|
-
"message"
|
|
29934
|
-
]
|
|
30027
|
+
$ref: "#/components/schemas/PendingSandboxAccountsError"
|
|
29935
30028
|
}
|
|
29936
30029
|
}
|
|
29937
30030
|
}
|
|
29938
|
-
}
|
|
29939
|
-
|
|
29940
|
-
|
|
29941
|
-
},
|
|
29942
|
-
"/api/v1/groups/{group}/photo": {
|
|
29943
|
-
put: {
|
|
29944
|
-
operationId: "pendingSandboxGroupActions.groups.group.photo.put",
|
|
29945
|
-
summary: "Group action by API (off)",
|
|
29946
|
-
description: "It exists in communities but is off in production: any key gets `403 route_not_available`.\n\n**Pending of the spec**: it is in production, missing from the portal's snapshot.",
|
|
29947
|
-
tags: [
|
|
29948
|
-
"GroupActions"
|
|
29949
|
-
],
|
|
29950
|
-
"x-fc-domain": "communities",
|
|
29951
|
-
"x-fc-permission": "communities:operate",
|
|
29952
|
-
"x-fc-pending": "communities-group-actions",
|
|
29953
|
-
parameters: [
|
|
29954
|
-
{
|
|
29955
|
-
name: "group",
|
|
29956
|
-
in: "path",
|
|
29957
|
-
required: true,
|
|
29958
|
-
schema: {
|
|
29959
|
-
type: "string"
|
|
29960
|
-
}
|
|
29961
|
-
}
|
|
29962
|
-
],
|
|
29963
|
-
responses: {
|
|
29964
|
-
"403": {
|
|
29965
|
-
description: "route_not_available: the group actions by API are off in production.",
|
|
30031
|
+
},
|
|
30032
|
+
"409": {
|
|
30033
|
+
description: "`operation_in_progress`",
|
|
29966
30034
|
content: {
|
|
29967
30035
|
"application/json": {
|
|
29968
30036
|
schema: {
|
|
29969
|
-
|
|
29970
|
-
properties: {
|
|
29971
|
-
error: {
|
|
29972
|
-
type: "string",
|
|
29973
|
-
const: "route_not_available"
|
|
29974
|
-
},
|
|
29975
|
-
message: {
|
|
29976
|
-
type: "string"
|
|
29977
|
-
}
|
|
29978
|
-
},
|
|
29979
|
-
required: [
|
|
29980
|
-
"error",
|
|
29981
|
-
"message"
|
|
29982
|
-
]
|
|
30037
|
+
$ref: "#/components/schemas/PendingSandboxAccountsError"
|
|
29983
30038
|
}
|
|
29984
30039
|
}
|
|
29985
30040
|
}
|
|
29986
|
-
}
|
|
29987
|
-
|
|
29988
|
-
|
|
29989
|
-
|
|
29990
|
-
|
|
29991
|
-
|
|
29992
|
-
|
|
29993
|
-
|
|
29994
|
-
|
|
29995
|
-
tags: [
|
|
29996
|
-
"GroupActions"
|
|
29997
|
-
],
|
|
29998
|
-
"x-fc-domain": "communities",
|
|
29999
|
-
"x-fc-permission": "communities:operate",
|
|
30000
|
-
"x-fc-pending": "communities-group-actions",
|
|
30001
|
-
parameters: [
|
|
30002
|
-
{
|
|
30003
|
-
name: "group",
|
|
30004
|
-
in: "path",
|
|
30005
|
-
required: true,
|
|
30006
|
-
schema: {
|
|
30007
|
-
type: "string"
|
|
30041
|
+
},
|
|
30042
|
+
"422": {
|
|
30043
|
+
description: "`invalid_request` (with `errors`), `invalid_reference_device`, `connection_type_mismatch`, `qr_requires_coexistence`, `method_already_present`, `idempotency_key_mismatch` or `invalid_idempotency_key`",
|
|
30044
|
+
content: {
|
|
30045
|
+
"application/json": {
|
|
30046
|
+
schema: {
|
|
30047
|
+
$ref: "#/components/schemas/PendingSandboxAccountsError"
|
|
30048
|
+
}
|
|
30049
|
+
}
|
|
30008
30050
|
}
|
|
30009
|
-
}
|
|
30010
|
-
|
|
30011
|
-
|
|
30012
|
-
"403": {
|
|
30013
|
-
description: "route_not_available: the group actions by API are off in production.",
|
|
30051
|
+
},
|
|
30052
|
+
"429": {
|
|
30053
|
+
description: '`too_many_open_links`, or the per-minute limit (`{"message": "Too Many Attempts."}`)',
|
|
30014
30054
|
content: {
|
|
30015
30055
|
"application/json": {
|
|
30016
30056
|
schema: {
|
|
30017
|
-
|
|
30018
|
-
|
|
30019
|
-
|
|
30020
|
-
type: "string",
|
|
30021
|
-
const: "route_not_available"
|
|
30057
|
+
oneOf: [
|
|
30058
|
+
{
|
|
30059
|
+
$ref: "#/components/schemas/PendingSandboxAccountsError"
|
|
30022
30060
|
},
|
|
30023
|
-
|
|
30024
|
-
|
|
30061
|
+
{
|
|
30062
|
+
$ref: "#/components/schemas/PendingSandboxTooManyAttempts"
|
|
30025
30063
|
}
|
|
30026
|
-
},
|
|
30027
|
-
required: [
|
|
30028
|
-
"error",
|
|
30029
|
-
"message"
|
|
30030
30064
|
]
|
|
30031
30065
|
}
|
|
30032
30066
|
}
|
|
30033
30067
|
}
|
|
30034
|
-
}
|
|
30035
|
-
|
|
30036
|
-
|
|
30037
|
-
},
|
|
30038
|
-
"/api/v1/groups/{group}/invite-link/renew": {
|
|
30039
|
-
post: {
|
|
30040
|
-
operationId: "pendingSandboxGroupActions.groups.group.invite-link.renew.post",
|
|
30041
|
-
summary: "Group action by API (off)",
|
|
30042
|
-
description: "It exists in communities but is off in production: any key gets `403 route_not_available`.\n\n**Pending of the spec**: it is in production, missing from the portal's snapshot.",
|
|
30043
|
-
tags: [
|
|
30044
|
-
"GroupActions"
|
|
30045
|
-
],
|
|
30046
|
-
"x-fc-domain": "communities",
|
|
30047
|
-
"x-fc-permission": "communities:operate",
|
|
30048
|
-
"x-fc-pending": "communities-group-actions",
|
|
30049
|
-
parameters: [
|
|
30050
|
-
{
|
|
30051
|
-
name: "group",
|
|
30052
|
-
in: "path",
|
|
30053
|
-
required: true,
|
|
30054
|
-
schema: {
|
|
30055
|
-
type: "string"
|
|
30056
|
-
}
|
|
30057
|
-
}
|
|
30058
|
-
],
|
|
30059
|
-
responses: {
|
|
30060
|
-
"403": {
|
|
30061
|
-
description: "route_not_available: the group actions by API are off in production.",
|
|
30068
|
+
},
|
|
30069
|
+
"503": {
|
|
30070
|
+
description: "`meta_connect_disabled`",
|
|
30062
30071
|
content: {
|
|
30063
30072
|
"application/json": {
|
|
30064
30073
|
schema: {
|
|
30065
|
-
|
|
30066
|
-
properties: {
|
|
30067
|
-
error: {
|
|
30068
|
-
type: "string",
|
|
30069
|
-
const: "route_not_available"
|
|
30070
|
-
},
|
|
30071
|
-
message: {
|
|
30072
|
-
type: "string"
|
|
30073
|
-
}
|
|
30074
|
-
},
|
|
30075
|
-
required: [
|
|
30076
|
-
"error",
|
|
30077
|
-
"message"
|
|
30078
|
-
]
|
|
30074
|
+
$ref: "#/components/schemas/PendingSandboxAccountsError"
|
|
30079
30075
|
}
|
|
30080
30076
|
}
|
|
30081
30077
|
}
|
|
30082
30078
|
}
|
|
30083
|
-
}
|
|
30079
|
+
},
|
|
30080
|
+
"x-fc-validation-error": "invalid_request"
|
|
30084
30081
|
}
|
|
30085
30082
|
},
|
|
30086
|
-
"/api/v1/
|
|
30083
|
+
"/api/v1/connect-links/{link}": {
|
|
30087
30084
|
get: {
|
|
30088
|
-
operationId: "
|
|
30089
|
-
summary: "
|
|
30090
|
-
description: "
|
|
30085
|
+
operationId: "pendingSandboxConnectLinks.show",
|
|
30086
|
+
summary: "Show a sign-up link",
|
|
30087
|
+
description: "The link without its token: its `status`, the `device_id` it created and its `error_code`.\n\n**Pending of the spec**: from the contract of the sign-up by API (22-10, A1), while accounts writes it; its OpenAPI will be the final source.",
|
|
30091
30088
|
tags: [
|
|
30092
|
-
"
|
|
30089
|
+
"ConnectLink"
|
|
30093
30090
|
],
|
|
30094
|
-
"x-fc-domain": "
|
|
30095
|
-
"x-fc-permission": "
|
|
30096
|
-
"x-fc-pending": "
|
|
30091
|
+
"x-fc-domain": "accounts",
|
|
30092
|
+
"x-fc-permission": "accounts:read",
|
|
30093
|
+
"x-fc-pending": "accounts-alta-de-numeros",
|
|
30097
30094
|
parameters: [
|
|
30098
30095
|
{
|
|
30099
|
-
name: "
|
|
30096
|
+
name: "link",
|
|
30100
30097
|
in: "path",
|
|
30101
30098
|
required: true,
|
|
30102
30099
|
schema: {
|
|
30103
|
-
type: "
|
|
30100
|
+
type: "integer"
|
|
30104
30101
|
}
|
|
30105
30102
|
}
|
|
30106
30103
|
],
|
|
30107
30104
|
responses: {
|
|
30108
|
-
"
|
|
30109
|
-
description: "
|
|
30105
|
+
"200": {
|
|
30106
|
+
description: "OK",
|
|
30110
30107
|
content: {
|
|
30111
30108
|
"application/json": {
|
|
30112
30109
|
schema: {
|
|
30113
30110
|
type: "object",
|
|
30114
30111
|
properties: {
|
|
30115
|
-
|
|
30116
|
-
|
|
30117
|
-
const: "route_not_available"
|
|
30118
|
-
},
|
|
30119
|
-
message: {
|
|
30120
|
-
type: "string"
|
|
30112
|
+
data: {
|
|
30113
|
+
$ref: "#/components/schemas/PendingSandboxConnectLink"
|
|
30121
30114
|
}
|
|
30122
30115
|
},
|
|
30123
30116
|
required: [
|
|
30124
|
-
"
|
|
30125
|
-
"message"
|
|
30117
|
+
"data"
|
|
30126
30118
|
]
|
|
30127
30119
|
}
|
|
30128
30120
|
}
|
|
30129
30121
|
}
|
|
30130
|
-
}
|
|
30131
|
-
|
|
30132
|
-
|
|
30133
|
-
},
|
|
30134
|
-
"/api/v1/groups/{group}/join-requests": {
|
|
30135
|
-
get: {
|
|
30136
|
-
operationId: "pendingSandboxGroupActions.groups.group.join-requests.get",
|
|
30137
|
-
summary: "Group action by API (off)",
|
|
30138
|
-
description: "It exists in communities but is off in production: any key gets `403 route_not_available`.\n\n**Pending of the spec**: it is in production, missing from the portal's snapshot.",
|
|
30139
|
-
tags: [
|
|
30140
|
-
"GroupActions"
|
|
30141
|
-
],
|
|
30142
|
-
"x-fc-domain": "communities",
|
|
30143
|
-
"x-fc-permission": "communities:operate",
|
|
30144
|
-
"x-fc-pending": "communities-group-actions",
|
|
30145
|
-
parameters: [
|
|
30146
|
-
{
|
|
30147
|
-
name: "group",
|
|
30148
|
-
in: "path",
|
|
30149
|
-
required: true,
|
|
30150
|
-
schema: {
|
|
30151
|
-
type: "string"
|
|
30152
|
-
}
|
|
30153
|
-
}
|
|
30154
|
-
],
|
|
30155
|
-
responses: {
|
|
30156
|
-
"403": {
|
|
30157
|
-
description: "route_not_available: the group actions by API are off in production.",
|
|
30122
|
+
},
|
|
30123
|
+
"404": {
|
|
30124
|
+
description: "`not_found`",
|
|
30158
30125
|
content: {
|
|
30159
30126
|
"application/json": {
|
|
30160
30127
|
schema: {
|
|
30161
|
-
|
|
30162
|
-
properties: {
|
|
30163
|
-
error: {
|
|
30164
|
-
type: "string",
|
|
30165
|
-
const: "route_not_available"
|
|
30166
|
-
},
|
|
30167
|
-
message: {
|
|
30168
|
-
type: "string"
|
|
30169
|
-
}
|
|
30170
|
-
},
|
|
30171
|
-
required: [
|
|
30172
|
-
"error",
|
|
30173
|
-
"message"
|
|
30174
|
-
]
|
|
30128
|
+
$ref: "#/components/schemas/PendingSandboxAccountsError"
|
|
30175
30129
|
}
|
|
30176
30130
|
}
|
|
30177
30131
|
}
|
|
30178
30132
|
}
|
|
30179
30133
|
}
|
|
30180
|
-
}
|
|
30181
|
-
|
|
30182
|
-
|
|
30183
|
-
|
|
30184
|
-
|
|
30185
|
-
summary: "Group action by API (off)",
|
|
30186
|
-
description: "It exists in communities but is off in production: any key gets `403 route_not_available`.\n\n**Pending of the spec**: it is in production, missing from the portal's snapshot.",
|
|
30134
|
+
},
|
|
30135
|
+
delete: {
|
|
30136
|
+
operationId: "pendingSandboxConnectLinks.destroy",
|
|
30137
|
+
summary: "Revoke a sign-up link",
|
|
30138
|
+
description: "Revokes a `pending` link: `204` without a body. `409 link_not_pending` if it is not pending any more; `404` if it does not exist.\n\nAmendment v0.1 (23-10): as accounts did it in `feat/alta-de-numeros-api` `52d49b319`.",
|
|
30187
30139
|
tags: [
|
|
30188
|
-
"
|
|
30140
|
+
"ConnectLink"
|
|
30189
30141
|
],
|
|
30190
|
-
"x-fc-domain": "
|
|
30191
|
-
"x-fc-permission": "
|
|
30192
|
-
"x-fc-pending": "
|
|
30142
|
+
"x-fc-domain": "accounts",
|
|
30143
|
+
"x-fc-permission": "accounts:operate",
|
|
30144
|
+
"x-fc-pending": "accounts-alta-de-numeros",
|
|
30193
30145
|
parameters: [
|
|
30194
30146
|
{
|
|
30195
|
-
name: "
|
|
30147
|
+
name: "link",
|
|
30196
30148
|
in: "path",
|
|
30197
30149
|
required: true,
|
|
30198
30150
|
schema: {
|
|
30199
|
-
type: "
|
|
30151
|
+
type: "integer"
|
|
30200
30152
|
}
|
|
30201
30153
|
}
|
|
30202
30154
|
],
|
|
30203
30155
|
responses: {
|
|
30204
|
-
"
|
|
30205
|
-
description: "
|
|
30156
|
+
"204": {
|
|
30157
|
+
description: "Revoked"
|
|
30158
|
+
},
|
|
30159
|
+
"404": {
|
|
30160
|
+
description: "`not_found`",
|
|
30206
30161
|
content: {
|
|
30207
30162
|
"application/json": {
|
|
30208
30163
|
schema: {
|
|
30209
|
-
|
|
30210
|
-
properties: {
|
|
30211
|
-
error: {
|
|
30212
|
-
type: "string",
|
|
30213
|
-
const: "route_not_available"
|
|
30214
|
-
},
|
|
30215
|
-
message: {
|
|
30216
|
-
type: "string"
|
|
30217
|
-
}
|
|
30218
|
-
},
|
|
30219
|
-
required: [
|
|
30220
|
-
"error",
|
|
30221
|
-
"message"
|
|
30222
|
-
]
|
|
30164
|
+
$ref: "#/components/schemas/PendingSandboxAccountsError"
|
|
30223
30165
|
}
|
|
30224
30166
|
}
|
|
30225
30167
|
}
|
|
30226
|
-
}
|
|
30227
|
-
|
|
30228
|
-
|
|
30229
|
-
},
|
|
30230
|
-
"/api/v1/groups/{group}/join-requests/reject": {
|
|
30231
|
-
post: {
|
|
30232
|
-
operationId: "pendingSandboxGroupActions.groups.group.join-requests.reject.post",
|
|
30233
|
-
summary: "Group action by API (off)",
|
|
30234
|
-
description: "It exists in communities but is off in production: any key gets `403 route_not_available`.\n\n**Pending of the spec**: it is in production, missing from the portal's snapshot.",
|
|
30235
|
-
tags: [
|
|
30236
|
-
"GroupActions"
|
|
30237
|
-
],
|
|
30238
|
-
"x-fc-domain": "communities",
|
|
30239
|
-
"x-fc-permission": "communities:operate",
|
|
30240
|
-
"x-fc-pending": "communities-group-actions",
|
|
30241
|
-
parameters: [
|
|
30242
|
-
{
|
|
30243
|
-
name: "group",
|
|
30244
|
-
in: "path",
|
|
30245
|
-
required: true,
|
|
30246
|
-
schema: {
|
|
30247
|
-
type: "string"
|
|
30248
|
-
}
|
|
30249
|
-
}
|
|
30250
|
-
],
|
|
30251
|
-
responses: {
|
|
30252
|
-
"403": {
|
|
30253
|
-
description: "route_not_available: the group actions by API are off in production.",
|
|
30168
|
+
},
|
|
30169
|
+
"409": {
|
|
30170
|
+
description: "`link_not_pending`",
|
|
30254
30171
|
content: {
|
|
30255
30172
|
"application/json": {
|
|
30256
30173
|
schema: {
|
|
30257
|
-
|
|
30258
|
-
properties: {
|
|
30259
|
-
error: {
|
|
30260
|
-
type: "string",
|
|
30261
|
-
const: "route_not_available"
|
|
30262
|
-
},
|
|
30263
|
-
message: {
|
|
30264
|
-
type: "string"
|
|
30265
|
-
}
|
|
30266
|
-
},
|
|
30267
|
-
required: [
|
|
30268
|
-
"error",
|
|
30269
|
-
"message"
|
|
30270
|
-
]
|
|
30174
|
+
$ref: "#/components/schemas/PendingSandboxAccountsError"
|
|
30271
30175
|
}
|
|
30272
30176
|
}
|
|
30273
30177
|
}
|
|
@@ -32471,18 +32375,43 @@ var openapi_default = {
|
|
|
32471
32375
|
"string",
|
|
32472
32376
|
"null"
|
|
32473
32377
|
],
|
|
32474
|
-
description: "
|
|
32378
|
+
description: "Digits only, without `+`."
|
|
32379
|
+
},
|
|
32380
|
+
connection: {
|
|
32381
|
+
type: "string",
|
|
32382
|
+
enum: [
|
|
32383
|
+
"qr",
|
|
32384
|
+
"coexistence",
|
|
32385
|
+
"dedicated"
|
|
32386
|
+
]
|
|
32387
|
+
},
|
|
32388
|
+
external_reference: {
|
|
32389
|
+
type: [
|
|
32390
|
+
"string",
|
|
32391
|
+
"null"
|
|
32392
|
+
]
|
|
32475
32393
|
}
|
|
32476
32394
|
},
|
|
32477
32395
|
required: [
|
|
32478
32396
|
"id",
|
|
32479
32397
|
"name",
|
|
32480
|
-
"number"
|
|
32481
|
-
|
|
32398
|
+
"number",
|
|
32399
|
+
"connection",
|
|
32400
|
+
"external_reference"
|
|
32401
|
+
],
|
|
32402
|
+
"x-fc-pending": "accounts-alta-de-numeros"
|
|
32482
32403
|
},
|
|
32483
32404
|
connected_at: {
|
|
32484
32405
|
type: "string",
|
|
32485
32406
|
format: "date-time"
|
|
32407
|
+
},
|
|
32408
|
+
connect_link_id: {
|
|
32409
|
+
type: [
|
|
32410
|
+
"integer",
|
|
32411
|
+
"null"
|
|
32412
|
+
],
|
|
32413
|
+
description: "The sign-up link of this connection, or `null`. Optional until accounts deploys it.",
|
|
32414
|
+
"x-fc-pending": "accounts-alta-de-numeros"
|
|
32486
32415
|
}
|
|
32487
32416
|
},
|
|
32488
32417
|
required: [
|
|
@@ -32491,6 +32420,23 @@ var openapi_default = {
|
|
|
32491
32420
|
]
|
|
32492
32421
|
}
|
|
32493
32422
|
}
|
|
32423
|
+
},
|
|
32424
|
+
example: {
|
|
32425
|
+
id: "evt_0f8e4b526c1d4c7a9a3e2b5d",
|
|
32426
|
+
type: "device.connected",
|
|
32427
|
+
version: 1,
|
|
32428
|
+
occurred_at: "2026-10-22T03:00:00Z",
|
|
32429
|
+
data: {
|
|
32430
|
+
device: {
|
|
32431
|
+
id: 4521,
|
|
32432
|
+
name: "Ventas",
|
|
32433
|
+
number: "5491100000000",
|
|
32434
|
+
connection: "qr",
|
|
32435
|
+
external_reference: "cliente-8841"
|
|
32436
|
+
},
|
|
32437
|
+
connected_at: "2026-10-22T03:00:00+00:00",
|
|
32438
|
+
connect_link_id: 812
|
|
32439
|
+
}
|
|
32494
32440
|
}
|
|
32495
32441
|
}
|
|
32496
32442
|
}
|
|
@@ -32530,18 +32476,56 @@ var openapi_default = {
|
|
|
32530
32476
|
"string",
|
|
32531
32477
|
"null"
|
|
32532
32478
|
],
|
|
32533
|
-
description: "
|
|
32479
|
+
description: "Digits only, without `+`."
|
|
32480
|
+
},
|
|
32481
|
+
connection: {
|
|
32482
|
+
type: "string",
|
|
32483
|
+
enum: [
|
|
32484
|
+
"qr",
|
|
32485
|
+
"coexistence",
|
|
32486
|
+
"dedicated"
|
|
32487
|
+
]
|
|
32488
|
+
},
|
|
32489
|
+
external_reference: {
|
|
32490
|
+
type: [
|
|
32491
|
+
"string",
|
|
32492
|
+
"null"
|
|
32493
|
+
]
|
|
32534
32494
|
}
|
|
32535
32495
|
},
|
|
32536
32496
|
required: [
|
|
32537
32497
|
"id",
|
|
32538
32498
|
"name",
|
|
32539
|
-
"number"
|
|
32540
|
-
|
|
32499
|
+
"number",
|
|
32500
|
+
"connection",
|
|
32501
|
+
"external_reference"
|
|
32502
|
+
],
|
|
32503
|
+
"x-fc-pending": "accounts-alta-de-numeros"
|
|
32541
32504
|
},
|
|
32542
32505
|
disconnected_at: {
|
|
32543
32506
|
type: "string",
|
|
32544
32507
|
format: "date-time"
|
|
32508
|
+
},
|
|
32509
|
+
reason: {
|
|
32510
|
+
type: "string",
|
|
32511
|
+
enum: [
|
|
32512
|
+
"qr_logged_out",
|
|
32513
|
+
"meta_account_not_registered",
|
|
32514
|
+
"shadow_ban_protection",
|
|
32515
|
+
"provider_error",
|
|
32516
|
+
"unknown",
|
|
32517
|
+
"disconnected_by_user"
|
|
32518
|
+
],
|
|
32519
|
+
description: "`disconnected_by_user` [part 2]: \xABDesconectar\xBB or \xABBorrar datos\xBB of an official number in the panel.",
|
|
32520
|
+
"x-fc-pending": "accounts-alta-de-numeros"
|
|
32521
|
+
},
|
|
32522
|
+
connect_link_id: {
|
|
32523
|
+
type: [
|
|
32524
|
+
"integer",
|
|
32525
|
+
"null"
|
|
32526
|
+
],
|
|
32527
|
+
description: "The sign-up link that connected this number last, or `null`. Optional until accounts deploys it.",
|
|
32528
|
+
"x-fc-pending": "accounts-alta-de-numeros"
|
|
32545
32529
|
}
|
|
32546
32530
|
},
|
|
32547
32531
|
required: [
|
|
@@ -32550,6 +32534,24 @@ var openapi_default = {
|
|
|
32550
32534
|
]
|
|
32551
32535
|
}
|
|
32552
32536
|
}
|
|
32537
|
+
},
|
|
32538
|
+
example: {
|
|
32539
|
+
id: "evt_0f8e4b526c1d4c7a9a3e2b5d",
|
|
32540
|
+
type: "device.disconnected",
|
|
32541
|
+
version: 1,
|
|
32542
|
+
occurred_at: "2026-10-22T03:00:00Z",
|
|
32543
|
+
data: {
|
|
32544
|
+
device: {
|
|
32545
|
+
id: 4521,
|
|
32546
|
+
name: "Ventas",
|
|
32547
|
+
number: "5491100000000",
|
|
32548
|
+
connection: "dedicated",
|
|
32549
|
+
external_reference: "cliente-8841"
|
|
32550
|
+
},
|
|
32551
|
+
disconnected_at: "2026-10-22T03:00:00+00:00",
|
|
32552
|
+
reason: "meta_account_not_registered",
|
|
32553
|
+
connect_link_id: null
|
|
32554
|
+
}
|
|
32553
32555
|
}
|
|
32554
32556
|
}
|
|
32555
32557
|
}
|
|
@@ -33018,6 +33020,226 @@ var openapi_default = {
|
|
|
33018
33020
|
}
|
|
33019
33021
|
}
|
|
33020
33022
|
}
|
|
33023
|
+
},
|
|
33024
|
+
"device.deleted": {
|
|
33025
|
+
post: {
|
|
33026
|
+
summary: "device.deleted",
|
|
33027
|
+
description: "A number of the account was deleted: its place of the quota is free again. Emitted once per number, before the row goes.\n\n**Pending of the spec**: from the contract of the sign-up by API (22-10, A3), while accounts writes it; its OpenAPI will be the final source.",
|
|
33028
|
+
"x-fc-status": "live",
|
|
33029
|
+
"x-fc-domain": "accounts",
|
|
33030
|
+
"x-fc-pending": "accounts-alta-de-numeros",
|
|
33031
|
+
requestBody: {
|
|
33032
|
+
content: {
|
|
33033
|
+
"application/json": {
|
|
33034
|
+
schema: {
|
|
33035
|
+
type: "object",
|
|
33036
|
+
properties: {
|
|
33037
|
+
data: {
|
|
33038
|
+
type: "object",
|
|
33039
|
+
properties: {
|
|
33040
|
+
device: {
|
|
33041
|
+
type: "object",
|
|
33042
|
+
properties: {
|
|
33043
|
+
id: {
|
|
33044
|
+
type: "integer"
|
|
33045
|
+
},
|
|
33046
|
+
name: {
|
|
33047
|
+
type: [
|
|
33048
|
+
"string",
|
|
33049
|
+
"null"
|
|
33050
|
+
]
|
|
33051
|
+
},
|
|
33052
|
+
number: {
|
|
33053
|
+
type: [
|
|
33054
|
+
"string",
|
|
33055
|
+
"null"
|
|
33056
|
+
],
|
|
33057
|
+
description: "Digits only, without `+`."
|
|
33058
|
+
},
|
|
33059
|
+
connection: {
|
|
33060
|
+
type: "string",
|
|
33061
|
+
enum: [
|
|
33062
|
+
"qr",
|
|
33063
|
+
"coexistence",
|
|
33064
|
+
"dedicated"
|
|
33065
|
+
]
|
|
33066
|
+
},
|
|
33067
|
+
external_reference: {
|
|
33068
|
+
type: [
|
|
33069
|
+
"string",
|
|
33070
|
+
"null"
|
|
33071
|
+
]
|
|
33072
|
+
}
|
|
33073
|
+
},
|
|
33074
|
+
required: [
|
|
33075
|
+
"id",
|
|
33076
|
+
"name",
|
|
33077
|
+
"number",
|
|
33078
|
+
"connection",
|
|
33079
|
+
"external_reference"
|
|
33080
|
+
]
|
|
33081
|
+
},
|
|
33082
|
+
deleted_at: {
|
|
33083
|
+
type: "string",
|
|
33084
|
+
format: "date-time"
|
|
33085
|
+
},
|
|
33086
|
+
connect_link_id: {
|
|
33087
|
+
type: [
|
|
33088
|
+
"integer",
|
|
33089
|
+
"null"
|
|
33090
|
+
],
|
|
33091
|
+
description: "The sign-up link that connected this number last, or `null`. Optional until accounts deploys it."
|
|
33092
|
+
}
|
|
33093
|
+
},
|
|
33094
|
+
required: [
|
|
33095
|
+
"device",
|
|
33096
|
+
"deleted_at"
|
|
33097
|
+
]
|
|
33098
|
+
}
|
|
33099
|
+
}
|
|
33100
|
+
},
|
|
33101
|
+
example: {
|
|
33102
|
+
id: "evt_0f8e4b526c1d4c7a9a3e2b5d",
|
|
33103
|
+
type: "device.deleted",
|
|
33104
|
+
version: 1,
|
|
33105
|
+
occurred_at: "2026-10-22T03:00:00Z",
|
|
33106
|
+
data: {
|
|
33107
|
+
device: {
|
|
33108
|
+
id: 4521,
|
|
33109
|
+
name: "Ventas",
|
|
33110
|
+
number: "5491100000000",
|
|
33111
|
+
connection: "qr",
|
|
33112
|
+
external_reference: "cliente-8841"
|
|
33113
|
+
},
|
|
33114
|
+
deleted_at: "2026-10-22T03:00:00+00:00",
|
|
33115
|
+
connect_link_id: 812
|
|
33116
|
+
}
|
|
33117
|
+
}
|
|
33118
|
+
}
|
|
33119
|
+
}
|
|
33120
|
+
}
|
|
33121
|
+
}
|
|
33122
|
+
},
|
|
33123
|
+
"device.health_changed": {
|
|
33124
|
+
post: {
|
|
33125
|
+
summary: "device.health_changed",
|
|
33126
|
+
description: "The aggregated health of an official number changed (not on every webhook of Meta).\n\n**Pending of the spec**: from the contract of the sign-up by API (22-10, A3), while accounts writes it; its OpenAPI will be the final source. Pending of the user: PR #1103 of accounts (open) defines `state: { previous, current }`; accounts implemented this contract. One of the two has to be chosen before merging.",
|
|
33127
|
+
"x-fc-status": "live",
|
|
33128
|
+
"x-fc-domain": "accounts",
|
|
33129
|
+
"x-fc-pending": "accounts-alta-de-numeros",
|
|
33130
|
+
requestBody: {
|
|
33131
|
+
content: {
|
|
33132
|
+
"application/json": {
|
|
33133
|
+
schema: {
|
|
33134
|
+
type: "object",
|
|
33135
|
+
properties: {
|
|
33136
|
+
data: {
|
|
33137
|
+
type: "object",
|
|
33138
|
+
properties: {
|
|
33139
|
+
device: {
|
|
33140
|
+
type: "object",
|
|
33141
|
+
properties: {
|
|
33142
|
+
id: {
|
|
33143
|
+
type: "integer"
|
|
33144
|
+
},
|
|
33145
|
+
name: {
|
|
33146
|
+
type: [
|
|
33147
|
+
"string",
|
|
33148
|
+
"null"
|
|
33149
|
+
]
|
|
33150
|
+
},
|
|
33151
|
+
number: {
|
|
33152
|
+
type: [
|
|
33153
|
+
"string",
|
|
33154
|
+
"null"
|
|
33155
|
+
],
|
|
33156
|
+
description: "Digits only, without `+`."
|
|
33157
|
+
},
|
|
33158
|
+
connection: {
|
|
33159
|
+
type: "string",
|
|
33160
|
+
enum: [
|
|
33161
|
+
"qr",
|
|
33162
|
+
"coexistence",
|
|
33163
|
+
"dedicated"
|
|
33164
|
+
]
|
|
33165
|
+
},
|
|
33166
|
+
external_reference: {
|
|
33167
|
+
type: [
|
|
33168
|
+
"string",
|
|
33169
|
+
"null"
|
|
33170
|
+
]
|
|
33171
|
+
}
|
|
33172
|
+
},
|
|
33173
|
+
required: [
|
|
33174
|
+
"id",
|
|
33175
|
+
"name",
|
|
33176
|
+
"number",
|
|
33177
|
+
"connection",
|
|
33178
|
+
"external_reference"
|
|
33179
|
+
]
|
|
33180
|
+
},
|
|
33181
|
+
previous_state: {
|
|
33182
|
+
type: "string",
|
|
33183
|
+
enum: [
|
|
33184
|
+
"available",
|
|
33185
|
+
"limited",
|
|
33186
|
+
"blocked",
|
|
33187
|
+
"unknown"
|
|
33188
|
+
]
|
|
33189
|
+
},
|
|
33190
|
+
state: {
|
|
33191
|
+
type: "string",
|
|
33192
|
+
enum: [
|
|
33193
|
+
"available",
|
|
33194
|
+
"limited",
|
|
33195
|
+
"blocked",
|
|
33196
|
+
"unknown"
|
|
33197
|
+
]
|
|
33198
|
+
},
|
|
33199
|
+
changed_at: {
|
|
33200
|
+
type: "string",
|
|
33201
|
+
format: "date-time"
|
|
33202
|
+
},
|
|
33203
|
+
connect_link_id: {
|
|
33204
|
+
type: [
|
|
33205
|
+
"integer",
|
|
33206
|
+
"null"
|
|
33207
|
+
],
|
|
33208
|
+
description: "The sign-up link that connected this number last, or `null`. Optional until accounts deploys it."
|
|
33209
|
+
}
|
|
33210
|
+
},
|
|
33211
|
+
required: [
|
|
33212
|
+
"device",
|
|
33213
|
+
"previous_state",
|
|
33214
|
+
"state",
|
|
33215
|
+
"changed_at"
|
|
33216
|
+
]
|
|
33217
|
+
}
|
|
33218
|
+
}
|
|
33219
|
+
},
|
|
33220
|
+
example: {
|
|
33221
|
+
id: "evt_0f8e4b526c1d4c7a9a3e2b5d",
|
|
33222
|
+
type: "device.health_changed",
|
|
33223
|
+
version: 1,
|
|
33224
|
+
occurred_at: "2026-10-22T03:00:00Z",
|
|
33225
|
+
data: {
|
|
33226
|
+
device: {
|
|
33227
|
+
id: 4521,
|
|
33228
|
+
name: "Ventas",
|
|
33229
|
+
number: "5491100000000",
|
|
33230
|
+
connection: "dedicated",
|
|
33231
|
+
external_reference: "cliente-8841"
|
|
33232
|
+
},
|
|
33233
|
+
previous_state: "available",
|
|
33234
|
+
state: "limited",
|
|
33235
|
+
changed_at: "2026-10-22T03:00:00+00:00",
|
|
33236
|
+
connect_link_id: 812
|
|
33237
|
+
}
|
|
33238
|
+
}
|
|
33239
|
+
}
|
|
33240
|
+
}
|
|
33241
|
+
}
|
|
33242
|
+
}
|
|
33021
33243
|
}
|
|
33022
33244
|
},
|
|
33023
33245
|
components: {
|
|
@@ -42103,6 +42325,345 @@ var openapi_default = {
|
|
|
42103
42325
|
required: [
|
|
42104
42326
|
"message"
|
|
42105
42327
|
]
|
|
42328
|
+
},
|
|
42329
|
+
PendingSandboxAccountsError: {
|
|
42330
|
+
type: "object",
|
|
42331
|
+
description: "An error of accounts: `error` (the code) and `message`; some codes add keys (`device_id`, `limit`).",
|
|
42332
|
+
properties: {
|
|
42333
|
+
error: {
|
|
42334
|
+
type: "string"
|
|
42335
|
+
},
|
|
42336
|
+
message: {
|
|
42337
|
+
type: "string"
|
|
42338
|
+
}
|
|
42339
|
+
},
|
|
42340
|
+
required: [
|
|
42341
|
+
"error",
|
|
42342
|
+
"message"
|
|
42343
|
+
]
|
|
42344
|
+
},
|
|
42345
|
+
PendingSandboxModelNotFound: {
|
|
42346
|
+
type: "object",
|
|
42347
|
+
description: 'The 404 of a model in accounts: only `message`, "Object not found".',
|
|
42348
|
+
properties: {
|
|
42349
|
+
message: {
|
|
42350
|
+
type: "string"
|
|
42351
|
+
}
|
|
42352
|
+
},
|
|
42353
|
+
required: [
|
|
42354
|
+
"message"
|
|
42355
|
+
]
|
|
42356
|
+
},
|
|
42357
|
+
PendingSandboxDeviceQr: {
|
|
42358
|
+
type: "object",
|
|
42359
|
+
properties: {
|
|
42360
|
+
data: {
|
|
42361
|
+
type: "object",
|
|
42362
|
+
properties: {
|
|
42363
|
+
connected: {
|
|
42364
|
+
type: "boolean"
|
|
42365
|
+
},
|
|
42366
|
+
qr_code: {
|
|
42367
|
+
type: [
|
|
42368
|
+
"string",
|
|
42369
|
+
"null"
|
|
42370
|
+
],
|
|
42371
|
+
description: "A PNG image as a data URI; `null` when the number is already connected."
|
|
42372
|
+
},
|
|
42373
|
+
expires_in: {
|
|
42374
|
+
type: "integer",
|
|
42375
|
+
description: "Seconds after which to ask for it again: 5 with a QR (ask for it again and show the new one at once: an old code does not link), 3 while the session starts, 0 once connected. Never `null`."
|
|
42376
|
+
}
|
|
42377
|
+
},
|
|
42378
|
+
required: [
|
|
42379
|
+
"connected",
|
|
42380
|
+
"qr_code",
|
|
42381
|
+
"expires_in"
|
|
42382
|
+
]
|
|
42383
|
+
}
|
|
42384
|
+
},
|
|
42385
|
+
required: [
|
|
42386
|
+
"data"
|
|
42387
|
+
]
|
|
42388
|
+
},
|
|
42389
|
+
PendingSandboxConnectLink: {
|
|
42390
|
+
type: "object",
|
|
42391
|
+
description: "A sign-up link, without its token.",
|
|
42392
|
+
properties: {
|
|
42393
|
+
id: {
|
|
42394
|
+
type: "integer"
|
|
42395
|
+
},
|
|
42396
|
+
type: {
|
|
42397
|
+
type: "string",
|
|
42398
|
+
enum: [
|
|
42399
|
+
"meta",
|
|
42400
|
+
"qr"
|
|
42401
|
+
]
|
|
42402
|
+
},
|
|
42403
|
+
status: {
|
|
42404
|
+
type: "string",
|
|
42405
|
+
enum: [
|
|
42406
|
+
"pending",
|
|
42407
|
+
"completed",
|
|
42408
|
+
"failed",
|
|
42409
|
+
"expired",
|
|
42410
|
+
"revoked"
|
|
42411
|
+
]
|
|
42412
|
+
},
|
|
42413
|
+
expires_at: {
|
|
42414
|
+
type: "string",
|
|
42415
|
+
format: "date-time"
|
|
42416
|
+
},
|
|
42417
|
+
allowed_connection_types: {
|
|
42418
|
+
type: [
|
|
42419
|
+
"array",
|
|
42420
|
+
"null"
|
|
42421
|
+
],
|
|
42422
|
+
items: {
|
|
42423
|
+
type: "string",
|
|
42424
|
+
enum: [
|
|
42425
|
+
"coexistence",
|
|
42426
|
+
"dedicated"
|
|
42427
|
+
]
|
|
42428
|
+
}
|
|
42429
|
+
},
|
|
42430
|
+
provision_us_number: {
|
|
42431
|
+
type: "boolean"
|
|
42432
|
+
},
|
|
42433
|
+
country: {
|
|
42434
|
+
type: [
|
|
42435
|
+
"string",
|
|
42436
|
+
"null"
|
|
42437
|
+
],
|
|
42438
|
+
description: "`qr` links that create a number only: the country of the phone, two letters in lower case. `null` otherwise."
|
|
42439
|
+
},
|
|
42440
|
+
device_id: {
|
|
42441
|
+
type: [
|
|
42442
|
+
"integer",
|
|
42443
|
+
"null"
|
|
42444
|
+
],
|
|
42445
|
+
description: "Only for a reconnection, or once the link is `completed`."
|
|
42446
|
+
},
|
|
42447
|
+
external_reference: {
|
|
42448
|
+
type: [
|
|
42449
|
+
"string",
|
|
42450
|
+
"null"
|
|
42451
|
+
]
|
|
42452
|
+
},
|
|
42453
|
+
return_url: {
|
|
42454
|
+
type: [
|
|
42455
|
+
"string",
|
|
42456
|
+
"null"
|
|
42457
|
+
]
|
|
42458
|
+
},
|
|
42459
|
+
error_code: {
|
|
42460
|
+
type: [
|
|
42461
|
+
"string",
|
|
42462
|
+
"null"
|
|
42463
|
+
],
|
|
42464
|
+
enum: [
|
|
42465
|
+
"link_expired",
|
|
42466
|
+
"already_used",
|
|
42467
|
+
"revoked",
|
|
42468
|
+
"quota_exceeded",
|
|
42469
|
+
"trial_number_blocked",
|
|
42470
|
+
"facebook_auth_failed",
|
|
42471
|
+
"meta_error",
|
|
42472
|
+
"number_not_owned",
|
|
42473
|
+
"qr_timeout",
|
|
42474
|
+
"provider_error",
|
|
42475
|
+
null
|
|
42476
|
+
]
|
|
42477
|
+
},
|
|
42478
|
+
created_at: {
|
|
42479
|
+
type: "string",
|
|
42480
|
+
format: "date-time"
|
|
42481
|
+
},
|
|
42482
|
+
completed_at: {
|
|
42483
|
+
type: [
|
|
42484
|
+
"string",
|
|
42485
|
+
"null"
|
|
42486
|
+
],
|
|
42487
|
+
format: "date-time"
|
|
42488
|
+
}
|
|
42489
|
+
},
|
|
42490
|
+
required: [
|
|
42491
|
+
"id",
|
|
42492
|
+
"type",
|
|
42493
|
+
"status",
|
|
42494
|
+
"expires_at",
|
|
42495
|
+
"allowed_connection_types",
|
|
42496
|
+
"provision_us_number",
|
|
42497
|
+
"country",
|
|
42498
|
+
"device_id",
|
|
42499
|
+
"external_reference",
|
|
42500
|
+
"return_url",
|
|
42501
|
+
"error_code",
|
|
42502
|
+
"created_at",
|
|
42503
|
+
"completed_at"
|
|
42504
|
+
]
|
|
42505
|
+
},
|
|
42506
|
+
PendingSandboxConnectLinkCreated: {
|
|
42507
|
+
type: "object",
|
|
42508
|
+
description: "The link as created: the only answer that carries `url`, with the raw token.",
|
|
42509
|
+
properties: {
|
|
42510
|
+
id: {
|
|
42511
|
+
type: "integer"
|
|
42512
|
+
},
|
|
42513
|
+
type: {
|
|
42514
|
+
type: "string",
|
|
42515
|
+
enum: [
|
|
42516
|
+
"meta",
|
|
42517
|
+
"qr"
|
|
42518
|
+
]
|
|
42519
|
+
},
|
|
42520
|
+
status: {
|
|
42521
|
+
type: "string",
|
|
42522
|
+
enum: [
|
|
42523
|
+
"pending",
|
|
42524
|
+
"completed",
|
|
42525
|
+
"failed",
|
|
42526
|
+
"expired",
|
|
42527
|
+
"revoked"
|
|
42528
|
+
]
|
|
42529
|
+
},
|
|
42530
|
+
expires_at: {
|
|
42531
|
+
type: "string",
|
|
42532
|
+
format: "date-time"
|
|
42533
|
+
},
|
|
42534
|
+
allowed_connection_types: {
|
|
42535
|
+
type: [
|
|
42536
|
+
"array",
|
|
42537
|
+
"null"
|
|
42538
|
+
],
|
|
42539
|
+
items: {
|
|
42540
|
+
type: "string",
|
|
42541
|
+
enum: [
|
|
42542
|
+
"coexistence",
|
|
42543
|
+
"dedicated"
|
|
42544
|
+
]
|
|
42545
|
+
}
|
|
42546
|
+
},
|
|
42547
|
+
provision_us_number: {
|
|
42548
|
+
type: "boolean"
|
|
42549
|
+
},
|
|
42550
|
+
country: {
|
|
42551
|
+
type: [
|
|
42552
|
+
"string",
|
|
42553
|
+
"null"
|
|
42554
|
+
],
|
|
42555
|
+
description: "`qr` links that create a number only: the country of the phone, two letters in lower case. `null` otherwise."
|
|
42556
|
+
},
|
|
42557
|
+
device_id: {
|
|
42558
|
+
type: [
|
|
42559
|
+
"integer",
|
|
42560
|
+
"null"
|
|
42561
|
+
],
|
|
42562
|
+
description: "Only for a reconnection, or once the link is `completed`."
|
|
42563
|
+
},
|
|
42564
|
+
external_reference: {
|
|
42565
|
+
type: [
|
|
42566
|
+
"string",
|
|
42567
|
+
"null"
|
|
42568
|
+
]
|
|
42569
|
+
},
|
|
42570
|
+
return_url: {
|
|
42571
|
+
type: [
|
|
42572
|
+
"string",
|
|
42573
|
+
"null"
|
|
42574
|
+
]
|
|
42575
|
+
},
|
|
42576
|
+
error_code: {
|
|
42577
|
+
type: [
|
|
42578
|
+
"string",
|
|
42579
|
+
"null"
|
|
42580
|
+
],
|
|
42581
|
+
enum: [
|
|
42582
|
+
"link_expired",
|
|
42583
|
+
"already_used",
|
|
42584
|
+
"revoked",
|
|
42585
|
+
"quota_exceeded",
|
|
42586
|
+
"trial_number_blocked",
|
|
42587
|
+
"facebook_auth_failed",
|
|
42588
|
+
"meta_error",
|
|
42589
|
+
"number_not_owned",
|
|
42590
|
+
"qr_timeout",
|
|
42591
|
+
"provider_error",
|
|
42592
|
+
null
|
|
42593
|
+
]
|
|
42594
|
+
},
|
|
42595
|
+
created_at: {
|
|
42596
|
+
type: "string",
|
|
42597
|
+
format: "date-time"
|
|
42598
|
+
},
|
|
42599
|
+
completed_at: {
|
|
42600
|
+
type: [
|
|
42601
|
+
"string",
|
|
42602
|
+
"null"
|
|
42603
|
+
],
|
|
42604
|
+
format: "date-time"
|
|
42605
|
+
},
|
|
42606
|
+
url: {
|
|
42607
|
+
type: "string",
|
|
42608
|
+
format: "uri",
|
|
42609
|
+
description: "`https://api.funnelchat.com/connect/<token>`, a 43-character base64url token. Shown once."
|
|
42610
|
+
}
|
|
42611
|
+
},
|
|
42612
|
+
required: [
|
|
42613
|
+
"id",
|
|
42614
|
+
"type",
|
|
42615
|
+
"status",
|
|
42616
|
+
"expires_at",
|
|
42617
|
+
"allowed_connection_types",
|
|
42618
|
+
"provision_us_number",
|
|
42619
|
+
"country",
|
|
42620
|
+
"device_id",
|
|
42621
|
+
"external_reference",
|
|
42622
|
+
"return_url",
|
|
42623
|
+
"error_code",
|
|
42624
|
+
"created_at",
|
|
42625
|
+
"completed_at",
|
|
42626
|
+
"url"
|
|
42627
|
+
]
|
|
42628
|
+
},
|
|
42629
|
+
PendingSandboxInvalidRequest: {
|
|
42630
|
+
type: "object",
|
|
42631
|
+
description: "The `422` of these routes: `invalid_request` with the errors per field.",
|
|
42632
|
+
properties: {
|
|
42633
|
+
error: {
|
|
42634
|
+
type: "string",
|
|
42635
|
+
const: "invalid_request"
|
|
42636
|
+
},
|
|
42637
|
+
message: {
|
|
42638
|
+
type: "string"
|
|
42639
|
+
},
|
|
42640
|
+
errors: {
|
|
42641
|
+
type: "object",
|
|
42642
|
+
additionalProperties: {
|
|
42643
|
+
type: "array",
|
|
42644
|
+
items: {
|
|
42645
|
+
type: "string"
|
|
42646
|
+
}
|
|
42647
|
+
}
|
|
42648
|
+
}
|
|
42649
|
+
},
|
|
42650
|
+
required: [
|
|
42651
|
+
"error",
|
|
42652
|
+
"message",
|
|
42653
|
+
"errors"
|
|
42654
|
+
]
|
|
42655
|
+
},
|
|
42656
|
+
PendingSandboxTooManyAttempts: {
|
|
42657
|
+
type: "object",
|
|
42658
|
+
description: 'Laravel\'s `429` of the per-minute limit: only `message`, "Too Many Attempts.", with `Retry-After`.',
|
|
42659
|
+
properties: {
|
|
42660
|
+
message: {
|
|
42661
|
+
type: "string"
|
|
42662
|
+
}
|
|
42663
|
+
},
|
|
42664
|
+
required: [
|
|
42665
|
+
"message"
|
|
42666
|
+
]
|
|
42106
42667
|
}
|
|
42107
42668
|
},
|
|
42108
42669
|
responses: {
|
|
@@ -42522,7 +43083,7 @@ var openapi_default = {
|
|
|
42522
43083
|
inputs: {
|
|
42523
43084
|
"spec/funnelchat.json": "8bbbae569b4cab520884db98f28204156c1bd6c629578bee609343c193e0dadb",
|
|
42524
43085
|
"spec/pending.json": "a14bc4dbc3025d3e958ffda8afd1fa9fdab34af264418060825f3300799aceb0",
|
|
42525
|
-
"spec/pending-sandbox.json": "
|
|
43086
|
+
"spec/pending-sandbox.json": "b2f7c2ccbab7c90f7adf22f8c3cec6700ff647700571dc306c898af8127bb22d"
|
|
42526
43087
|
},
|
|
42527
43088
|
snapshot_commit: "0ef4abb008870ed8c7cae49a08900a81e5e57ed8",
|
|
42528
43089
|
pending_commit: "88557ce7819c5a4061e84c58053a1b6c49f35ec2",
|
|
@@ -42581,31 +43142,28 @@ var openapi_default = {
|
|
|
42581
43142
|
"GET /api/v1/forms/families/{familyId}/responses [pending-sandbox.json]",
|
|
42582
43143
|
"GET /api/v1/forms/families/{familyId}/responses/{runId} [pending-sandbox.json]",
|
|
42583
43144
|
"POST /api/v1/contacts/{contact}/message/form [pending-sandbox.json]",
|
|
42584
|
-
"
|
|
42585
|
-
"
|
|
42586
|
-
"
|
|
42587
|
-
"POST /api/v1/
|
|
42588
|
-
"
|
|
42589
|
-
"
|
|
42590
|
-
"PUT /api/v1/groups/{group}/name [pending-sandbox.json]",
|
|
42591
|
-
"PUT /api/v1/groups/{group}/description [pending-sandbox.json]",
|
|
42592
|
-
"PUT /api/v1/groups/{group}/photo [pending-sandbox.json]",
|
|
42593
|
-
"PUT /api/v1/groups/{group}/settings [pending-sandbox.json]",
|
|
42594
|
-
"POST /api/v1/groups/{group}/invite-link/renew [pending-sandbox.json]",
|
|
42595
|
-
"POST /api/v1/groups/{group}/messages [pending-sandbox.json]",
|
|
42596
|
-
"GET /api/v1/groups/{group}/invite-link/live [pending-sandbox.json]",
|
|
42597
|
-
"GET /api/v1/groups/{group}/join-requests [pending-sandbox.json]",
|
|
42598
|
-
"POST /api/v1/groups/{group}/join-requests/approve [pending-sandbox.json]",
|
|
42599
|
-
"POST /api/v1/groups/{group}/join-requests/reject [pending-sandbox.json]",
|
|
43145
|
+
"POST /api/v1/devices [pending-sandbox.json]",
|
|
43146
|
+
"GET /api/v1/devices/{device}/qr [pending-sandbox.json]",
|
|
43147
|
+
"GET /api/v1/connect-links [pending-sandbox.json]",
|
|
43148
|
+
"POST /api/v1/connect-links [pending-sandbox.json]",
|
|
43149
|
+
"GET /api/v1/connect-links/{link} [pending-sandbox.json]",
|
|
43150
|
+
"DELETE /api/v1/connect-links/{link} [pending-sandbox.json]",
|
|
42600
43151
|
"POST /api/v1/contacts/{contact}/message (cuerpo: file_type, file_name) [pending-sandbox.json]",
|
|
42601
43152
|
"POST /api/v1/message/send-message (cuerpo: file_type, file_name) [pending-sandbox.json]",
|
|
43153
|
+
"PUT /api/v1/devices/{device} (cuerpo: external_reference) [pending-sandbox.json]",
|
|
43154
|
+
"GET /api/v1/devices (par\xE1metros: external_reference) [pending-sandbox.json]",
|
|
42602
43155
|
"POST /api/v1/contacts/{contact}/message (respuestas: 200) [pending-sandbox.json]",
|
|
42603
43156
|
"POST /api/v1/message/send-message (respuestas: 200) [pending-sandbox.json]",
|
|
42604
43157
|
"POST /api/v1/devices/{device}/templates/{template}/send-template (respuestas: 200) [pending-sandbox.json]",
|
|
42605
43158
|
"POST /api/v1/contacts/{contact}/message/contact-card (respuestas: 200) [pending-sandbox.json]",
|
|
42606
43159
|
"POST /api/v1/contacts/{contact}/messages/{message}/forward (respuestas: 200) [pending-sandbox.json]",
|
|
43160
|
+
"DELETE /api/v1/devices/{device} (respuestas: 200) [pending-sandbox.json]",
|
|
43161
|
+
"webhook device.deleted [pending-sandbox.json]",
|
|
43162
|
+
"webhook device.health_changed [pending-sandbox.json]",
|
|
42607
43163
|
"webhook message.received (data: device_id) [pending-sandbox.json]",
|
|
42608
|
-
"webhook message.status_changed (data: error) [pending-sandbox.json]"
|
|
43164
|
+
"webhook message.status_changed (data: error) [pending-sandbox.json]",
|
|
43165
|
+
"webhook device.connected (data: device, connect_link_id) [pending-sandbox.json]",
|
|
43166
|
+
"webhook device.disconnected (data: device, reason, connect_link_id) [pending-sandbox.json]"
|
|
42609
43167
|
]
|
|
42610
43168
|
}
|
|
42611
43169
|
};
|
|
@@ -42684,8 +43242,8 @@ function successResponse(op) {
|
|
|
42684
43242
|
const entries = Object.entries(op.responses ?? {}).filter(([status2]) => /^2\d\d$/.test(status2)).sort(([a], [b]) => Number(a) - Number(b));
|
|
42685
43243
|
const [status, raw] = entries[0] ?? ["200", void 0];
|
|
42686
43244
|
const response = deref(raw);
|
|
42687
|
-
const
|
|
42688
|
-
return { status: Number(status), schema:
|
|
43245
|
+
const json2 = response?.content?.["application/json"];
|
|
43246
|
+
return { status: Number(status), schema: json2?.schema, example: json2?.example };
|
|
42689
43247
|
}
|
|
42690
43248
|
|
|
42691
43249
|
// src/openapi/example.ts
|
|
@@ -42787,7 +43345,49 @@ function specData(type) {
|
|
|
42787
43345
|
}
|
|
42788
43346
|
return {};
|
|
42789
43347
|
}
|
|
42790
|
-
var
|
|
43348
|
+
var PENDING_DEVICE_ENTRIES = [
|
|
43349
|
+
{
|
|
43350
|
+
type: "device.deleted",
|
|
43351
|
+
area: "accounts",
|
|
43352
|
+
status: "active",
|
|
43353
|
+
is_critical: false,
|
|
43354
|
+
label: "N\xFAmero eliminado",
|
|
43355
|
+
description: "Uno de los n\xFAmeros de WhatsApp de la cuenta se elimin\xF3 y liber\xF3 su lugar del plan.",
|
|
43356
|
+
subscribable: true
|
|
43357
|
+
},
|
|
43358
|
+
{
|
|
43359
|
+
type: "device.health_changed",
|
|
43360
|
+
area: "accounts",
|
|
43361
|
+
status: "active",
|
|
43362
|
+
is_critical: false,
|
|
43363
|
+
label: "Cambi\xF3 la salud de un n\xFAmero",
|
|
43364
|
+
description: "Cambi\xF3 el estado de salud de uno de los n\xFAmeros oficiales de la cuenta: disponible, limitado, bloqueado o desconocido.",
|
|
43365
|
+
subscribable: true
|
|
43366
|
+
}
|
|
43367
|
+
];
|
|
43368
|
+
function envelopeFromSpec(type) {
|
|
43369
|
+
for (const [key, item] of Object.entries(spec.webhooks ?? {})) {
|
|
43370
|
+
if (eventTypeOfKey(key) !== type) continue;
|
|
43371
|
+
const example = item.post?.requestBody?.content?.["application/json"]?.example;
|
|
43372
|
+
if (isRecord(example) && "data" in example) return structuredClone(example);
|
|
43373
|
+
}
|
|
43374
|
+
return null;
|
|
43375
|
+
}
|
|
43376
|
+
function withPendingDeviceEvents(entries) {
|
|
43377
|
+
const out = entries.map((entry) => {
|
|
43378
|
+
if (entry.type !== "device.connected" && entry.type !== "device.disconnected") return entry;
|
|
43379
|
+
const data = entry.example?.data;
|
|
43380
|
+
if (isRecord(data) && isRecord(data.device) && "connection" in data.device) return entry;
|
|
43381
|
+
const example = envelopeFromSpec(entry.type);
|
|
43382
|
+
return example ? { ...entry, example } : entry;
|
|
43383
|
+
});
|
|
43384
|
+
const missing = PENDING_DEVICE_ENTRIES.filter((pending) => !out.some((entry) => entry.type === pending.type));
|
|
43385
|
+
if (missing.length === 0) return out;
|
|
43386
|
+
const after = out.map((entry) => entry.type.startsWith("device.")).lastIndexOf(true) + 1;
|
|
43387
|
+
out.splice(after, 0, ...missing.map((pending) => ({ ...pending, example: envelopeFromSpec(pending.type) })));
|
|
43388
|
+
return out;
|
|
43389
|
+
}
|
|
43390
|
+
var catalog = withPendingDeviceEvents(catalog_default.entries);
|
|
42791
43391
|
function catalogEntry(type) {
|
|
42792
43392
|
return catalog.find((entry) => entry.type === type);
|
|
42793
43393
|
}
|
|
@@ -43107,6 +43707,7 @@ var edgeError = (code) => {
|
|
|
43107
43707
|
var methodNotAllowed = (method, allowed) => new ApiError(405, "method_not_allowed", `El m\xE9todo ${method} no est\xE1 disponible para esta ruta. Usa ${allowed.join(", ")}.`, {}, { Allow: allowed.join(", ") });
|
|
43108
43708
|
var validationFailed = (errors) => new ApiError(422, "validation_failed", summarize(errors), { errors });
|
|
43109
43709
|
var fieldError = (field, message) => validationFailed({ [field]: [message] });
|
|
43710
|
+
var invalidRequest = (errors) => new ApiError(422, "invalid_request", summarize(errors), { errors });
|
|
43110
43711
|
|
|
43111
43712
|
// src/forms/templates.ts
|
|
43112
43713
|
var FORM_TEMPLATES = [
|
|
@@ -43378,8 +43979,8 @@ function parseForced(mode, value, field) {
|
|
|
43378
43979
|
function takeForcedFailure(app, headers, contactId) {
|
|
43379
43980
|
const reject = headers["x-sandbox-reject"];
|
|
43380
43981
|
if (reject) return { mode: "reject", code: parseForced("reject", reject, "X-Sandbox-Reject") };
|
|
43381
|
-
const
|
|
43382
|
-
if (
|
|
43982
|
+
const fail2 = headers["x-sandbox-fail"];
|
|
43983
|
+
if (fail2) return { mode: "fail", code: parseForced("fail", fail2, "X-Sandbox-Fail") };
|
|
43383
43984
|
const armed = app.store.failures.find((f) => f.mode !== "edge" && f.remaining > 0 && (f.contactId === null || f.contactId === contactId));
|
|
43384
43985
|
if (!armed) return null;
|
|
43385
43986
|
armed.remaining--;
|
|
@@ -43555,7 +44156,7 @@ function resolveFile(body2, style) {
|
|
|
43555
44156
|
|
|
43556
44157
|
// package.json
|
|
43557
44158
|
var package_default = {
|
|
43558
|
-
version: "0.
|
|
44159
|
+
version: "0.2.0"};
|
|
43559
44160
|
|
|
43560
44161
|
// src/version.ts
|
|
43561
44162
|
var VERSION = package_default.version;
|
|
@@ -43591,6 +44192,13 @@ var Store = class {
|
|
|
43591
44192
|
forms = /* @__PURE__ */ new Map();
|
|
43592
44193
|
formFamilies = /* @__PURE__ */ new Map();
|
|
43593
44194
|
formRuns = /* @__PURE__ */ new Map();
|
|
44195
|
+
connectLinks = /* @__PURE__ */ new Map();
|
|
44196
|
+
/** When each `POST /connect-links` arrived in the last minute: the per-minute limit. */
|
|
44197
|
+
linkAttempts = [];
|
|
44198
|
+
/** Answers by `Idempotency-Key`, per resource (`devices`, `connect-links`): one account. */
|
|
44199
|
+
idempotency = /* @__PURE__ */ new Map();
|
|
44200
|
+
/** The number quota of the account and the switch of the Meta sign-up (`POST /_sandbox/numbers`). */
|
|
44201
|
+
numbers = { limit: 5, included: true, trial: false, metaConnect: false, linksPerMinute: 10 };
|
|
43594
44202
|
/** Last inbound message per `contactId:deviceId`: the 24-hour window. */
|
|
43595
44203
|
lastInbound = /* @__PURE__ */ new Map();
|
|
43596
44204
|
/** API-key sends per contact since its last inbound message: the antiblock counters. */
|
|
@@ -43613,7 +44221,9 @@ var Store = class {
|
|
|
43613
44221
|
note: new Sequence(1),
|
|
43614
44222
|
formFamily: new Sequence(8001),
|
|
43615
44223
|
formVersion: new Sequence(8101),
|
|
43616
|
-
formRun: new Sequence(1)
|
|
44224
|
+
formRun: new Sequence(1),
|
|
44225
|
+
device: new Sequence(5),
|
|
44226
|
+
connectLink: new Sequence(1)
|
|
43617
44227
|
};
|
|
43618
44228
|
logRequest(entry) {
|
|
43619
44229
|
const full = { id: this.ids.request.next(), ...entry };
|
|
@@ -43647,8 +44257,14 @@ function seed(store, now = /* @__PURE__ */ new Date()) {
|
|
|
43647
44257
|
provider: 2,
|
|
43648
44258
|
kind: "cloud",
|
|
43649
44259
|
connected: true,
|
|
44260
|
+
coexistence: false,
|
|
44261
|
+
fromPool: false,
|
|
44262
|
+
externalReference: null,
|
|
44263
|
+
health: "available",
|
|
44264
|
+
lastConnectLinkId: null,
|
|
43650
44265
|
wabaId: "100000000000001",
|
|
43651
44266
|
phoneNumberId: "100000000000003",
|
|
44267
|
+
firstConnectedAt: now,
|
|
43652
44268
|
createdAt: now
|
|
43653
44269
|
});
|
|
43654
44270
|
store.devices.set(4, {
|
|
@@ -43658,8 +44274,14 @@ function seed(store, now = /* @__PURE__ */ new Date()) {
|
|
|
43658
44274
|
provider: 1,
|
|
43659
44275
|
kind: "qr",
|
|
43660
44276
|
connected: true,
|
|
44277
|
+
coexistence: false,
|
|
44278
|
+
fromPool: false,
|
|
44279
|
+
externalReference: null,
|
|
44280
|
+
health: null,
|
|
44281
|
+
lastConnectLinkId: null,
|
|
43661
44282
|
wabaId: null,
|
|
43662
44283
|
phoneNumberId: null,
|
|
44284
|
+
firstConnectedAt: now,
|
|
43663
44285
|
createdAt: now
|
|
43664
44286
|
});
|
|
43665
44287
|
const contactId = store.ids.contact.next();
|
|
@@ -43931,8 +44553,15 @@ var SandboxApp = class {
|
|
|
43931
44553
|
webhooks;
|
|
43932
44554
|
/** Grows on every reset, so work started before a reset does not touch the new state. */
|
|
43933
44555
|
generation = 0;
|
|
44556
|
+
/** The address the sandbox listens on, once it does: the host of the sign-up links when `publicUrl` is not set. */
|
|
44557
|
+
listenUrl = null;
|
|
44558
|
+
/** `http://host:port` of the sign-up page: `publicUrl`, or where the sandbox listens. */
|
|
44559
|
+
get publicUrl() {
|
|
44560
|
+
return this.config.publicUrl ?? this.listenUrl ?? `http://localhost:${this.config.port}`;
|
|
44561
|
+
}
|
|
43934
44562
|
populate() {
|
|
43935
44563
|
seed(this.store);
|
|
44564
|
+
this.store.numbers = { ...this.config.numbers };
|
|
43936
44565
|
for (const preset of this.config.subscriptions) {
|
|
43937
44566
|
const unknown = preset.events.filter((e) => !isSubscribable(e));
|
|
43938
44567
|
if (unknown.length) this.logger.warn(`subscription from the configuration with types that are not in the catalog: ${unknown.join(", ")}`);
|
|
@@ -43979,6 +44608,49 @@ function listContext(ctx) {
|
|
|
43979
44608
|
function body(ctx) {
|
|
43980
44609
|
return typeof ctx.body === "object" && ctx.body !== null && !Array.isArray(ctx.body) ? ctx.body : {};
|
|
43981
44610
|
}
|
|
44611
|
+
var IDEMPOTENCY_HEADER = "Idempotency-Key";
|
|
44612
|
+
var REPLAYED_HEADER = "Idempotent-Replayed";
|
|
44613
|
+
var TTL_MS = 24 * 60 * 60 * 1e3;
|
|
44614
|
+
var running = /* @__PURE__ */ new WeakMap();
|
|
44615
|
+
function canonical(value) {
|
|
44616
|
+
if (Array.isArray(value)) return value.map(canonical);
|
|
44617
|
+
if (typeof value !== "object" || value === null) return value;
|
|
44618
|
+
return Object.fromEntries(
|
|
44619
|
+
Object.keys(value).sort().map((key) => [key, canonical(value[key])])
|
|
44620
|
+
);
|
|
44621
|
+
}
|
|
44622
|
+
async function idempotent(ctx, resource, run) {
|
|
44623
|
+
const key = ctx.headers["idempotency-key"];
|
|
44624
|
+
if (key === void 0) return run();
|
|
44625
|
+
if (key.length < 1 || key.length > 255 || !/^[\x21-\x7e]+$/.test(key)) {
|
|
44626
|
+
throw new ApiError(422, "invalid_idempotency_key", `El header ${IDEMPOTENCY_HEADER} debe tener entre 1 y 255 caracteres visibles, sin espacios.`);
|
|
44627
|
+
}
|
|
44628
|
+
const { store } = ctx.app;
|
|
44629
|
+
const now = Date.now();
|
|
44630
|
+
for (const [k, answer] of store.idempotency) if (now - answer.storedAt.getTime() >= TTL_MS) store.idempotency.delete(k);
|
|
44631
|
+
const slot = `${resource}|${key}`;
|
|
44632
|
+
const fingerprint = createHash("sha256").update(JSON.stringify([ctx.op.method, ctx.pathname, canonical(ctx.body ?? null)])).digest("hex");
|
|
44633
|
+
const stored = store.idempotency.get(slot);
|
|
44634
|
+
if (stored) {
|
|
44635
|
+
if (stored.fingerprint !== fingerprint) {
|
|
44636
|
+
throw new ApiError(422, "idempotency_key_mismatch", `Ya usaste este ${IDEMPOTENCY_HEADER} con otro cuerpo. Usa una clave nueva para otra operaci\xF3n.`);
|
|
44637
|
+
}
|
|
44638
|
+
return { status: stored.status, body: structuredClone(stored.body), headers: { ...stored.headers, [REPLAYED_HEADER]: "true" } };
|
|
44639
|
+
}
|
|
44640
|
+
const inFlight = running.get(ctx.app) ?? /* @__PURE__ */ new Set();
|
|
44641
|
+
running.set(ctx.app, inFlight);
|
|
44642
|
+
if (inFlight.has(slot)) throw new ApiError(409, "operation_in_progress", `Una operaci\xF3n con este ${IDEMPOTENCY_HEADER} todav\xEDa est\xE1 en curso.`);
|
|
44643
|
+
inFlight.add(slot);
|
|
44644
|
+
try {
|
|
44645
|
+
const result = await run();
|
|
44646
|
+
if (result.status >= 200 && result.status < 300) {
|
|
44647
|
+
store.idempotency.set(slot, { fingerprint, status: result.status, body: structuredClone(result.body), headers: { ...result.headers }, storedAt: /* @__PURE__ */ new Date() });
|
|
44648
|
+
}
|
|
44649
|
+
return result;
|
|
44650
|
+
} finally {
|
|
44651
|
+
inFlight.delete(slot);
|
|
44652
|
+
}
|
|
44653
|
+
}
|
|
43982
44654
|
|
|
43983
44655
|
// src/http/paginate.ts
|
|
43984
44656
|
function perPageOf(query, fallback, max = 100) {
|
|
@@ -44000,27 +44672,27 @@ function urlWith(path, query, extra) {
|
|
|
44000
44672
|
function paginate(items, ctx, map, fallbackPerPage = 25) {
|
|
44001
44673
|
const perPage = perPageOf(ctx.query, fallbackPerPage);
|
|
44002
44674
|
const pageRaw = Number(ctx.query.page ?? 1);
|
|
44003
|
-
const
|
|
44675
|
+
const page2 = Number.isInteger(pageRaw) && pageRaw > 0 ? pageRaw : 1;
|
|
44004
44676
|
const total = items.length;
|
|
44005
44677
|
const lastPage = Math.max(1, Math.ceil(total / perPage));
|
|
44006
|
-
const slice = items.slice((
|
|
44007
|
-
const from = slice.length ? (
|
|
44678
|
+
const slice = items.slice((page2 - 1) * perPage, page2 * perPage);
|
|
44679
|
+
const from = slice.length ? (page2 - 1) * perPage + 1 : null;
|
|
44008
44680
|
const to = slice.length ? from + slice.length - 1 : null;
|
|
44009
44681
|
const link = (n) => urlWith(ctx.path, ctx.query, { page: n });
|
|
44010
44682
|
const pageLinks = [
|
|
44011
|
-
{ url:
|
|
44012
|
-
...Array.from({ length: lastPage }, (_, i) => ({ url: link(i + 1), label: String(i + 1), active: i + 1 ===
|
|
44013
|
-
{ url:
|
|
44683
|
+
{ url: page2 > 1 ? link(page2 - 1) : null, label: "« Anterior", active: false },
|
|
44684
|
+
...Array.from({ length: lastPage }, (_, i) => ({ url: link(i + 1), label: String(i + 1), active: i + 1 === page2 })),
|
|
44685
|
+
{ url: page2 < lastPage ? link(page2 + 1) : null, label: "Siguiente »", active: false }
|
|
44014
44686
|
];
|
|
44015
44687
|
return {
|
|
44016
44688
|
data: slice.map(map),
|
|
44017
44689
|
links: {
|
|
44018
44690
|
first: link(1),
|
|
44019
44691
|
last: link(lastPage),
|
|
44020
|
-
prev:
|
|
44021
|
-
next:
|
|
44692
|
+
prev: page2 > 1 ? link(page2 - 1) : null,
|
|
44693
|
+
next: page2 < lastPage ? link(page2 + 1) : null
|
|
44022
44694
|
},
|
|
44023
|
-
meta: { current_page:
|
|
44695
|
+
meta: { current_page: page2, from, last_page: lastPage, links: pageLinks, path: ctx.path, per_page: perPage, to, total }
|
|
44024
44696
|
};
|
|
44025
44697
|
}
|
|
44026
44698
|
function encodeCursor(position) {
|
|
@@ -44227,7 +44899,7 @@ function deviceCapabilities(device) {
|
|
|
44227
44899
|
function deviceHealth(device) {
|
|
44228
44900
|
if (device.kind !== "cloud") return null;
|
|
44229
44901
|
return {
|
|
44230
|
-
state: device.
|
|
44902
|
+
state: device.health ?? "unknown",
|
|
44231
44903
|
reason: null,
|
|
44232
44904
|
reason_truncated: false,
|
|
44233
44905
|
reason_key: null,
|
|
@@ -44239,15 +44911,19 @@ function deviceHealth(device) {
|
|
|
44239
44911
|
stale: false
|
|
44240
44912
|
};
|
|
44241
44913
|
}
|
|
44914
|
+
function deviceConnection(device) {
|
|
44915
|
+
if (device.kind === "qr") return "qr";
|
|
44916
|
+
return device.coexistence ? "coexistence" : "dedicated";
|
|
44917
|
+
}
|
|
44242
44918
|
function deviceResource(device) {
|
|
44243
44919
|
return {
|
|
44244
44920
|
id: device.id,
|
|
44245
44921
|
alias: device.alias,
|
|
44246
44922
|
image: "",
|
|
44247
44923
|
color: device.kind === "cloud" ? "#25D366" : "#128C7E",
|
|
44248
|
-
number: device.number,
|
|
44924
|
+
number: device.number ?? "",
|
|
44249
44925
|
normalized_number: device.number,
|
|
44250
|
-
first_conecction_at: laravelJson(device.
|
|
44926
|
+
first_conecction_at: device.firstConnectedAt ? laravelJson(device.firstConnectedAt) : null,
|
|
44251
44927
|
user_id: ACCOUNT_USER_ID,
|
|
44252
44928
|
provider: device.provider,
|
|
44253
44929
|
waba_id: device.wabaId ?? "",
|
|
@@ -44257,15 +44933,43 @@ function deviceResource(device) {
|
|
|
44257
44933
|
active: 1,
|
|
44258
44934
|
health: deviceHealth(device),
|
|
44259
44935
|
number_key: device.number,
|
|
44260
|
-
number_key_confirmed:
|
|
44261
|
-
is_coexistence:
|
|
44936
|
+
number_key_confirmed: device.number !== null,
|
|
44937
|
+
is_coexistence: device.coexistence,
|
|
44262
44938
|
slot_state: "held",
|
|
44263
44939
|
pool_suspended: false,
|
|
44264
44940
|
pool_suspension_reason: null,
|
|
44265
44941
|
pool_keep: false,
|
|
44266
44942
|
qr_allowed: device.kind === "qr",
|
|
44267
44943
|
can_change_to_meta: false,
|
|
44268
|
-
capabilities: deviceCapabilities(device)
|
|
44944
|
+
capabilities: deviceCapabilities(device),
|
|
44945
|
+
external_reference: device.externalReference,
|
|
44946
|
+
connection: deviceConnection(device)
|
|
44947
|
+
};
|
|
44948
|
+
}
|
|
44949
|
+
function eventDevice(device) {
|
|
44950
|
+
return {
|
|
44951
|
+
id: device.id,
|
|
44952
|
+
name: device.alias,
|
|
44953
|
+
number: device.number,
|
|
44954
|
+
connection: deviceConnection(device),
|
|
44955
|
+
external_reference: device.externalReference
|
|
44956
|
+
};
|
|
44957
|
+
}
|
|
44958
|
+
function connectLinkResource(link) {
|
|
44959
|
+
return {
|
|
44960
|
+
id: link.id,
|
|
44961
|
+
type: link.type,
|
|
44962
|
+
status: link.status,
|
|
44963
|
+
expires_at: iso8601(link.expiresAt),
|
|
44964
|
+
allowed_connection_types: link.allowedConnectionTypes,
|
|
44965
|
+
provision_us_number: link.provisionUsNumber,
|
|
44966
|
+
country: link.country,
|
|
44967
|
+
device_id: link.deviceId,
|
|
44968
|
+
external_reference: link.externalReference,
|
|
44969
|
+
return_url: link.returnUrl,
|
|
44970
|
+
error_code: link.errorCode,
|
|
44971
|
+
created_at: iso8601(link.createdAt),
|
|
44972
|
+
completed_at: link.completedAt ? iso8601(link.completedAt) : null
|
|
44269
44973
|
};
|
|
44270
44974
|
}
|
|
44271
44975
|
function publicMessage(message) {
|
|
@@ -44355,6 +45059,354 @@ function deliveryResource(delivery) {
|
|
|
44355
45059
|
};
|
|
44356
45060
|
}
|
|
44357
45061
|
|
|
45062
|
+
// src/numbers/numbers.ts
|
|
45063
|
+
var DISCONNECT_REASONS = ["qr_logged_out", "meta_account_not_registered", "shadow_ban_protection", "provider_error", "unknown", "disconnected_by_user"];
|
|
45064
|
+
var HEALTH_STATES = ["available", "limited", "blocked", "unknown"];
|
|
45065
|
+
var LINK_ERROR_CODES = [
|
|
45066
|
+
"link_expired",
|
|
45067
|
+
"already_used",
|
|
45068
|
+
"revoked",
|
|
45069
|
+
"quota_exceeded",
|
|
45070
|
+
"trial_number_blocked",
|
|
45071
|
+
"facebook_auth_failed",
|
|
45072
|
+
"meta_error",
|
|
45073
|
+
"number_not_owned",
|
|
45074
|
+
"qr_timeout",
|
|
45075
|
+
"provider_error"
|
|
45076
|
+
];
|
|
45077
|
+
var OPEN_LINKS_LIMIT = 20;
|
|
45078
|
+
var poolLimit = (app) => app.store.numbers.trial ? 1 : 3;
|
|
45079
|
+
var placeOf = (device) => device.number ?? `device:${device.id}`;
|
|
45080
|
+
function placesInUse(app) {
|
|
45081
|
+
return new Set([...app.store.devices.values()].map(placeOf)).size;
|
|
45082
|
+
}
|
|
45083
|
+
var isPendingQr = (device) => device.kind === "qr" && !device.connected && device.firstConnectedAt === null;
|
|
45084
|
+
function pendingQrIds(app) {
|
|
45085
|
+
return [...app.store.devices.values()].filter(isPendingQr).map((d) => d.id);
|
|
45086
|
+
}
|
|
45087
|
+
function checkQuota(app) {
|
|
45088
|
+
const { numbers: numbers2 } = app.store;
|
|
45089
|
+
if (!numbers2.included) {
|
|
45090
|
+
throw new ApiError(403, "numbers_not_included", "Tu plan no incluye n\xFAmeros de WhatsApp.");
|
|
45091
|
+
}
|
|
45092
|
+
const used = placesInUse(app);
|
|
45093
|
+
if (used >= numbers2.limit) {
|
|
45094
|
+
throw new ApiError(403, "number_limit_reached", `Alcanzaste el l\xEDmite de ${numbers2.limit} n\xFAmeros de tu plan.`, {
|
|
45095
|
+
limit: numbers2.limit,
|
|
45096
|
+
used,
|
|
45097
|
+
pending_device_ids: pendingQrIds(app)
|
|
45098
|
+
});
|
|
45099
|
+
}
|
|
45100
|
+
}
|
|
45101
|
+
function checkPendingNumber(app) {
|
|
45102
|
+
const [pending] = pendingQrIds(app);
|
|
45103
|
+
if (pending !== void 0) {
|
|
45104
|
+
throw new ApiError(409, "pending_number_exists", "Ya tienes un n\xFAmero por QR sin conectar: con\xE9ctalo o b\xF3rralo antes de crear otro.", { device_id: pending });
|
|
45105
|
+
}
|
|
45106
|
+
}
|
|
45107
|
+
var invalidReferenceDevice = () => new ApiError(422, "invalid_reference_device", "El n\xFAmero indicado no existe en la cuenta.");
|
|
45108
|
+
function checkSameNumber(app, id) {
|
|
45109
|
+
const target = app.store.devices.get(Number(id));
|
|
45110
|
+
if (!target) throw invalidReferenceDevice();
|
|
45111
|
+
if (target.kind === "qr") {
|
|
45112
|
+
throw new ApiError(422, "method_already_present", "Ese n\xFAmero ya tiene QR.", { device_id: target.id });
|
|
45113
|
+
}
|
|
45114
|
+
const sibling = [...app.store.devices.values()].find((d) => d.kind === "qr" && d.number !== null && d.number === target.number);
|
|
45115
|
+
if (sibling) throw new ApiError(422, "method_already_present", "Ese n\xFAmero ya tiene QR.", { device_id: sibling.id });
|
|
45116
|
+
if (!target.coexistence) {
|
|
45117
|
+
throw new ApiError(422, "qr_requires_coexistence", "Solo puedes sumar QR a un n\xFAmero oficial que est\xE9 en coexistencia con la app de WhatsApp Business.");
|
|
45118
|
+
}
|
|
45119
|
+
return target;
|
|
45120
|
+
}
|
|
45121
|
+
function createDevice(app, input) {
|
|
45122
|
+
const id = app.store.ids.device.next();
|
|
45123
|
+
const now = /* @__PURE__ */ new Date();
|
|
45124
|
+
const cloud = input.connection !== "qr";
|
|
45125
|
+
const connected = input.connected ?? false;
|
|
45126
|
+
const device = {
|
|
45127
|
+
id,
|
|
45128
|
+
alias: input.alias ?? `N\xFAmero ${id}`,
|
|
45129
|
+
number: input.number ?? null,
|
|
45130
|
+
provider: cloud ? 2 : 1,
|
|
45131
|
+
kind: cloud ? "cloud" : "qr",
|
|
45132
|
+
connected,
|
|
45133
|
+
coexistence: input.connection === "coexistence",
|
|
45134
|
+
fromPool: input.fromPool ?? false,
|
|
45135
|
+
externalReference: input.externalReference ?? null,
|
|
45136
|
+
health: cloud ? connected ? "available" : "unknown" : null,
|
|
45137
|
+
lastConnectLinkId: null,
|
|
45138
|
+
wabaId: cloud ? `1000000000${String(id).padStart(5, "0")}` : null,
|
|
45139
|
+
phoneNumberId: cloud ? `2000000000${String(id).padStart(5, "0")}` : null,
|
|
45140
|
+
firstConnectedAt: connected ? now : null,
|
|
45141
|
+
createdAt: now
|
|
45142
|
+
};
|
|
45143
|
+
app.store.devices.set(id, device);
|
|
45144
|
+
return device;
|
|
45145
|
+
}
|
|
45146
|
+
function fakeNumber(device) {
|
|
45147
|
+
const tail = String(device.id).padStart(4, "0").slice(-4);
|
|
45148
|
+
return device.fromPool ? `1555010${tail}` : `54911555${tail}`;
|
|
45149
|
+
}
|
|
45150
|
+
var eventData = (device, rest, connectLinkId = device.lastConnectLinkId) => ({
|
|
45151
|
+
device: eventDevice(device),
|
|
45152
|
+
...rest,
|
|
45153
|
+
connect_link_id: connectLinkId
|
|
45154
|
+
});
|
|
45155
|
+
function connectDevice(app, device, options = {}) {
|
|
45156
|
+
if (device.connected) return null;
|
|
45157
|
+
const at = /* @__PURE__ */ new Date();
|
|
45158
|
+
const connectLinkId = options.connectLinkId ?? null;
|
|
45159
|
+
if (connectLinkId !== null) device.lastConnectLinkId = connectLinkId;
|
|
45160
|
+
if (options.number) device.number = options.number;
|
|
45161
|
+
device.number ??= fakeNumber(device);
|
|
45162
|
+
device.connected = true;
|
|
45163
|
+
device.firstConnectedAt ??= at;
|
|
45164
|
+
if (device.kind === "cloud" && (device.health === null || device.health === "unknown")) device.health = "available";
|
|
45165
|
+
return app.webhooks.emit("device.connected", eventData(device, { connected_at: isoSeconds(at) }, connectLinkId), { occurredAt: at });
|
|
45166
|
+
}
|
|
45167
|
+
function disconnectDevice(app, device, reason) {
|
|
45168
|
+
if (!device.connected) return null;
|
|
45169
|
+
const at = /* @__PURE__ */ new Date();
|
|
45170
|
+
device.connected = false;
|
|
45171
|
+
return app.webhooks.emit("device.disconnected", eventData(device, { disconnected_at: isoSeconds(at), reason }), { occurredAt: at });
|
|
45172
|
+
}
|
|
45173
|
+
function setHealth(app, device, state2) {
|
|
45174
|
+
const previous = device.health ?? "unknown";
|
|
45175
|
+
if (previous === state2) return null;
|
|
45176
|
+
device.health = state2;
|
|
45177
|
+
const at = /* @__PURE__ */ new Date();
|
|
45178
|
+
return app.webhooks.emit("device.health_changed", eventData(device, { previous_state: previous, state: state2, changed_at: isoSeconds(at) }), { occurredAt: at });
|
|
45179
|
+
}
|
|
45180
|
+
function deleteDevice(app, device) {
|
|
45181
|
+
const at = /* @__PURE__ */ new Date();
|
|
45182
|
+
const event = app.webhooks.emit("device.deleted", eventData(device, { deleted_at: isoSeconds(at) }), { occurredAt: at });
|
|
45183
|
+
app.store.devices.delete(device.id);
|
|
45184
|
+
for (const link of app.store.connectLinks.values()) {
|
|
45185
|
+
if (link.reconnect && link.deviceId === device.id && link.status === "pending") {
|
|
45186
|
+
link.status = "revoked";
|
|
45187
|
+
link.errorCode = "revoked";
|
|
45188
|
+
}
|
|
45189
|
+
}
|
|
45190
|
+
return event;
|
|
45191
|
+
}
|
|
45192
|
+
var sha256 = (text2) => createHash("sha256").update(text2).digest("hex");
|
|
45193
|
+
var newToken = () => randomBytes(32).toString("base64url");
|
|
45194
|
+
function refreshLink(link, now = /* @__PURE__ */ new Date()) {
|
|
45195
|
+
if (link.status === "pending" && link.expiresAt.getTime() <= now.getTime()) {
|
|
45196
|
+
link.status = "expired";
|
|
45197
|
+
link.errorCode = "link_expired";
|
|
45198
|
+
}
|
|
45199
|
+
return link;
|
|
45200
|
+
}
|
|
45201
|
+
function findLinkByToken(app, token) {
|
|
45202
|
+
const hash = sha256(token);
|
|
45203
|
+
for (const link of app.store.connectLinks.values()) if (link.tokenHash === hash) return refreshLink(link);
|
|
45204
|
+
return void 0;
|
|
45205
|
+
}
|
|
45206
|
+
function openLinks(app) {
|
|
45207
|
+
let n = 0;
|
|
45208
|
+
for (const link of app.store.connectLinks.values()) if (refreshLink(link).status === "pending") n++;
|
|
45209
|
+
return n;
|
|
45210
|
+
}
|
|
45211
|
+
var META_ATTEMPTS = 5;
|
|
45212
|
+
var RETRYABLE = ["facebook_auth_failed", "meta_error"];
|
|
45213
|
+
function fail(link, code) {
|
|
45214
|
+
link.status = "failed";
|
|
45215
|
+
link.errorCode = code;
|
|
45216
|
+
link.completedAt = /* @__PURE__ */ new Date();
|
|
45217
|
+
return "failed";
|
|
45218
|
+
}
|
|
45219
|
+
function connectionAllowed(link, connection) {
|
|
45220
|
+
return link.type !== "meta" || !connection || (link.allowedConnectionTypes ?? []).includes(connection);
|
|
45221
|
+
}
|
|
45222
|
+
function completeLink(app, link, input) {
|
|
45223
|
+
if (input.outcome === "failed") {
|
|
45224
|
+
const code = input.errorCode ?? (link.type === "qr" ? "qr_timeout" : "meta_error");
|
|
45225
|
+
if (link.type === "meta" && RETRYABLE.includes(code)) {
|
|
45226
|
+
link.failedAttempts++;
|
|
45227
|
+
if (link.failedAttempts < META_ATTEMPTS) {
|
|
45228
|
+
link.errorCode = null;
|
|
45229
|
+
return "retry";
|
|
45230
|
+
}
|
|
45231
|
+
}
|
|
45232
|
+
return fail(link, code);
|
|
45233
|
+
}
|
|
45234
|
+
link.status = "completed";
|
|
45235
|
+
if (link.reconnect) {
|
|
45236
|
+
const device2 = link.deviceId === null ? void 0 : app.store.devices.get(link.deviceId);
|
|
45237
|
+
if (!device2) return fail(link, "provider_error");
|
|
45238
|
+
link.completedAt = /* @__PURE__ */ new Date();
|
|
45239
|
+
connectDevice(app, device2, { number: input.number, connectLinkId: link.id });
|
|
45240
|
+
return "completed";
|
|
45241
|
+
}
|
|
45242
|
+
let connection = "qr";
|
|
45243
|
+
let number = input.number ?? null;
|
|
45244
|
+
let fromPool = false;
|
|
45245
|
+
if (link.sameNumberAs !== null) {
|
|
45246
|
+
const target = app.store.devices.get(link.sameNumberAs);
|
|
45247
|
+
if (!target || !target.coexistence) return fail(link, "provider_error");
|
|
45248
|
+
number = target.number;
|
|
45249
|
+
} else {
|
|
45250
|
+
try {
|
|
45251
|
+
checkQuota(app);
|
|
45252
|
+
} catch {
|
|
45253
|
+
return fail(link, "quota_exceeded");
|
|
45254
|
+
}
|
|
45255
|
+
}
|
|
45256
|
+
if (link.type === "meta") {
|
|
45257
|
+
const allowed = link.allowedConnectionTypes ?? ["dedicated"];
|
|
45258
|
+
connection = input.connection && allowed.includes(input.connection) ? input.connection : allowed[0];
|
|
45259
|
+
if (connection === "dedicated" && link.provisionUsNumber && !number) {
|
|
45260
|
+
const inPool = [...app.store.devices.values()].filter((d) => d.fromPool).length;
|
|
45261
|
+
if (inPool >= poolLimit(app)) return fail(link, app.store.numbers.trial ? "trial_number_blocked" : "quota_exceeded");
|
|
45262
|
+
fromPool = true;
|
|
45263
|
+
}
|
|
45264
|
+
}
|
|
45265
|
+
const device = createDevice(app, { connection, externalReference: link.externalReference, fromPool, number });
|
|
45266
|
+
link.deviceId = device.id;
|
|
45267
|
+
link.completedAt = /* @__PURE__ */ new Date();
|
|
45268
|
+
connectDevice(app, device, { connectLinkId: link.id });
|
|
45269
|
+
return "completed";
|
|
45270
|
+
}
|
|
45271
|
+
function returnUrlFor(link) {
|
|
45272
|
+
if (!link.returnUrl) return null;
|
|
45273
|
+
const url = new URL(link.returnUrl);
|
|
45274
|
+
url.searchParams.set("connect_link_id", String(link.id));
|
|
45275
|
+
url.searchParams.set("status", link.status === "completed" ? "completed" : "failed");
|
|
45276
|
+
if (link.deviceId !== null && link.status === "completed") url.searchParams.set("device_id", String(link.deviceId));
|
|
45277
|
+
if (link.errorCode && link.status !== "completed") url.searchParams.set("error_code", link.errorCode);
|
|
45278
|
+
return url.toString();
|
|
45279
|
+
}
|
|
45280
|
+
|
|
45281
|
+
// src/handlers/connect-links.ts
|
|
45282
|
+
var DEFAULT_EXPIRES_IN_MINUTES = 1440;
|
|
45283
|
+
function linkOf(app, id) {
|
|
45284
|
+
const link = app.store.connectLinks.get(Number(id));
|
|
45285
|
+
if (!link) throw notFound("No encontramos ese link de alta en la cuenta.");
|
|
45286
|
+
return refreshLink(link);
|
|
45287
|
+
}
|
|
45288
|
+
var present = (value) => value !== void 0 && value !== null && value !== "" && !(Array.isArray(value) && value.length === 0);
|
|
45289
|
+
function checkLinkBody(b) {
|
|
45290
|
+
const errors = {};
|
|
45291
|
+
const meta = b.type === "meta";
|
|
45292
|
+
if (meta && !present(b.allowed_connection_types)) {
|
|
45293
|
+
errors.allowed_connection_types = ["El campo allowed_connection_types es obligatorio cuando type es meta."];
|
|
45294
|
+
}
|
|
45295
|
+
if (!meta && present(b.allowed_connection_types)) {
|
|
45296
|
+
errors.allowed_connection_types = ["El campo allowed_connection_types est\xE1 prohibido cuando type es qr."];
|
|
45297
|
+
}
|
|
45298
|
+
if (!meta && present(b.provision_us_number)) errors.provision_us_number = ["El campo provision_us_number est\xE1 prohibido cuando type es qr."];
|
|
45299
|
+
else if (meta && b.provision_us_number === true) {
|
|
45300
|
+
const allowed = Array.isArray(b.allowed_connection_types) ? b.allowed_connection_types : [];
|
|
45301
|
+
if (present(b.device_id)) errors.provision_us_number = ["El campo provision_us_number no se acepta junto con device_id."];
|
|
45302
|
+
else if (present(b.allowed_connection_types) && !allowed.includes("dedicated")) {
|
|
45303
|
+
errors.provision_us_number = ["El campo provision_us_number exige dedicated en allowed_connection_types."];
|
|
45304
|
+
}
|
|
45305
|
+
}
|
|
45306
|
+
if (meta && present(b.country)) errors.country = ["El campo country est\xE1 prohibido cuando type es meta."];
|
|
45307
|
+
else if (present(b.country) && present(b.device_id)) errors.country = ["El campo country proh\xEDbe que device_id est\xE9 presente."];
|
|
45308
|
+
if (meta && present(b.same_number_as)) errors.same_number_as = ["El campo same_number_as est\xE1 prohibido cuando type es meta."];
|
|
45309
|
+
else if (present(b.same_number_as) && present(b.device_id)) errors.same_number_as = ["El campo same_number_as proh\xEDbe que device_id est\xE9 presente."];
|
|
45310
|
+
if (present(b.return_url)) {
|
|
45311
|
+
let url = null;
|
|
45312
|
+
try {
|
|
45313
|
+
url = new URL(String(b.return_url));
|
|
45314
|
+
} catch {
|
|
45315
|
+
url = null;
|
|
45316
|
+
}
|
|
45317
|
+
if (!url || url.protocol !== "https:") errors.return_url = ["El campo return_url debe ser una URL https."];
|
|
45318
|
+
}
|
|
45319
|
+
if (Object.keys(errors).length) throw invalidRequest(errors);
|
|
45320
|
+
}
|
|
45321
|
+
function throttle(app) {
|
|
45322
|
+
const now = Date.now();
|
|
45323
|
+
const { store } = app;
|
|
45324
|
+
store.linkAttempts = store.linkAttempts.filter((at) => now - at < 6e4);
|
|
45325
|
+
if (store.linkAttempts.length >= store.numbers.linksPerMinute) {
|
|
45326
|
+
const retryAfter = Math.max(1, Math.ceil((6e4 - (now - store.linkAttempts[0])) / 1e3));
|
|
45327
|
+
throw new RawApiError(429, { message: "Too Many Attempts." }, { "Retry-After": String(retryAfter) });
|
|
45328
|
+
}
|
|
45329
|
+
store.linkAttempts.push(now);
|
|
45330
|
+
}
|
|
45331
|
+
var connectLinkHandlers = {
|
|
45332
|
+
"POST /api/v1/connect-links": (ctx) => {
|
|
45333
|
+
throttle(ctx.app);
|
|
45334
|
+
return idempotent(ctx, "connect-links", () => {
|
|
45335
|
+
const { app } = ctx;
|
|
45336
|
+
const b = body(ctx);
|
|
45337
|
+
checkLinkBody(b);
|
|
45338
|
+
if (b.type === "meta" && !app.store.numbers.metaConnect) {
|
|
45339
|
+
throw new ApiError(503, "meta_connect_disabled", "El alta de n\xFAmeros oficiales por link no est\xE1 disponible todav\xEDa.");
|
|
45340
|
+
}
|
|
45341
|
+
const reconnect = present(b.device_id);
|
|
45342
|
+
if (reconnect) {
|
|
45343
|
+
const device = app.store.devices.get(Number(b.device_id));
|
|
45344
|
+
if (!device) throw invalidReferenceDevice();
|
|
45345
|
+
if (device.kind === "qr" !== (b.type === "qr")) {
|
|
45346
|
+
throw new ApiError(422, "connection_type_mismatch", "El type del link tiene que coincidir con el m\xE9todo del n\xFAmero a reconectar.");
|
|
45347
|
+
}
|
|
45348
|
+
}
|
|
45349
|
+
const sameNumberAs = present(b.same_number_as) ? checkSameNumber(app, b.same_number_as).id : null;
|
|
45350
|
+
if (!reconnect && sameNumberAs === null) checkQuota(app);
|
|
45351
|
+
if (openLinks(app) >= OPEN_LINKS_LIMIT) {
|
|
45352
|
+
throw new ApiError(429, "too_many_open_links", `Tienes ${OPEN_LINKS_LIMIT} links de alta abiertos. Revoca alguno o espera a que venzan.`, { limit: OPEN_LINKS_LIMIT });
|
|
45353
|
+
}
|
|
45354
|
+
const now = /* @__PURE__ */ new Date();
|
|
45355
|
+
const deviceId = reconnect ? Number(b.device_id) : null;
|
|
45356
|
+
if (reconnect) {
|
|
45357
|
+
for (const other of app.store.connectLinks.values()) {
|
|
45358
|
+
if (other.reconnect && other.deviceId === deviceId && refreshLink(other).status === "pending") {
|
|
45359
|
+
other.status = "revoked";
|
|
45360
|
+
other.errorCode = "revoked";
|
|
45361
|
+
}
|
|
45362
|
+
}
|
|
45363
|
+
}
|
|
45364
|
+
const token = newToken();
|
|
45365
|
+
const minutes = present(b.expires_in_minutes) ? Number(b.expires_in_minutes) : DEFAULT_EXPIRES_IN_MINUTES;
|
|
45366
|
+
const link = {
|
|
45367
|
+
id: app.store.ids.connectLink.next(),
|
|
45368
|
+
type: b.type === "meta" ? "meta" : "qr",
|
|
45369
|
+
status: "pending",
|
|
45370
|
+
tokenHash: sha256(token),
|
|
45371
|
+
allowedConnectionTypes: b.type === "meta" ? [...new Set(b.allowed_connection_types)] : null,
|
|
45372
|
+
provisionUsNumber: b.type === "meta" && b.provision_us_number === true,
|
|
45373
|
+
// accounts keeps it in lower case, and only on a QR link that creates a number.
|
|
45374
|
+
country: b.type === "qr" && !reconnect && present(b.country) ? String(b.country).toLowerCase() : null,
|
|
45375
|
+
deviceId,
|
|
45376
|
+
reconnect,
|
|
45377
|
+
sameNumberAs,
|
|
45378
|
+
externalReference: present(b.external_reference) ? String(b.external_reference) : null,
|
|
45379
|
+
returnUrl: present(b.return_url) ? String(b.return_url) : null,
|
|
45380
|
+
errorCode: null,
|
|
45381
|
+
failedAttempts: 0,
|
|
45382
|
+
expiresAt: new Date(now.getTime() + minutes * 6e4),
|
|
45383
|
+
createdAt: now,
|
|
45384
|
+
completedAt: null
|
|
45385
|
+
};
|
|
45386
|
+
app.store.connectLinks.set(link.id, link);
|
|
45387
|
+
app.logger.info(`sign-up link ${link.id} (${link.type}) \u2192 ${app.publicUrl}/connect/\u2026`);
|
|
45388
|
+
const { id, type, status, ...rest } = connectLinkResource(link);
|
|
45389
|
+
return { status: 201, body: { data: { id, type, status, url: `${app.publicUrl}/connect/${token}`, ...rest } }, headers: { "Cache-Control": "no-store" } };
|
|
45390
|
+
});
|
|
45391
|
+
},
|
|
45392
|
+
// Paginated like the other lists, newest first (by id); filters by `status` and `external_reference`.
|
|
45393
|
+
"GET /api/v1/connect-links": (ctx) => {
|
|
45394
|
+
const { status, external_reference: reference } = ctx.query;
|
|
45395
|
+
const list = [...ctx.app.store.connectLinks.values()].map((link) => refreshLink(link)).filter((link) => !status || link.status === status).filter((link) => reference === void 0 || reference === "" || link.externalReference === String(reference)).sort((a, b) => b.id - a.id);
|
|
45396
|
+
return ok(paginate(list, listContext(ctx), connectLinkResource));
|
|
45397
|
+
},
|
|
45398
|
+
"GET /api/v1/connect-links/{link}": (ctx) => ok({ data: connectLinkResource(linkOf(ctx.app, ctx.path.link)) }),
|
|
45399
|
+
"DELETE /api/v1/connect-links/{link}": (ctx) => {
|
|
45400
|
+
const link = linkOf(ctx.app, ctx.path.link);
|
|
45401
|
+
if (link.status !== "pending") {
|
|
45402
|
+
throw new ApiError(409, "link_not_pending", `El link ya no est\xE1 pendiente: est\xE1 ${link.status}.`, { status: link.status });
|
|
45403
|
+
}
|
|
45404
|
+
link.status = "revoked";
|
|
45405
|
+
link.errorCode = "revoked";
|
|
45406
|
+
return noContent();
|
|
45407
|
+
}
|
|
45408
|
+
};
|
|
45409
|
+
|
|
44358
45410
|
// src/handlers/contacts.ts
|
|
44359
45411
|
function getContact(app, id) {
|
|
44360
45412
|
const contact = app.store.contacts.get(Number(id));
|
|
@@ -44711,6 +45763,149 @@ function getCustomField(ctx) {
|
|
|
44711
45763
|
if (!field) throw objectNotFound();
|
|
44712
45764
|
return field;
|
|
44713
45765
|
}
|
|
45766
|
+
var MODULES = 25;
|
|
45767
|
+
var SCALE = 6;
|
|
45768
|
+
var QUIET = 4;
|
|
45769
|
+
var crcTable2 = null;
|
|
45770
|
+
function crc32(bytes) {
|
|
45771
|
+
if (!crcTable2) {
|
|
45772
|
+
crcTable2 = new Int32Array(256);
|
|
45773
|
+
for (let n = 0; n < 256; n++) {
|
|
45774
|
+
let c = n;
|
|
45775
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
45776
|
+
crcTable2[n] = c;
|
|
45777
|
+
}
|
|
45778
|
+
}
|
|
45779
|
+
let crc = -1;
|
|
45780
|
+
for (const byte of bytes) crc = crcTable2[(crc ^ byte) & 255] ^ crc >>> 8;
|
|
45781
|
+
return (crc ^ -1) >>> 0;
|
|
45782
|
+
}
|
|
45783
|
+
function chunk(type, data) {
|
|
45784
|
+
const length = Buffer.alloc(4);
|
|
45785
|
+
length.writeUInt32BE(data.length);
|
|
45786
|
+
const body2 = Buffer.concat([Buffer.from(type, "ascii"), data]);
|
|
45787
|
+
const crc = Buffer.alloc(4);
|
|
45788
|
+
crc.writeUInt32BE(crc32(body2));
|
|
45789
|
+
return Buffer.concat([length, body2, crc]);
|
|
45790
|
+
}
|
|
45791
|
+
function modules(seed2) {
|
|
45792
|
+
const bits = createHash("sha512").update(seed2).digest();
|
|
45793
|
+
const grid = Array.from(
|
|
45794
|
+
{ length: MODULES },
|
|
45795
|
+
(_, y) => Array.from({ length: MODULES }, (_2, x) => {
|
|
45796
|
+
const i = y * MODULES + x;
|
|
45797
|
+
return (bits[i % bits.length] >> i % 8 & 1) === 1;
|
|
45798
|
+
})
|
|
45799
|
+
);
|
|
45800
|
+
const finder = (ox, oy) => {
|
|
45801
|
+
for (let y = -1; y <= 7; y++) {
|
|
45802
|
+
for (let x = -1; x <= 7; x++) {
|
|
45803
|
+
const gx = ox + x;
|
|
45804
|
+
const gy = oy + y;
|
|
45805
|
+
if (gx < 0 || gy < 0 || gx >= MODULES || gy >= MODULES) continue;
|
|
45806
|
+
const ring = Math.max(Math.abs(x - 3), Math.abs(y - 3));
|
|
45807
|
+
grid[gy][gx] = ring !== 2 && ring <= 3;
|
|
45808
|
+
}
|
|
45809
|
+
}
|
|
45810
|
+
};
|
|
45811
|
+
finder(0, 0);
|
|
45812
|
+
finder(MODULES - 7, 0);
|
|
45813
|
+
finder(0, MODULES - 7);
|
|
45814
|
+
for (let i = 8; i < MODULES - 8; i++) {
|
|
45815
|
+
grid[6][i] = i % 2 === 0;
|
|
45816
|
+
grid[i][6] = i % 2 === 0;
|
|
45817
|
+
}
|
|
45818
|
+
return grid;
|
|
45819
|
+
}
|
|
45820
|
+
function fakeQrDataUri(seed2) {
|
|
45821
|
+
const grid = modules(seed2);
|
|
45822
|
+
const size = (MODULES + QUIET * 2) * SCALE;
|
|
45823
|
+
const raw = Buffer.alloc((size + 1) * size, 255);
|
|
45824
|
+
for (let y = 0; y < size; y++) {
|
|
45825
|
+
raw[y * (size + 1)] = 0;
|
|
45826
|
+
const my = Math.floor(y / SCALE) - QUIET;
|
|
45827
|
+
for (let x = 0; x < size; x++) {
|
|
45828
|
+
const mx = Math.floor(x / SCALE) - QUIET;
|
|
45829
|
+
const dark = my >= 0 && mx >= 0 && my < MODULES && mx < MODULES && grid[my][mx];
|
|
45830
|
+
if (dark) raw[y * (size + 1) + 1 + x] = 0;
|
|
45831
|
+
}
|
|
45832
|
+
}
|
|
45833
|
+
const header = Buffer.alloc(13);
|
|
45834
|
+
header.writeUInt32BE(size, 0);
|
|
45835
|
+
header.writeUInt32BE(size, 4);
|
|
45836
|
+
header[8] = 8;
|
|
45837
|
+
header[9] = 0;
|
|
45838
|
+
const png = Buffer.concat([
|
|
45839
|
+
Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
|
|
45840
|
+
chunk("IHDR", header),
|
|
45841
|
+
chunk("IDAT", deflateSync(raw)),
|
|
45842
|
+
chunk("IEND", Buffer.alloc(0))
|
|
45843
|
+
]);
|
|
45844
|
+
return `data:image/png;base64,${png.toString("base64")}`;
|
|
45845
|
+
}
|
|
45846
|
+
|
|
45847
|
+
// src/handlers/devices.ts
|
|
45848
|
+
var QR_EXPIRES_IN = 5;
|
|
45849
|
+
function deviceOf(app, id) {
|
|
45850
|
+
const device = app.store.devices.get(Number(id));
|
|
45851
|
+
if (!device) throw new RawApiError(404, { message: "Object not found" });
|
|
45852
|
+
return device;
|
|
45853
|
+
}
|
|
45854
|
+
var optionalText = (value) => value === void 0 || value === null ? null : String(value);
|
|
45855
|
+
var deviceHandlers = {
|
|
45856
|
+
// With an API key the list always paginates (accounts `PublicApiListings`: `perPage`, 25 by default, up to 100, by
|
|
45857
|
+
// id). `external_reference` filters by exact equality.
|
|
45858
|
+
"GET /api/v1/devices": (ctx) => {
|
|
45859
|
+
const reference = ctx.query.external_reference;
|
|
45860
|
+
const list = [...ctx.app.store.devices.values()].filter((d) => reference === void 0 || reference === "" || d.externalReference === String(reference)).sort((a, b) => a.id - b.id);
|
|
45861
|
+
return ok(paginate(list, listContext(ctx), deviceResource));
|
|
45862
|
+
},
|
|
45863
|
+
"GET /api/v1/devices/{device}": (ctx) => ok(deviceResource(deviceOf(ctx.app, ctx.path.device))),
|
|
45864
|
+
// What the sandbox keeps: the alias and the external reference. The rest of the body is accepted and ignored.
|
|
45865
|
+
"PUT /api/v1/devices/{device}": (ctx) => {
|
|
45866
|
+
const device = deviceOf(ctx.app, ctx.path.device);
|
|
45867
|
+
const b = body(ctx);
|
|
45868
|
+
if (typeof b.alias === "string" && b.alias.trim() !== "") device.alias = b.alias;
|
|
45869
|
+
if ("external_reference" in b) device.externalReference = optionalText(b.external_reference);
|
|
45870
|
+
return ok(deviceResource(device));
|
|
45871
|
+
},
|
|
45872
|
+
// `200` with an empty body, as today.
|
|
45873
|
+
"DELETE /api/v1/devices/{device}": (ctx) => {
|
|
45874
|
+
deleteDevice(ctx.app, deviceOf(ctx.app, ctx.path.device));
|
|
45875
|
+
return { status: 200 };
|
|
45876
|
+
},
|
|
45877
|
+
"POST /api/v1/devices": (ctx) => idempotent(ctx, "devices", () => {
|
|
45878
|
+
const b = body(ctx);
|
|
45879
|
+
const target = b.same_number_as === void 0 || b.same_number_as === null ? null : checkSameNumber(ctx.app, b.same_number_as);
|
|
45880
|
+
if (!target) {
|
|
45881
|
+
checkQuota(ctx.app);
|
|
45882
|
+
checkPendingNumber(ctx.app);
|
|
45883
|
+
}
|
|
45884
|
+
const device = createDevice(ctx.app, {
|
|
45885
|
+
connection: "qr",
|
|
45886
|
+
alias: optionalText(b.alias) ?? target?.alias ?? null,
|
|
45887
|
+
number: target?.number ?? null,
|
|
45888
|
+
externalReference: optionalText(b.external_reference) ?? target?.externalReference ?? null
|
|
45889
|
+
});
|
|
45890
|
+
return ok({ data: deviceResource(device) }, 201);
|
|
45891
|
+
}),
|
|
45892
|
+
// `Cache-Control: no-store`, as accounts: a QR read a few seconds ago may no longer link, so nothing may keep it.
|
|
45893
|
+
"GET /api/v1/devices/{device}/qr": (ctx) => {
|
|
45894
|
+
const device = deviceOf(ctx.app, ctx.path.device);
|
|
45895
|
+
if (device.kind !== "qr") throw new ApiError(422, "provider_unsupported", "El c\xF3digo QR solo existe para n\xFAmeros por QR.");
|
|
45896
|
+
return {
|
|
45897
|
+
status: 200,
|
|
45898
|
+
body: {
|
|
45899
|
+
data: {
|
|
45900
|
+
connected: device.connected,
|
|
45901
|
+
qr_code: device.connected ? null : fakeQrDataUri(`${device.id}:${Math.floor(Date.now() / (QR_EXPIRES_IN * 1e3))}`),
|
|
45902
|
+
expires_in: device.connected ? 0 : QR_EXPIRES_IN
|
|
45903
|
+
}
|
|
45904
|
+
},
|
|
45905
|
+
headers: { "Cache-Control": "no-store" }
|
|
45906
|
+
};
|
|
45907
|
+
}
|
|
45908
|
+
};
|
|
44714
45909
|
|
|
44715
45910
|
// src/messaging/lifecycle.ts
|
|
44716
45911
|
var ORDER = { sent: 1, delivered: 2, read: 3 };
|
|
@@ -45090,14 +46285,6 @@ var messageHandlers = {
|
|
|
45090
46285
|
if (template.deviceId !== Number(ctx.path.device)) throw notFound("La plantilla no es de este n\xFAmero.");
|
|
45091
46286
|
ctx.app.store.templates.delete(template.id);
|
|
45092
46287
|
return noContent();
|
|
45093
|
-
},
|
|
45094
|
-
// --- numbers (accounts) -----------------------------------------------------------------------------------
|
|
45095
|
-
// With an API key the list always paginates (accounts `PublicApiListings`: `perPage`, 25 by default, up to 100, by id).
|
|
45096
|
-
"GET /api/v1/devices": (ctx) => ok(paginate([...ctx.app.store.devices.values()].sort((a, b) => a.id - b.id), listContext(ctx), deviceResource)),
|
|
45097
|
-
"GET /api/v1/devices/{device}": (ctx) => {
|
|
45098
|
-
const device = ctx.app.store.devices.get(Number(ctx.path.device));
|
|
45099
|
-
if (!device) throw new RawApiError(404, { message: "Object not found" });
|
|
45100
|
-
return ok(deviceResource(device));
|
|
45101
46288
|
}
|
|
45102
46289
|
};
|
|
45103
46290
|
function setTemplateStatus(app, template, status, reason) {
|
|
@@ -45620,27 +46807,9 @@ var formHandlers = {
|
|
|
45620
46807
|
"POST /api/v1/contacts/{contact}/message/form": sendForm
|
|
45621
46808
|
};
|
|
45622
46809
|
|
|
45623
|
-
// src/handlers/
|
|
45624
|
-
var
|
|
45625
|
-
|
|
45626
|
-
"POST /api/v1/groups",
|
|
45627
|
-
"POST /api/v1/groups/{group}/participants",
|
|
45628
|
-
"POST /api/v1/groups/{group}/participants/remove",
|
|
45629
|
-
"POST /api/v1/groups/{group}/admins",
|
|
45630
|
-
"POST /api/v1/groups/{group}/admins/remove",
|
|
45631
|
-
"PUT /api/v1/groups/{group}/name",
|
|
45632
|
-
"PUT /api/v1/groups/{group}/description",
|
|
45633
|
-
"PUT /api/v1/groups/{group}/photo",
|
|
45634
|
-
"PUT /api/v1/groups/{group}/settings",
|
|
45635
|
-
"POST /api/v1/groups/{group}/invite-link/renew",
|
|
45636
|
-
"POST /api/v1/groups/{group}/messages",
|
|
45637
|
-
"GET /api/v1/groups/{group}/invite-link/live",
|
|
45638
|
-
"GET /api/v1/groups/{group}/join-requests",
|
|
45639
|
-
"POST /api/v1/groups/{group}/join-requests/approve",
|
|
45640
|
-
"POST /api/v1/groups/{group}/join-requests/reject"
|
|
45641
|
-
];
|
|
45642
|
-
var GATE_CLOSED = /* @__PURE__ */ new Set([...GROUP_ACTION_ROUTES, "POST /api/v1/contacts/{contact}/message/poll", "POST /api/v1/contacts/{contact}/message/event"]);
|
|
45643
|
-
var groupHandlers = Object.fromEntries(
|
|
46810
|
+
// src/handlers/gate.ts
|
|
46811
|
+
var GATE_CLOSED = /* @__PURE__ */ new Set(["POST /api/v1/contacts/{contact}/message/poll", "POST /api/v1/contacts/{contact}/message/event"]);
|
|
46812
|
+
var gateHandlers = Object.fromEntries(
|
|
45644
46813
|
[...GATE_CLOSED].map((route) => [
|
|
45645
46814
|
route,
|
|
45646
46815
|
() => {
|
|
@@ -45816,6 +46985,132 @@ function parseQuery(search) {
|
|
|
45816
46985
|
}
|
|
45817
46986
|
return out;
|
|
45818
46987
|
}
|
|
46988
|
+
function securityHeaders(nonce) {
|
|
46989
|
+
return {
|
|
46990
|
+
"Cache-Control": "no-store, private",
|
|
46991
|
+
"Referrer-Policy": "no-referrer",
|
|
46992
|
+
"X-Frame-Options": "DENY",
|
|
46993
|
+
"X-Robots-Tag": "noindex",
|
|
46994
|
+
"Strict-Transport-Security": "max-age=31536000",
|
|
46995
|
+
"Cross-Origin-Opener-Policy": "same-origin-allow-popups",
|
|
46996
|
+
"X-Content-Type-Options": "nosniff",
|
|
46997
|
+
"Content-Security-Policy": `default-src 'none'; style-src 'nonce-${nonce}'; script-src 'nonce-${nonce}'; form-action 'self' https:; frame-ancestors 'none'; base-uri 'none'`
|
|
46998
|
+
};
|
|
46999
|
+
}
|
|
47000
|
+
var PATH = /^\/connect\/([A-Za-z0-9_-]{1,200})(?:\/(start|complete|qr))?\/?$/;
|
|
47001
|
+
var isConnectPath = (pathname) => pathname === "/connect" || pathname.startsWith("/connect/");
|
|
47002
|
+
var escape = (text2) => text2.replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
|
|
47003
|
+
function page(status, title, text2, extra = "") {
|
|
47004
|
+
const nonce = randomBytes(16).toString("base64");
|
|
47005
|
+
const html = `<!doctype html>
|
|
47006
|
+
<html lang="en">
|
|
47007
|
+
<head>
|
|
47008
|
+
<meta charset="utf-8">
|
|
47009
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
47010
|
+
<meta name="robots" content="noindex">
|
|
47011
|
+
<title>${escape(title)} \xB7 Funnelchat</title>
|
|
47012
|
+
<style nonce="${nonce}">
|
|
47013
|
+
:root { --bg: #f6f7f9; --card: #fff; --text: #1d2433; --muted: #5b6475; --line: #dfe3ea; --accent: #0b6e4f; }
|
|
47014
|
+
@media (prefers-color-scheme: dark) { :root { --bg: #11151c; --card: #1a202a; --text: #e6e9ef; --muted: #9aa4b5; --line: #2b3340; --accent: #3fb68b; } }
|
|
47015
|
+
body { margin: 0; min-height: 100vh; display: grid; place-items: center; padding: 16px; box-sizing: border-box; font: 15px/1.5 system-ui, -apple-system, Segoe UI, sans-serif; background: var(--bg); color: var(--text); }
|
|
47016
|
+
main { max-width: 420px; background: var(--card); border: 1px solid var(--line); border-radius: 10px; padding: 24px; }
|
|
47017
|
+
h1 { font-size: 20px; margin: 0 0 8px; }
|
|
47018
|
+
p { margin: 0 0 8px; }
|
|
47019
|
+
label { display: block; margin: 6px 0; }
|
|
47020
|
+
button { margin: 8px 0; padding: 10px 16px; border: 0; border-radius: 6px; background: var(--accent); color: #fff; font: inherit; cursor: pointer; }
|
|
47021
|
+
small { color: var(--muted); }
|
|
47022
|
+
</style>
|
|
47023
|
+
</head>
|
|
47024
|
+
<body>
|
|
47025
|
+
<main>
|
|
47026
|
+
<h1>${escape(title)}</h1>
|
|
47027
|
+
<p>${escape(text2)}</p>
|
|
47028
|
+
${extra}<small>Funnelchat sandbox: a test page. Nothing is connected to WhatsApp.</small>
|
|
47029
|
+
</main>
|
|
47030
|
+
</body>
|
|
47031
|
+
</html>
|
|
47032
|
+
`;
|
|
47033
|
+
return { status, html, headers: securityHeaders(nonce) };
|
|
47034
|
+
}
|
|
47035
|
+
function json(status, body2) {
|
|
47036
|
+
return { status, body: body2, headers: securityHeaders(randomBytes(16).toString("base64")) };
|
|
47037
|
+
}
|
|
47038
|
+
function linkPage(token, link, again) {
|
|
47039
|
+
const choice = link.type === "meta" && (link.allowedConnectionTypes?.length ?? 0) > 1 ? (link.allowedConnectionTypes ?? []).map(
|
|
47040
|
+
(c, i) => `<label><input type="radio" name="connection" value="${c}"${i === 0 ? " checked" : ""}> ${c === "coexistence" ? "Keep using the WhatsApp Business app (coexistence)" : "A number only for the API"}</label>`
|
|
47041
|
+
).join("\n") : "";
|
|
47042
|
+
const answer = page(
|
|
47043
|
+
200,
|
|
47044
|
+
again?.title ?? (link.type === "qr" ? "Connect your WhatsApp by QR" : "Connect your WhatsApp Business number"),
|
|
47045
|
+
again?.text ?? "In the sandbox the button connects at once: there is no QR to scan and no Meta sign-up.",
|
|
47046
|
+
`<form method="post" action="/connect/${token}/start">
|
|
47047
|
+
${choice}
|
|
47048
|
+
<button type="submit">Connect</button>
|
|
47049
|
+
</form>
|
|
47050
|
+
`
|
|
47051
|
+
);
|
|
47052
|
+
const nonce = /'nonce-([^']+)'/.exec(answer.headers["Content-Security-Policy"])?.[1] ?? "";
|
|
47053
|
+
answer.html = answer.html.replace("</body>", `<script nonce="${nonce}">history.replaceState(null, '', '/connect/reopen');</script>
|
|
47054
|
+
</body>`);
|
|
47055
|
+
return answer;
|
|
47056
|
+
}
|
|
47057
|
+
function outcomeOf(input) {
|
|
47058
|
+
const code = typeof input.error_code === "string" && LINK_ERROR_CODES.includes(input.error_code) ? input.error_code : null;
|
|
47059
|
+
const connection = input.connection === "coexistence" || input.connection === "dedicated" ? input.connection : null;
|
|
47060
|
+
const number = input.number === void 0 ? null : digits(input.number) || null;
|
|
47061
|
+
return { outcome: input.outcome === "failed" ? "failed" : "completed", errorCode: code, connection, number };
|
|
47062
|
+
}
|
|
47063
|
+
function closed(link) {
|
|
47064
|
+
if (link.status === "expired") return page(410, "This link expired", "Ask whoever sent it for a new one.");
|
|
47065
|
+
if (link.status === "revoked") return page(410, "This link is no longer valid", "Ask whoever sent it for a new one.");
|
|
47066
|
+
return page(410, "This link was already used", "A sign-up link works only once. Ask whoever sent it for a new one.");
|
|
47067
|
+
}
|
|
47068
|
+
function handleConnectPage(app, method, pathname, headers, search, body2) {
|
|
47069
|
+
if (headers.authorization !== void 0 || headers["x-api-key"] !== void 0) {
|
|
47070
|
+
return json(403, { error: "credentials_not_allowed", message: "Esta p\xE1gina no acepta credenciales. Abre el link en un navegador, sin API key." });
|
|
47071
|
+
}
|
|
47072
|
+
if (pathname.replace(/\/+$/, "") === "/connect/reopen" && method === "GET") {
|
|
47073
|
+
return page(200, "Open the link again", "For your security the link left the address bar. Open it again from the message you received.");
|
|
47074
|
+
}
|
|
47075
|
+
const match = PATH.exec(pathname);
|
|
47076
|
+
if (!match || match[2] === "qr") return page(404, "This link does not exist", "Check that you copied the whole link.");
|
|
47077
|
+
const token = match[1];
|
|
47078
|
+
const action = match[2];
|
|
47079
|
+
const expected = action ? "POST" : "GET";
|
|
47080
|
+
if (method !== expected) {
|
|
47081
|
+
const answer = page(405, "Not allowed", "Open the link in a browser.");
|
|
47082
|
+
return { ...answer, headers: { ...answer.headers, Allow: expected } };
|
|
47083
|
+
}
|
|
47084
|
+
const link = findLinkByToken(app, token);
|
|
47085
|
+
if (!link) return page(404, "This link does not exist", "Check that you copied the whole link.");
|
|
47086
|
+
if (link.status !== "pending") return closed(link);
|
|
47087
|
+
if (link.type === "meta" && !app.store.numbers.metaConnect) {
|
|
47088
|
+
return page(503, "Not available yet", "Connecting official numbers by link is not available yet.");
|
|
47089
|
+
}
|
|
47090
|
+
if (!action) return linkPage(token, link);
|
|
47091
|
+
const input = { ...parseQuery(search), ...isRecord(body2) ? body2 : {} };
|
|
47092
|
+
const outcome = outcomeOf(input);
|
|
47093
|
+
if (!connectionAllowed(link, outcome.connection)) {
|
|
47094
|
+
return json(422, { error: "connection_type_not_allowed", message: "Este link no permite ese tipo de conexi\xF3n.", allowed_connection_types: link.allowedConnectionTypes });
|
|
47095
|
+
}
|
|
47096
|
+
if (completeLink(app, link, outcome) === "retry") {
|
|
47097
|
+
const left = META_ATTEMPTS - link.failedAttempts;
|
|
47098
|
+
const code = outcome.errorCode ?? "meta_error";
|
|
47099
|
+
app.logger.info(`sign-up link ${link.id}: ${code}, still pending (${left} attempts left)`);
|
|
47100
|
+
if ((headers["content-type"] ?? "").includes("json") || (headers.accept ?? "").includes("application/json")) {
|
|
47101
|
+
return json(200, { state: "retry", error_code: code, attempts_left: left });
|
|
47102
|
+
}
|
|
47103
|
+
return linkPage(token, link, { title: "That did not work", text: `Facebook or Meta answered ${code}. Try again: ${left} attempts left.` });
|
|
47104
|
+
}
|
|
47105
|
+
app.logger.info(`sign-up link ${link.id}: ${link.status}${link.errorCode ? ` (${link.errorCode})` : ""}${link.deviceId ? ` \u2192 number ${link.deviceId}` : ""}`);
|
|
47106
|
+
const back = returnUrlFor(link);
|
|
47107
|
+
if (back) {
|
|
47108
|
+
const { headers: pageHeaders } = page(303, "Done", "Going back.");
|
|
47109
|
+
return { status: 303, headers: { ...pageHeaders, Location: back } };
|
|
47110
|
+
}
|
|
47111
|
+
const status = link.status;
|
|
47112
|
+
return status === "completed" ? page(200, "Done: your number is connected", "You can close this page.") : page(200, "The number could not be connected", `Reason: ${link.errorCode ?? "unknown"}. Ask whoever sent the link for a new one.`);
|
|
47113
|
+
}
|
|
45819
47114
|
|
|
45820
47115
|
// src/sandbox/panel.ts
|
|
45821
47116
|
function panelHtml() {
|
|
@@ -45921,18 +47216,34 @@ function contactOf(app, id) {
|
|
|
45921
47216
|
if (!contact) throw notFound("The contact does not exist in the sandbox.");
|
|
45922
47217
|
return contact;
|
|
45923
47218
|
}
|
|
45924
|
-
function
|
|
47219
|
+
function deviceOf2(app, id) {
|
|
45925
47220
|
const device = app.store.devices.get(Number(id ?? 3));
|
|
45926
47221
|
if (!device) throw fieldError("device_id", `There is no number ${id} in the sandbox: the example ones are 3 (official) and 4 (QR).`);
|
|
45927
47222
|
return device;
|
|
45928
47223
|
}
|
|
45929
47224
|
function emitted(result) {
|
|
45930
|
-
return { id: result.envelope.id, type: result.envelope.type, subscriptions: result.subscriptions };
|
|
47225
|
+
return result ? { id: result.envelope.id, type: result.envelope.type, subscriptions: result.subscriptions } : null;
|
|
47226
|
+
}
|
|
47227
|
+
function numbersState(app) {
|
|
47228
|
+
const { numbers: numbers2 } = app.store;
|
|
47229
|
+
return {
|
|
47230
|
+
limit: numbers2.limit,
|
|
47231
|
+
in_use: placesInUse(app),
|
|
47232
|
+
pending_device_ids: pendingQrIds(app),
|
|
47233
|
+
included: numbers2.included,
|
|
47234
|
+
trial: numbers2.trial,
|
|
47235
|
+
pool_limit: poolLimit(app),
|
|
47236
|
+
meta_connect: numbers2.metaConnect,
|
|
47237
|
+
links_per_minute: numbers2.linksPerMinute
|
|
47238
|
+
};
|
|
45931
47239
|
}
|
|
45932
47240
|
function state(app) {
|
|
45933
47241
|
const { store, config } = app;
|
|
45934
47242
|
return {
|
|
45935
47243
|
devices: [...store.devices.values()].map(deviceResource),
|
|
47244
|
+
numbers: numbersState(app),
|
|
47245
|
+
// Never the tokens: they are only in the `url` of the create answer.
|
|
47246
|
+
connect_links: [...store.connectLinks.values()].map((link) => connectLinkResource(refreshLink(link))),
|
|
45936
47247
|
counts: {
|
|
45937
47248
|
contacts: store.contacts.size,
|
|
45938
47249
|
messages: store.messages.size,
|
|
@@ -45980,7 +47291,7 @@ function limitOf2(query) {
|
|
|
45980
47291
|
}
|
|
45981
47292
|
function inboundMessage(app, req) {
|
|
45982
47293
|
const b = obj(req.body);
|
|
45983
|
-
const device =
|
|
47294
|
+
const device = deviceOf2(app, b.device_id);
|
|
45984
47295
|
let contact;
|
|
45985
47296
|
if (b.contact_id !== void 0) contact = contactOf(app, b.contact_id);
|
|
45986
47297
|
else {
|
|
@@ -46198,7 +47509,7 @@ var ROUTES = [
|
|
|
46198
47509
|
handle: (app, req) => {
|
|
46199
47510
|
const b = obj(req.body);
|
|
46200
47511
|
const contact = contactOf(app, b.contact_id);
|
|
46201
|
-
const device =
|
|
47512
|
+
const device = deviceOf2(app, b.device_id);
|
|
46202
47513
|
if (typeof b.open !== "boolean") throw fieldError("open", "open must be true or false.");
|
|
46203
47514
|
const key = `${contact.id}:${device.id}`;
|
|
46204
47515
|
if (b.open) app.store.lastInbound.set(key, /* @__PURE__ */ new Date());
|
|
@@ -46207,17 +47518,52 @@ var ROUTES = [
|
|
|
46207
47518
|
}
|
|
46208
47519
|
},
|
|
46209
47520
|
{
|
|
47521
|
+
// `connect` is the scan of the QR (or the end of the Meta sign-up): it takes `number`, a fake one otherwise.
|
|
47522
|
+
// `disconnect` takes `reason` (qr_logged_out for QR and unknown for official numbers by default).
|
|
46210
47523
|
method: "POST",
|
|
46211
47524
|
pattern: /^\/_sandbox\/devices\/(\d+)\/(connect|disconnect)$/,
|
|
46212
|
-
handle: (app,
|
|
46213
|
-
const device =
|
|
46214
|
-
const
|
|
46215
|
-
if (
|
|
46216
|
-
|
|
46217
|
-
|
|
46218
|
-
|
|
46219
|
-
|
|
46220
|
-
|
|
47525
|
+
handle: (app, req, [id, action]) => {
|
|
47526
|
+
const device = deviceOf2(app, id);
|
|
47527
|
+
const b = obj(req.body);
|
|
47528
|
+
if (action === "connect") {
|
|
47529
|
+
const number = b.number === void 0 || b.number === null ? null : digits(b.number);
|
|
47530
|
+
if (number !== null && number.length < 8) throw fieldError("number", "number must be a phone, digits only.");
|
|
47531
|
+
return { status: 200, body: { device: deviceResource(device), event: emitted(connectDevice(app, device, { number })) } };
|
|
47532
|
+
}
|
|
47533
|
+
const reason = b.reason ?? (device.kind === "qr" ? "qr_logged_out" : "unknown");
|
|
47534
|
+
if (!DISCONNECT_REASONS.includes(reason)) throw fieldError("reason", `reason must be one of: ${DISCONNECT_REASONS.join(", ")}.`);
|
|
47535
|
+
return { status: 200, body: { device: deviceResource(device), event: emitted(disconnectDevice(app, device, reason)) } };
|
|
47536
|
+
}
|
|
47537
|
+
},
|
|
47538
|
+
{
|
|
47539
|
+
method: "POST",
|
|
47540
|
+
pattern: /^\/_sandbox\/devices\/(\d+)\/health$/,
|
|
47541
|
+
handle: (app, req, [id]) => {
|
|
47542
|
+
const device = deviceOf2(app, id);
|
|
47543
|
+
if (device.kind !== "cloud") throw fieldError("device_id", "Only official numbers have a health state.");
|
|
47544
|
+
const state2 = obj(req.body).state;
|
|
47545
|
+
if (!HEALTH_STATES.includes(state2)) throw fieldError("state", `state must be one of: ${HEALTH_STATES.join(", ")}.`);
|
|
47546
|
+
return { status: 200, body: { device: deviceResource(device), event: emitted(setHealth(app, device, state2)) } };
|
|
47547
|
+
}
|
|
47548
|
+
},
|
|
47549
|
+
{ method: "GET", pattern: /^\/_sandbox\/numbers$/, handle: (app) => ({ status: 200, body: numbersState(app) }) },
|
|
47550
|
+
{
|
|
47551
|
+
method: "POST",
|
|
47552
|
+
pattern: /^\/_sandbox\/numbers$/,
|
|
47553
|
+
handle: (app, req) => {
|
|
47554
|
+
const b = obj(req.body);
|
|
47555
|
+
const { numbers: numbers2 } = app.store;
|
|
47556
|
+
for (const [field, key] of [["limit", "limit"], ["links_per_minute", "linksPerMinute"]]) {
|
|
47557
|
+
if (b[field] === void 0) continue;
|
|
47558
|
+
if (!Number.isInteger(b[field]) || b[field] < 0) throw fieldError(field, `${field} must be an integer, 0 or more.`);
|
|
47559
|
+
numbers2[key] = b[field];
|
|
47560
|
+
}
|
|
47561
|
+
for (const [field, key] of [["included", "included"], ["trial", "trial"], ["meta_connect", "metaConnect"]]) {
|
|
47562
|
+
if (b[field] === void 0) continue;
|
|
47563
|
+
if (typeof b[field] !== "boolean") throw fieldError(field, `${field} must be true or false.`);
|
|
47564
|
+
numbers2[key] = b[field];
|
|
47565
|
+
}
|
|
47566
|
+
return { status: 200, body: numbersState(app) };
|
|
46221
47567
|
}
|
|
46222
47568
|
},
|
|
46223
47569
|
{
|
|
@@ -46248,16 +47594,24 @@ async function handleSandbox(app, req) {
|
|
|
46248
47594
|
// src/server.ts
|
|
46249
47595
|
var MAX_BODY_BYTES = 1024 * 1024;
|
|
46250
47596
|
var API_KEY = /^Bearer\s+(\d+)\|fc_live_[A-Za-z0-9]+$/;
|
|
46251
|
-
var handlers = {
|
|
47597
|
+
var handlers = {
|
|
47598
|
+
...contactHandlers,
|
|
47599
|
+
...messageHandlers,
|
|
47600
|
+
...deviceHandlers,
|
|
47601
|
+
...connectLinkHandlers,
|
|
47602
|
+
...formHandlers,
|
|
47603
|
+
...gateHandlers,
|
|
47604
|
+
...webhookHandlers
|
|
47605
|
+
};
|
|
46252
47606
|
var BodyError = class extends ApiError {
|
|
46253
47607
|
};
|
|
46254
47608
|
async function readBody(req) {
|
|
46255
47609
|
const chunks = [];
|
|
46256
47610
|
let size = 0;
|
|
46257
|
-
for await (const
|
|
46258
|
-
size +=
|
|
47611
|
+
for await (const chunk2 of req) {
|
|
47612
|
+
size += chunk2.length;
|
|
46259
47613
|
if (size > MAX_BODY_BYTES) throw new BodyError(413, "payload_too_large", "El cuerpo supera 1 MB.");
|
|
46260
|
-
chunks.push(
|
|
47614
|
+
chunks.push(chunk2);
|
|
46261
47615
|
}
|
|
46262
47616
|
return Buffer.concat(chunks);
|
|
46263
47617
|
}
|
|
@@ -46335,7 +47689,7 @@ function createSandbox(options = {}) {
|
|
|
46335
47689
|
const validation = validateRequest(op.operation, match.params, parseQuery(url.searchParams), requestBody, requestBodySchema(op.operation));
|
|
46336
47690
|
if (!validation.ok) {
|
|
46337
47691
|
if (validation.kind === "not_found") throw notFound();
|
|
46338
|
-
throw validationFailed(validation.errors);
|
|
47692
|
+
throw op.operation["x-fc-validation-error"] === "invalid_request" ? invalidRequest(validation.errors) : validationFailed(validation.errors);
|
|
46339
47693
|
}
|
|
46340
47694
|
const handler = handlers[`${op.method.toUpperCase()} ${op.path}`];
|
|
46341
47695
|
const ctx = {
|
|
@@ -46383,6 +47737,25 @@ function createSandbox(options = {}) {
|
|
|
46383
47737
|
}
|
|
46384
47738
|
return;
|
|
46385
47739
|
}
|
|
47740
|
+
if (isConnectPath(url.pathname)) {
|
|
47741
|
+
try {
|
|
47742
|
+
const body2 = method === "POST" ? parseBody(await readBody(req), headers["content-type"]) : void 0;
|
|
47743
|
+
const answer = handleConnectPage(app, method, url.pathname, headers, url.searchParams, body2);
|
|
47744
|
+
if (answer.body !== void 0) {
|
|
47745
|
+
send2(res, requestId, answer);
|
|
47746
|
+
} else {
|
|
47747
|
+
const pageHeaders = { "X-Funnelchat-Sandbox": VERSION, ...answer.headers, [REQUEST_ID_HEADER]: requestId };
|
|
47748
|
+
if (answer.html !== void 0) pageHeaders["Content-Type"] = "text/html; charset=utf-8";
|
|
47749
|
+
res.writeHead(answer.status, pageHeaders);
|
|
47750
|
+
res.end(answer.html);
|
|
47751
|
+
}
|
|
47752
|
+
} catch (error) {
|
|
47753
|
+
const result2 = errorResult(error);
|
|
47754
|
+
send2(res, requestId, { ...result2, headers: { ...result2.headers, "Referrer-Policy": "no-referrer" } });
|
|
47755
|
+
}
|
|
47756
|
+
app.logger.info(`${method} /connect/*** \u2192 ${res.statusCode} (${Date.now() - started} ms)`);
|
|
47757
|
+
return;
|
|
47758
|
+
}
|
|
46386
47759
|
if (url.pathname === "/" && method === "GET") {
|
|
46387
47760
|
send2(res, requestId, {
|
|
46388
47761
|
status: 200,
|
|
@@ -46421,7 +47794,8 @@ function createSandbox(options = {}) {
|
|
|
46421
47794
|
server.off("error", reject);
|
|
46422
47795
|
const { port } = server.address();
|
|
46423
47796
|
const host = config.host === "0.0.0.0" || config.host === "::" ? "localhost" : config.host;
|
|
46424
|
-
|
|
47797
|
+
app.listenUrl = `http://${host}:${port}`;
|
|
47798
|
+
resolve2({ url: app.listenUrl, port });
|
|
46425
47799
|
});
|
|
46426
47800
|
}),
|
|
46427
47801
|
close: () => new Promise((resolve2) => {
|
|
@@ -46433,5 +47807,5 @@ function createSandbox(options = {}) {
|
|
|
46433
47807
|
}
|
|
46434
47808
|
|
|
46435
47809
|
export { ConfigError, DEFAULT_CONFIG_FILE, DEFAULT_PORT, 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, parseArgs, resolveConfig, signature };
|
|
46436
|
-
//# sourceMappingURL=chunk-
|
|
46437
|
-
//# sourceMappingURL=chunk-
|
|
47810
|
+
//# sourceMappingURL=chunk-M5E6NAXM.js.map
|
|
47811
|
+
//# sourceMappingURL=chunk-M5E6NAXM.js.map
|