@wokuapp/sdk 0.3.0 → 0.5.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 +211 -2
- package/dist/index.cjs +329 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1647 -74
- package/dist/index.d.ts +1647 -74
- package/dist/index.js +325 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,6 +28,97 @@ support tickets, delivery tracking and survey sends over the public `/v1` API.
|
|
|
28
28
|
> **Server-only.** The secret key grants full management access, so the SDK
|
|
29
29
|
> refuses to run in a browser. Never ship it to a client bundle.
|
|
30
30
|
|
|
31
|
+
## Proposed follow-up support (unreleased)
|
|
32
|
+
|
|
33
|
+
The next release adds `woku.followUps.get(id)` and typed `Ticket.followUp`,
|
|
34
|
+
`Ticket.contentVersion` and conversation origin. Deploy the matching API before
|
|
35
|
+
using this method. It reads `GET /v1/follow-ups/:id` with your management secret
|
|
36
|
+
and returns the conversation from the API's `data` envelope.
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
const ticket = await woku.tickets.get('ticket_123');
|
|
40
|
+
if (ticket.followUp?.conversationId) {
|
|
41
|
+
const conversation = await woku.followUps.get(ticket.followUp.conversationId);
|
|
42
|
+
console.log(conversation.origin, conversation.messages);
|
|
43
|
+
}
|
|
44
|
+
const enriched = (ticket.followUp?.appliedSummaryVersion ?? 0) >= 1;
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`completed` means the conversation ended; `appliedSummaryVersion` indicates
|
|
48
|
+
that its evidence reached the ticket. `availableSummaryVersion` may advance
|
|
49
|
+
before it is applied. Historical tickets may omit these fields. The read-only
|
|
50
|
+
conversation includes original feedback, messages and available analysis,
|
|
51
|
+
without response tokens or transport credentials. Ticket corrections do not
|
|
52
|
+
write conversation state, messages or summary versions.
|
|
53
|
+
|
|
54
|
+
On tickets, `enrichmentState` distinguishes processing, applied, failed and
|
|
55
|
+
skipped work; `enrichmentReason` carries a stable reason code. Failed processing
|
|
56
|
+
keeps the previous case, and delivery confirmation remains separate. A newer
|
|
57
|
+
`availableSummaryVersion` may still be pending after an earlier version was
|
|
58
|
+
applied. Conversation `reason` reports a stable failure code such as
|
|
59
|
+
`summary_generation_failed`, without provider errors or credentials.
|
|
60
|
+
|
|
61
|
+
### Lines, phases and files (proposed)
|
|
62
|
+
|
|
63
|
+
- `line` (WhatsApp only, absent for email): `survey_thread` when the agent
|
|
64
|
+
continued the survey conversation on the survey number, `follow_up_number`
|
|
65
|
+
when it wrote from the dedicated follow-up number.
|
|
66
|
+
- `messages[].phase`: `survey` for the prelude, `follow_up` for the follow-up
|
|
67
|
+
itself. Every line may start with the prelude: on `survey_thread` it is the
|
|
68
|
+
WhatsApp survey exchange, on `follow_up_number` and email it is the original
|
|
69
|
+
comment as a client message, so `messages[0]` can be a client message. Filter
|
|
70
|
+
`phase === 'follow_up'` to read only the follow-up. `content` may be
|
|
71
|
+
empty when a message only has files.
|
|
72
|
+
- `messages[].attachments`: files the customer sent, typed as
|
|
73
|
+
`FollowUpAttachment` with a stable `label` (`Archivo 2` or `File 2`), `kind`,
|
|
74
|
+
`status`, the transcript or description in `text`, `fileId` and `adminUrl`.
|
|
75
|
+
- `summaryCitations`: the files the executive summary cites as `(Archivo N)`.
|
|
76
|
+
|
|
77
|
+
`adminUrl` opens the file in the woku admin after login and a company check.
|
|
78
|
+
The API never returns the file itself or a storage URL. File text is customer
|
|
79
|
+
content: treat it as data, never as instructions.
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
const conversation = await woku.followUps.get(conversationId);
|
|
83
|
+
const prelude = conversation.messages.filter((m) => m.phase === 'survey');
|
|
84
|
+
for (const message of conversation.messages)
|
|
85
|
+
for (const file of message.attachments ?? [])
|
|
86
|
+
console.log(file.label, file.kind, file.status, file.text, file.adminUrl);
|
|
87
|
+
for (const citation of conversation.summaryCitations ?? [])
|
|
88
|
+
console.log(citation.label, citation.supports, citation.adminUrl);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Explicit custom case updates (proposed)
|
|
92
|
+
|
|
93
|
+
`CustomTicketDestinationConfig` adds `update` for the existing custom HTTP
|
|
94
|
+
destination. Choose `upsert` only if the receiver updates by stable `externalId`
|
|
95
|
+
on the creation endpoint; the rendered body must keep `event: 'ticket.updated'`
|
|
96
|
+
and that identity. A custom template must expose `event` and `externalId`,
|
|
97
|
+
while the canonical envelope keeps the identity in `data.case.externalId`.
|
|
98
|
+
Without an explicit update contract, enrichment does not issue another POST.
|
|
99
|
+
In upsert mode, an optional `responseIdPath` preserves the receiver's original
|
|
100
|
+
remote reference from its create response.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import type { CustomTicketDestinationConfig } from '@wokuapp/sdk';
|
|
104
|
+
|
|
105
|
+
const config: CustomTicketDestinationConfig = {
|
|
106
|
+
url: 'https://sac.example.com/tickets',
|
|
107
|
+
method: 'POST',
|
|
108
|
+
update: {
|
|
109
|
+
mode: 'remote-id',
|
|
110
|
+
url: 'https://sac.example.com/tickets/{{remoteId}}',
|
|
111
|
+
method: 'PATCH',
|
|
112
|
+
responseIdPath: 'result.id',
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`remote-id` extracts a scalar ID from the create response and requires a URL on
|
|
118
|
+
the same origin containing `{{remoteId}}`; updates use PUT or PATCH (default).
|
|
119
|
+
URLs cannot contain credentials or fragments. A safe dotted `responseIdPath`
|
|
120
|
+
is required. Existing destination authentication stays in `credentials`.
|
|
121
|
+
|
|
31
122
|
## Install
|
|
32
123
|
|
|
33
124
|
```bash
|
|
@@ -222,9 +313,127 @@ available if re-enabled later.
|
|
|
222
313
|
Existing definitions retain their contract; create a new v2 journey to adopt these
|
|
223
314
|
rules. Current participations retain their original definition version.
|
|
224
315
|
|
|
316
|
+
### Flows
|
|
317
|
+
|
|
318
|
+
A flow is an ordered sequence of steps, each a woku or an NPS, CSAT or CES tool,
|
|
319
|
+
that a respondent answers one after the other. Flows live outside folders.
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
const flow = await woku.flows.create({
|
|
323
|
+
name: 'Post-purchase',
|
|
324
|
+
steps: [
|
|
325
|
+
{ type: 'nps', toolId: 'nps_tool_id' },
|
|
326
|
+
{ type: 'csat', toolId: 'csat_tool_id' },
|
|
327
|
+
{ type: 'woku', toolId: 'woku_id' },
|
|
328
|
+
],
|
|
329
|
+
});
|
|
330
|
+
await woku.flows.update(flow._id, {
|
|
331
|
+
steps: [{ type: 'csat', toolId: 'csat_tool_id' }],
|
|
332
|
+
});
|
|
333
|
+
for await (const f of await woku.flows.list())
|
|
334
|
+
console.log(f.name, f.steps.length);
|
|
335
|
+
await woku.flows.delete(flow._id); // its tools and responses stay
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Use 1 to 20 steps, each tool once and all of your company. For an `nps` step,
|
|
339
|
+
your company id as `toolId` means the company-level NPS. `flows.get(id)` returns
|
|
340
|
+
the flow data (steps with their tools, wokus and branding).
|
|
341
|
+
|
|
342
|
+
### Folders and tools
|
|
343
|
+
|
|
344
|
+
One folder tree holds wokus and NPS, CSAT and CES tools together. Create folders,
|
|
345
|
+
find any tool, read figures per type and move things in batches:
|
|
346
|
+
|
|
347
|
+
```ts
|
|
348
|
+
const folder = await woku.folders.create({ name: 'Post-sale' });
|
|
349
|
+
|
|
350
|
+
// Create a tool already inside a folder.
|
|
351
|
+
await woku.csatTools.create({
|
|
352
|
+
name: 'Delivery',
|
|
353
|
+
question: 'How satisfied are you with your delivery?',
|
|
354
|
+
folderId: folder.id,
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// One list for every tool type. Iterate across pages with the cursor.
|
|
358
|
+
const page = await woku.tools.list({
|
|
359
|
+
folderId: folder.id,
|
|
360
|
+
types: ['csat', 'nps'],
|
|
361
|
+
});
|
|
362
|
+
for await (const tool of page) {
|
|
363
|
+
console.log(tool.type, tool.name, tool.reviewCount, tool.headline.value);
|
|
364
|
+
}
|
|
365
|
+
const found = await woku.tools.list({ q: 'delivery', scope: 'company' });
|
|
366
|
+
// Tracker chips (`name:value`): a tool matches when it has every one. Company-wide.
|
|
367
|
+
const tagged = await woku.tools.list({ trackers: ['TRR:00482'] });
|
|
368
|
+
|
|
369
|
+
// "See more" for folders: foldersLimit pages the folders of the level (omit it
|
|
370
|
+
// to receive all of them). The tools keep their own cursor.
|
|
371
|
+
const first = await woku.tools.list({ foldersLimit: 12 });
|
|
372
|
+
if (first.hasMoreFolders()) {
|
|
373
|
+
const more = await first.getMoreFolders(); // only the next folders, no tools
|
|
374
|
+
console.log(more.folders.length);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Headline figures per type (native scales, never mixed), company or folder.
|
|
378
|
+
const stats = await woku.tools.stats({ folderId: folder.id });
|
|
379
|
+
|
|
380
|
+
// Move items of mixed types, or send them to the root with folderId: null.
|
|
381
|
+
await woku.tools.move({
|
|
382
|
+
items: [
|
|
383
|
+
{ type: 'csat', id: 'csat_tool_id' },
|
|
384
|
+
{ type: 'woku', id: 'woku_id' },
|
|
385
|
+
],
|
|
386
|
+
folderId: folder.id,
|
|
387
|
+
});
|
|
388
|
+
await woku.npsTools.move('nps_tool_id', { folderId: null });
|
|
389
|
+
|
|
390
|
+
// AI report and CSV over every tool of the folder and its subfolders.
|
|
391
|
+
const report = await woku.folders.report(folder.id, { lang: 'en' });
|
|
392
|
+
const csv: string = await woku.folders.csv(folder.id);
|
|
393
|
+
// PDF of that report with a breakdown per type (bytes, e.g. write them to a file).
|
|
394
|
+
const pdf: Uint8Array = await woku.folders.pdf(folder.id, { lang: 'en' });
|
|
395
|
+
|
|
396
|
+
// Per-tool figures for a period against the one before it (7, 30 or 90 days, or
|
|
397
|
+
// 0 for the whole history), with the daily sums and the histogram of scores.
|
|
398
|
+
// Every type keeps its own scale: NPS -100..100, CSAT %, CES and stars 1-5.
|
|
399
|
+
const metrics = await woku.folders.metrics(folder.id, { days: 30 });
|
|
400
|
+
for (const tool of metrics.tools) {
|
|
401
|
+
console.log(
|
|
402
|
+
tool.name,
|
|
403
|
+
tool.current.headline.value,
|
|
404
|
+
tool.previous.headline.value,
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// WhatsApp numbers alerted when a comment on any tool of the folder is an
|
|
409
|
+
// improvement (wokus, NPS, CSAT and CES). The list replaces the previous one.
|
|
410
|
+
await woku.folders.update(folder.id, {
|
|
411
|
+
whatsappAlertRecipients: [{ whatsappId: '56912345678', name: 'Ana' }],
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Deleting a folder never deletes its content: it moves up to the parent.
|
|
415
|
+
await woku.folders.delete(folder.id);
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
```ts
|
|
419
|
+
// Keep customer journeys in folders: Viajes > journey > moment.
|
|
420
|
+
const status = await woku.journeyFolders.status(); // { enabled, organizing, pending: { journeys, tools }, ... }
|
|
421
|
+
if (!status.enabled) await woku.journeyFolders.organize(); // 202; rejects with 409 while a pass is running
|
|
422
|
+
|
|
423
|
+
const journey = await woku.journeys.get('journey_id');
|
|
424
|
+
console.log(journey.folderId, journey.momentFolders); // moment key -> folder id
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Folders created by woku (journeys, moments, integrations) report their `origin`
|
|
428
|
+
(`'journeys-root' | 'journey' | 'journey-moment' | 'integration'`); your own folders have `origin: null`.
|
|
429
|
+
|
|
430
|
+
`woku.folders` also has `list`, `get`, `update` and `move`. NPS, CSAT and CES
|
|
431
|
+
tools (`npsTools`, `csatTools`, `cesTools`) accept `folderId` on `create`, expose
|
|
432
|
+
`folderId` on every tool and have `move(id, { folderId })`.
|
|
433
|
+
|
|
225
434
|
## Pagination
|
|
226
435
|
|
|
227
|
-
Paginated resource methods return a `Page` (journey enrollments use
|
|
436
|
+
Paginated resource methods return a `Page` (journey enrollments and `tools.list` use a cursor envelope). Iterate every item across pages, or walk pages:
|
|
228
437
|
|
|
229
438
|
```ts
|
|
230
439
|
for await (const ticket of await woku.tickets.list({ severity: 'high' })) {
|
|
@@ -285,7 +494,7 @@ await woku.npsTools.create(body, { idempotencyKey: 'my-key' });
|
|
|
285
494
|
`trackers`, `npsTools` / `csatTools` / `cesTools`, `nps` / `csat` / `ces`,
|
|
286
495
|
`wokus`, `forms`, `flows`, `actionPlans`, `actionPlanGroups`, `tickets`,
|
|
287
496
|
`ticketDestinations`, `dispatches`, `reports`, `company`, `quarantines`,
|
|
288
|
-
`journeys`.
|
|
497
|
+
`journeys`, `folders`, `tools`, `journeyFolders`.
|
|
289
498
|
|
|
290
499
|
## License
|
|
291
500
|
|
package/dist/index.cjs
CHANGED
|
@@ -22,8 +22,23 @@ var Media = class {
|
|
|
22
22
|
}
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
// src/resources/follow-ups.ts
|
|
26
|
+
var FollowUps = class {
|
|
27
|
+
constructor(client) {
|
|
28
|
+
this.client = client;
|
|
29
|
+
}
|
|
30
|
+
async get(id, opts) {
|
|
31
|
+
const result = await this.client.request(
|
|
32
|
+
"get",
|
|
33
|
+
`/v1/follow-ups/${encodeURIComponent(id)}`,
|
|
34
|
+
opts
|
|
35
|
+
);
|
|
36
|
+
return result.data;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
25
40
|
// package.json
|
|
26
|
-
var version = "0.
|
|
41
|
+
var version = "0.5.0";
|
|
27
42
|
|
|
28
43
|
// src/core/errors.ts
|
|
29
44
|
var WokuError = class extends Error {
|
|
@@ -370,7 +385,8 @@ var WokuClient = class {
|
|
|
370
385
|
headers,
|
|
371
386
|
bodyText,
|
|
372
387
|
timeout,
|
|
373
|
-
args.signal
|
|
388
|
+
args.signal,
|
|
389
|
+
args.binary
|
|
374
390
|
);
|
|
375
391
|
if (status >= 200 && status < 300) {
|
|
376
392
|
return body;
|
|
@@ -487,7 +503,7 @@ var WokuClient = class {
|
|
|
487
503
|
}
|
|
488
504
|
return headers;
|
|
489
505
|
}
|
|
490
|
-
async send(url, method, headers, body, timeout, signal) {
|
|
506
|
+
async send(url, method, headers, body, timeout, signal, binary) {
|
|
491
507
|
if (signal?.aborted)
|
|
492
508
|
throw new WokuConnectionError("Request aborted", { code: "aborted" });
|
|
493
509
|
const controller = new AbortController();
|
|
@@ -508,6 +524,20 @@ var WokuClient = class {
|
|
|
508
524
|
body,
|
|
509
525
|
signal: controller.signal
|
|
510
526
|
});
|
|
527
|
+
if (binary && response.status >= 200 && response.status < 300) {
|
|
528
|
+
if (!response.arrayBuffer) {
|
|
529
|
+
throw new WokuError(
|
|
530
|
+
"The fetch implementation cannot read binary responses.",
|
|
531
|
+
{ code: "config_error" }
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
return {
|
|
535
|
+
status: response.status,
|
|
536
|
+
body: new Uint8Array(await response.arrayBuffer()),
|
|
537
|
+
requestId: requestIdFrom(response.headers),
|
|
538
|
+
retryAfterSeconds: retryAfterFromHeaders(response.headers)
|
|
539
|
+
};
|
|
540
|
+
}
|
|
511
541
|
const text = await response.text();
|
|
512
542
|
return {
|
|
513
543
|
status: response.status,
|
|
@@ -706,6 +736,14 @@ var NpsTools = class {
|
|
|
706
736
|
}
|
|
707
737
|
);
|
|
708
738
|
}
|
|
739
|
+
/** Move the tool into a folder, or to the root with `folderId: null`. */
|
|
740
|
+
move(id, body, opts) {
|
|
741
|
+
return this.client.request(
|
|
742
|
+
"patch",
|
|
743
|
+
`/v1/nps-tools/${encodeURIComponent(id)}/move`,
|
|
744
|
+
{ ...opts, body }
|
|
745
|
+
);
|
|
746
|
+
}
|
|
709
747
|
delete(id, opts) {
|
|
710
748
|
return this.client.request(
|
|
711
749
|
"delete",
|
|
@@ -745,6 +783,14 @@ var CsatTools = class {
|
|
|
745
783
|
}
|
|
746
784
|
);
|
|
747
785
|
}
|
|
786
|
+
/** Move the tool into a folder, or to the root with `folderId: null`. */
|
|
787
|
+
move(id, body, opts) {
|
|
788
|
+
return this.client.request(
|
|
789
|
+
"patch",
|
|
790
|
+
`/v1/csat-tools/${encodeURIComponent(id)}/move`,
|
|
791
|
+
{ ...opts, body }
|
|
792
|
+
);
|
|
793
|
+
}
|
|
748
794
|
delete(id, opts) {
|
|
749
795
|
return this.client.request(
|
|
750
796
|
"delete",
|
|
@@ -784,6 +830,14 @@ var CesTools = class {
|
|
|
784
830
|
}
|
|
785
831
|
);
|
|
786
832
|
}
|
|
833
|
+
/** Move the tool into a folder, or to the root with `folderId: null`. */
|
|
834
|
+
move(id, body, opts) {
|
|
835
|
+
return this.client.request(
|
|
836
|
+
"patch",
|
|
837
|
+
`/v1/ces-tools/${encodeURIComponent(id)}/move`,
|
|
838
|
+
{ ...opts, body }
|
|
839
|
+
);
|
|
840
|
+
}
|
|
787
841
|
delete(id, opts) {
|
|
788
842
|
return this.client.request(
|
|
789
843
|
"delete",
|
|
@@ -1001,9 +1055,11 @@ var Flows = class {
|
|
|
1001
1055
|
constructor(client) {
|
|
1002
1056
|
this.client = client;
|
|
1003
1057
|
}
|
|
1058
|
+
/** List the flows of the company, with their ordered `steps`. */
|
|
1004
1059
|
list(params, opts) {
|
|
1005
1060
|
return this.client.getPage("/v1/flows", params, opts);
|
|
1006
1061
|
}
|
|
1062
|
+
/** The data of a flow: ordered steps with their tools, wokus and branding. */
|
|
1007
1063
|
get(id, opts) {
|
|
1008
1064
|
return this.client.request(
|
|
1009
1065
|
"get",
|
|
@@ -1011,6 +1067,33 @@ var Flows = class {
|
|
|
1011
1067
|
opts
|
|
1012
1068
|
);
|
|
1013
1069
|
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Create a flow from 1 to 20 ordered steps (each tool once, all of your
|
|
1072
|
+
* company). Flows live outside folders.
|
|
1073
|
+
*/
|
|
1074
|
+
create(body, opts) {
|
|
1075
|
+
return this.client.request("post", "/v1/flows", {
|
|
1076
|
+
...opts,
|
|
1077
|
+
body,
|
|
1078
|
+
idempotent: true
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
/** Rename a flow or replace its full ordered list of steps. */
|
|
1082
|
+
update(id, body, opts) {
|
|
1083
|
+
return this.client.request(
|
|
1084
|
+
"patch",
|
|
1085
|
+
`/v1/flows/${encodeURIComponent(id)}`,
|
|
1086
|
+
{ ...opts, body }
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
/** Delete a flow. Its tools and their responses stay. */
|
|
1090
|
+
delete(id, opts) {
|
|
1091
|
+
return this.client.request(
|
|
1092
|
+
"delete",
|
|
1093
|
+
`/v1/flows/${encodeURIComponent(id)}`,
|
|
1094
|
+
opts
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1014
1097
|
};
|
|
1015
1098
|
|
|
1016
1099
|
// src/resources/action-plans.ts
|
|
@@ -1508,6 +1591,240 @@ var Quarantines = class {
|
|
|
1508
1591
|
}
|
|
1509
1592
|
};
|
|
1510
1593
|
|
|
1594
|
+
// src/resources/folders.ts
|
|
1595
|
+
var Folders = class {
|
|
1596
|
+
constructor(client) {
|
|
1597
|
+
this.client = client;
|
|
1598
|
+
}
|
|
1599
|
+
/** Every folder of the company, or the direct subfolders of `parentFolderId`. */
|
|
1600
|
+
list(params, opts) {
|
|
1601
|
+
return this.client.request("get", "/v1/folders", {
|
|
1602
|
+
...opts,
|
|
1603
|
+
query: { ...params, ...opts?.query }
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1606
|
+
get(id, opts) {
|
|
1607
|
+
return this.client.request(
|
|
1608
|
+
"get",
|
|
1609
|
+
`/v1/folders/${encodeURIComponent(id)}`,
|
|
1610
|
+
opts
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
create(body, opts) {
|
|
1614
|
+
return this.client.request("post", "/v1/folders", {
|
|
1615
|
+
...opts,
|
|
1616
|
+
body,
|
|
1617
|
+
idempotent: true
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* Rename or recolor a folder (`color: null` clears the color) and/or replace
|
|
1622
|
+
* its WhatsApp alert recipients (`whatsappAlertRecipients`, `[]` clears them).
|
|
1623
|
+
*/
|
|
1624
|
+
update(id, body, opts) {
|
|
1625
|
+
return this.client.request(
|
|
1626
|
+
"patch",
|
|
1627
|
+
`/v1/folders/${encodeURIComponent(id)}`,
|
|
1628
|
+
{ ...opts, body }
|
|
1629
|
+
);
|
|
1630
|
+
}
|
|
1631
|
+
/**
|
|
1632
|
+
* Move a folder under another folder, or to the root with
|
|
1633
|
+
* `parentFolderId: null`. A folder cannot move into its own subtree.
|
|
1634
|
+
*/
|
|
1635
|
+
move(id, body, opts) {
|
|
1636
|
+
return this.client.request(
|
|
1637
|
+
"patch",
|
|
1638
|
+
`/v1/folders/${encodeURIComponent(id)}/move`,
|
|
1639
|
+
{ ...opts, body }
|
|
1640
|
+
);
|
|
1641
|
+
}
|
|
1642
|
+
/**
|
|
1643
|
+
* Delete a folder. Nothing inside is deleted: subfolders, wokus and tools
|
|
1644
|
+
* move up to the parent folder (or the root).
|
|
1645
|
+
*/
|
|
1646
|
+
delete(id, opts) {
|
|
1647
|
+
return this.client.request(
|
|
1648
|
+
"delete",
|
|
1649
|
+
`/v1/folders/${encodeURIComponent(id)}`,
|
|
1650
|
+
opts
|
|
1651
|
+
);
|
|
1652
|
+
}
|
|
1653
|
+
/**
|
|
1654
|
+
* AI report over every woku, NPS, CSAT and CES tool of the folder and its
|
|
1655
|
+
* subfolders. Scales differ per type, so there is no rating distribution.
|
|
1656
|
+
*/
|
|
1657
|
+
report(id, params, opts) {
|
|
1658
|
+
return this.client.request(
|
|
1659
|
+
"get",
|
|
1660
|
+
`/v1/folders/${encodeURIComponent(id)}/report`,
|
|
1661
|
+
{ ...opts, query: { ...params, ...opts?.query } }
|
|
1662
|
+
);
|
|
1663
|
+
}
|
|
1664
|
+
/**
|
|
1665
|
+
* Per-tool figures of the woku, NPS, CSAT and CES tools of the folder and its
|
|
1666
|
+
* subfolders for a period against the one before it, with the daily sums and
|
|
1667
|
+
* the histogram of scores. `days` is 7, 30 (default) or 90, or 0 for the whole
|
|
1668
|
+
* history (every `previous` window is then empty). The period is the last
|
|
1669
|
+
* `days` calendar days in UTC, today included. Scales are never mixed across
|
|
1670
|
+
* types.
|
|
1671
|
+
*/
|
|
1672
|
+
metrics(id, params, opts) {
|
|
1673
|
+
return this.client.request(
|
|
1674
|
+
"get",
|
|
1675
|
+
`/v1/folders/${encodeURIComponent(id)}/metrics`,
|
|
1676
|
+
{ ...opts, query: { ...params, ...opts?.query } }
|
|
1677
|
+
);
|
|
1678
|
+
}
|
|
1679
|
+
/** CSV text with one row per response of every tool of the folder subtree. */
|
|
1680
|
+
csv(id, opts) {
|
|
1681
|
+
return this.client.request(
|
|
1682
|
+
"get",
|
|
1683
|
+
`/v1/folders/${encodeURIComponent(id)}/csv`,
|
|
1684
|
+
opts
|
|
1685
|
+
);
|
|
1686
|
+
}
|
|
1687
|
+
/**
|
|
1688
|
+
* PDF of the folder report (every woku, NPS, CSAT and CES tool of the folder
|
|
1689
|
+
* and its subfolders) with a breakdown per type on each native scale.
|
|
1690
|
+
* Returns the file bytes: write them to disk or send them as is.
|
|
1691
|
+
*/
|
|
1692
|
+
pdf(id, params, opts) {
|
|
1693
|
+
return this.client.request(
|
|
1694
|
+
"get",
|
|
1695
|
+
`/v1/folders/${encodeURIComponent(id)}/pdf`,
|
|
1696
|
+
{
|
|
1697
|
+
...opts,
|
|
1698
|
+
headers: { Accept: "application/pdf", ...opts?.headers },
|
|
1699
|
+
query: { ...params, ...opts?.query },
|
|
1700
|
+
binary: true
|
|
1701
|
+
}
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1704
|
+
};
|
|
1705
|
+
|
|
1706
|
+
// src/resources/tools.ts
|
|
1707
|
+
var ToolsPage = class {
|
|
1708
|
+
constructor(response, fetchNext, fetchMoreFolders = () => Promise.reject(new RangeError("No more folders"))) {
|
|
1709
|
+
this.fetchNext = fetchNext;
|
|
1710
|
+
this.fetchMoreFolders = fetchMoreFolders;
|
|
1711
|
+
this.folders = response.folders;
|
|
1712
|
+
this.tools = response.tools;
|
|
1713
|
+
this.nextCursor = response.nextCursor;
|
|
1714
|
+
this.foldersNextCursor = response.foldersNextCursor ?? null;
|
|
1715
|
+
this.facets = response.facets;
|
|
1716
|
+
}
|
|
1717
|
+
hasMoreFolders() {
|
|
1718
|
+
return this.foldersNextCursor !== null;
|
|
1719
|
+
}
|
|
1720
|
+
/** The next page of folders ("see more"): a page with those folders and no tools. */
|
|
1721
|
+
getMoreFolders() {
|
|
1722
|
+
if (this.foldersNextCursor === null)
|
|
1723
|
+
throw new RangeError("No more folders");
|
|
1724
|
+
return this.fetchMoreFolders(this.foldersNextCursor);
|
|
1725
|
+
}
|
|
1726
|
+
hasNextPage() {
|
|
1727
|
+
return this.nextCursor !== null;
|
|
1728
|
+
}
|
|
1729
|
+
getNextPage() {
|
|
1730
|
+
if (this.nextCursor === null) throw new RangeError("No next page");
|
|
1731
|
+
return this.fetchNext(this.nextCursor);
|
|
1732
|
+
}
|
|
1733
|
+
async *iterPages() {
|
|
1734
|
+
yield this;
|
|
1735
|
+
if (this.hasNextPage()) yield* (await this.getNextPage()).iterPages();
|
|
1736
|
+
}
|
|
1737
|
+
async *[Symbol.asyncIterator]() {
|
|
1738
|
+
for await (const page of this.iterPages()) {
|
|
1739
|
+
for (const tool of page.tools) yield tool;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
};
|
|
1743
|
+
var Tools = class {
|
|
1744
|
+
constructor(client) {
|
|
1745
|
+
this.client = client;
|
|
1746
|
+
}
|
|
1747
|
+
/**
|
|
1748
|
+
* List one folder level (or search the company). Folders and tools of every
|
|
1749
|
+
* type come together; the first page also carries the folders and facets.
|
|
1750
|
+
*/
|
|
1751
|
+
async list(params, opts) {
|
|
1752
|
+
const fetchPage = async (cursor, foldersCursor) => {
|
|
1753
|
+
const response = await this.client.request(
|
|
1754
|
+
"get",
|
|
1755
|
+
"/v1/tools",
|
|
1756
|
+
{
|
|
1757
|
+
...opts,
|
|
1758
|
+
query: {
|
|
1759
|
+
...params,
|
|
1760
|
+
types: params?.types?.join(","),
|
|
1761
|
+
...cursor ? { cursor } : {},
|
|
1762
|
+
...foldersCursor ? { foldersCursor } : {},
|
|
1763
|
+
...opts?.query
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
);
|
|
1767
|
+
return new ToolsPage(
|
|
1768
|
+
response,
|
|
1769
|
+
fetchPage,
|
|
1770
|
+
(next) => fetchPage(void 0, next)
|
|
1771
|
+
);
|
|
1772
|
+
};
|
|
1773
|
+
return fetchPage();
|
|
1774
|
+
}
|
|
1775
|
+
/**
|
|
1776
|
+
* Headline figures per type (woku stars, NPS score, CSAT satisfied %, CES
|
|
1777
|
+
* mean) for the company or one folder and its subfolders, in a period.
|
|
1778
|
+
* A type that fails reports `{ error: true }`; the others stay valid.
|
|
1779
|
+
*/
|
|
1780
|
+
stats(params, opts) {
|
|
1781
|
+
return this.client.request("get", "/v1/tools/stats", {
|
|
1782
|
+
...opts,
|
|
1783
|
+
query: { ...params, ...opts?.query }
|
|
1784
|
+
});
|
|
1785
|
+
}
|
|
1786
|
+
/**
|
|
1787
|
+
* Move up to 200 items of mixed types (`woku`, `nps`, `csat`, `ces`,
|
|
1788
|
+
* `folder`) into a folder, or to the root with `folderId: null`. Idempotent;
|
|
1789
|
+
* nothing moves if one item or the destination is not found.
|
|
1790
|
+
*/
|
|
1791
|
+
move(body, opts) {
|
|
1792
|
+
return this.client.request("post", "/v1/tools/move", {
|
|
1793
|
+
...opts,
|
|
1794
|
+
body
|
|
1795
|
+
});
|
|
1796
|
+
}
|
|
1797
|
+
};
|
|
1798
|
+
|
|
1799
|
+
// src/resources/journey-folders.ts
|
|
1800
|
+
var JourneyFolders = class {
|
|
1801
|
+
constructor(client) {
|
|
1802
|
+
this.client = client;
|
|
1803
|
+
}
|
|
1804
|
+
/** Whether the organization is on, running, and how much is still without a folder. */
|
|
1805
|
+
status(opts) {
|
|
1806
|
+
return this.client.request(
|
|
1807
|
+
"get",
|
|
1808
|
+
"/v1/journey-folders",
|
|
1809
|
+
opts
|
|
1810
|
+
);
|
|
1811
|
+
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Switch the organization on and run the one-off pass in the background: tools
|
|
1814
|
+
* of existing journeys without a folder move into their moment folder, and
|
|
1815
|
+
* tools journeys create from now on are placed there. Tools that already have
|
|
1816
|
+
* a folder are never touched. Rejects with a conflict (409) while a pass is
|
|
1817
|
+
* already running; poll {@link status} for progress.
|
|
1818
|
+
*/
|
|
1819
|
+
organize(opts) {
|
|
1820
|
+
return this.client.request(
|
|
1821
|
+
"post",
|
|
1822
|
+
"/v1/journey-folders/organize",
|
|
1823
|
+
opts
|
|
1824
|
+
);
|
|
1825
|
+
}
|
|
1826
|
+
};
|
|
1827
|
+
|
|
1511
1828
|
// src/woku.ts
|
|
1512
1829
|
var Woku = class {
|
|
1513
1830
|
constructor(options) {
|
|
@@ -1527,11 +1844,15 @@ var Woku = class {
|
|
|
1527
1844
|
this.actionPlans = new ActionPlans(this.client);
|
|
1528
1845
|
this.actionPlanGroups = new ActionPlanGroups(this.client);
|
|
1529
1846
|
this.tickets = new Tickets(this.client);
|
|
1847
|
+
this.followUps = new FollowUps(this.client);
|
|
1530
1848
|
this.ticketDestinations = new TicketDestinations(this.client);
|
|
1531
1849
|
this.dispatches = new Dispatches(this.client);
|
|
1532
1850
|
this.reports = new Reports(this.client);
|
|
1533
1851
|
this.company = new Company(this.client);
|
|
1534
1852
|
this.journeys = new Journeys(this.client);
|
|
1853
|
+
this.folders = new Folders(this.client);
|
|
1854
|
+
this.tools = new Tools(this.client);
|
|
1855
|
+
this.journeyFolders = new JourneyFolders(this.client);
|
|
1535
1856
|
this.quarantines = new Quarantines(this.client);
|
|
1536
1857
|
}
|
|
1537
1858
|
};
|
|
@@ -1539,7 +1860,10 @@ var Woku = class {
|
|
|
1539
1860
|
exports.AuthenticationError = AuthenticationError;
|
|
1540
1861
|
exports.BadRequestError = BadRequestError;
|
|
1541
1862
|
exports.ConflictError = ConflictError;
|
|
1863
|
+
exports.Folders = Folders;
|
|
1864
|
+
exports.FollowUps = FollowUps;
|
|
1542
1865
|
exports.InternalServerError = InternalServerError;
|
|
1866
|
+
exports.JourneyFolders = JourneyFolders;
|
|
1543
1867
|
exports.Journeys = Journeys;
|
|
1544
1868
|
exports.Media = Media;
|
|
1545
1869
|
exports.NotFoundError = NotFoundError;
|
|
@@ -1547,6 +1871,8 @@ exports.Page = Page;
|
|
|
1547
1871
|
exports.PayloadTooLargeError = PayloadTooLargeError;
|
|
1548
1872
|
exports.PermissionDeniedError = PermissionDeniedError;
|
|
1549
1873
|
exports.RateLimitError = RateLimitError;
|
|
1874
|
+
exports.Tools = Tools;
|
|
1875
|
+
exports.ToolsPage = ToolsPage;
|
|
1550
1876
|
exports.UnprocessableEntityError = UnprocessableEntityError;
|
|
1551
1877
|
exports.Woku = Woku;
|
|
1552
1878
|
exports.WokuAPIError = WokuAPIError;
|