@crvouga/mockingbird-service-intercom 0.2.0 → 1.1.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/CHANGELOG.md +13 -1
- package/README.md +18 -9
- package/dist/{chunk-OSBG2XJ7.js → chunk-DOY3BICO.js} +148 -33
- package/dist/chunk-DOY3BICO.js.map +7 -0
- package/dist/{chunk-5RRE6WVT.js → chunk-RBUSIJK6.js} +15 -3
- package/dist/{chunk-5RRE6WVT.js.map → chunk-RBUSIJK6.js.map} +2 -2
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +64 -4
- package/dist/index.js +1 -1
- package/dist/server.d.ts +63 -3
- package/dist/server.js +2 -2
- package/package.json +7 -3
- package/dist/chunk-OSBG2XJ7.js.map +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
# Changelog — @crvouga/mockingbird-service-intercom
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 1.1.0 (2026-09-25)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- metadata-only outbox, opt-in member webhook topics, and promote to ready ([4d635e9](https://github.com/crvouga/mockingbird/commit/4d635e90e1938dd5814ac33c10c92c768321a15b))
|
|
8
|
+
|
|
9
|
+
## 1.0.0 (2026-09-24)
|
|
10
|
+
|
|
11
|
+
### ⚠️ Breaking changes
|
|
12
|
+
|
|
13
|
+
- vendor branding for every service; purge internal mocks and consumer names ([f689534](https://github.com/crvouga/mockingbird/commit/f6895343543550dafcb7d76b426cae7635e03226))
|
|
14
|
+
|
|
15
|
+
## 0.2.0 (2026-09-23)
|
|
4
16
|
|
|
5
17
|
### Features
|
|
6
18
|
|
package/README.md
CHANGED
|
@@ -9,7 +9,8 @@ our backend makes:
|
|
|
9
9
|
`?display_as=plaintext`, and cursor-paginated search;
|
|
10
10
|
- **identity:** `/admins` and `/me`.
|
|
11
11
|
|
|
12
|
-
Admin replies and close/open
|
|
12
|
+
Admin replies and close/open (and, for a receiver that subscribes to them, member messages and
|
|
13
|
+
replies) send Intercom's `notification_event` webhooks, signed with
|
|
13
14
|
`X-Hub-Signature: sha1=<hex HMAC-SHA1>`, to both of our receivers: the backend's
|
|
14
15
|
`POST /messaging/webhook` and the EMR's `POST /v1/webhooks/intercom`.
|
|
15
16
|
|
|
@@ -36,6 +37,10 @@ npx mockingbird-intercom serve --port 8807 \
|
|
|
36
37
|
--webhook-secret "$INTERCOM_WEBHOOK_SECRET"
|
|
37
38
|
```
|
|
38
39
|
|
|
40
|
+
Add `--webhook-events conversation.user.created,conversation.user.replied,conversation.admin.replied,conversation.admin.closed,conversation.admin.opened`
|
|
41
|
+
to subscribe `--webhook-url` to the member topics too; `--emr-webhook-url` keeps the admin-only
|
|
42
|
+
default.
|
|
43
|
+
|
|
39
44
|
| Consumer | Setting |
|
|
40
45
|
| --- | --- |
|
|
41
46
|
| Member messaging adapter (`intercom-messaging.adapter.ts`) | `INTERCOM_API_BASE_URL=http://127.0.0.1:8807` (already read), `INTERCOM_ACCESS_TOKEN=<any>` |
|
|
@@ -124,11 +129,14 @@ The mock sends webhooks for these events:
|
|
|
124
129
|
| Close (`/parts` or `POST /__admin/conversations/:id/close`) | `conversation.admin.closed` |
|
|
125
130
|
| Open (`/parts` or `POST /__admin/conversations/:id/open`) | `conversation.admin.opened` |
|
|
126
131
|
| Admin-initiated conversation (`POST /__admin/conversations`) | `conversation.admin.single.created` |
|
|
132
|
+
| Member starts a conversation (`POST /conversations`) | `conversation.user.created` (opt-in) |
|
|
133
|
+
| Member reply (`/reply` with `type: "user"`) | `conversation.user.replied` (opt-in) |
|
|
127
134
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
`
|
|
131
|
-
|
|
135
|
+
An endpoint receives the four admin topics above unless it lists its own `events`. The member
|
|
136
|
+
topics, `conversation.admin.snoozed` and `.assigned` are published but opt-in: subscribe with
|
|
137
|
+
`--webhook-events`, `webhooks.urls: [{url, events}]` or `PUT /__admin/webhook-endpoints`. The EMR
|
|
138
|
+
treats every `notification_event` it receives as an admin event, so its subscription must not
|
|
139
|
+
include member topics. Notes send nothing.
|
|
132
140
|
|
|
133
141
|
Each delivery is a JSON body posted with `X-Hub-Signature: sha1=<hex HMAC-SHA1(secret, rawBody)>`:
|
|
134
142
|
|
|
@@ -156,7 +164,8 @@ Each delivery is a JSON body posted with `X-Hub-Signature: sha1=<hex HMAC-SHA1(s
|
|
|
156
164
|
|
|
157
165
|
| Route | Effect |
|
|
158
166
|
| --- | --- |
|
|
159
|
-
| `POST /__admin/conversations/:id/admin-reply` | `{adminId? (default: first admin), body, messageType?: "comment" \| "note"}` appends an admin part and fires the webhook. |
|
|
167
|
+
| `POST /__admin/conversations/:id/admin-reply` | `{adminId? \| admin_id? (default: first admin), body, messageType?: "comment" \| "note"}` appends an admin part ("the care team replied") and fires the webhook. |
|
|
168
|
+
| `GET /__admin/outbox?since=&to=&limit=`, `GET /__admin/outbox/:id` | What the client sent through `POST /conversations` and `/reply`, oldest first: `{id, operation, conversationId, contactId, partId, partType ("source" for the opening message), authorType, authorId, hasBody, bodyLength, attachmentCount, at, createdAt}`. **Metadata only**: the body is never stored. `to=` matches the conversation or contact id; `since=` takes epoch ms or ISO-8601 on the mock clock. The admin plane's own replies are not listed. |
|
|
160
169
|
| `POST /__admin/conversations/:id/close`, `…/open` | `{adminId?}` closes or reopens as an admin and fires the webhook. |
|
|
161
170
|
| `POST /__admin/conversations` | `{contactId \| externalId, adminId?, body}` creates an admin-initiated conversation. The EMR then looks the member up by `contacts[0].external_id`. |
|
|
162
171
|
| `GET /__admin/conversations`, `GET /__admin/contacts` | The namespace's records. |
|
|
@@ -205,14 +214,14 @@ never records message bodies or contact details.
|
|
|
205
214
|
| --- | --- | --- |
|
|
206
215
|
| `IntercomAPI` | class | The in-process mock: `fetch(request)`, `reset()`, `appendPart(…)`, `manage(…)`, `startAdminConversation(…)`, `conversations()`, `contacts()`, `conversationBody(…)`, `contactBody(…)`, `error(…)`, `state`. Options: `sqlite`, `now`, `wallClock`, `namespace`, `admins`, `settings`, `onWebhook`. |
|
|
207
216
|
| `IntercomError` | class | A vendor error (status, code, message) raised by the admin-facing methods. |
|
|
208
|
-
| `createRuntime` | function | The mock with the full service contract (health, admin, namespaces, credentials, presets, webhooks). Options: `webhooks: {urls, secret?, events?, retryDelaysMs?, fetch?}
|
|
217
|
+
| `createRuntime` | function | The mock with the full service contract (health, admin, namespaces, credentials, presets, webhooks). Options: `webhooks: {urls, secret?, events?, retryDelaysMs?, fetch?}` (a `urls` entry may be `{url, events}` to override `events` for that URL), `admins`, `settings`, `clock`, `wallClock`, `seed`, `adminKey`, `onLog`, `sqlite`. |
|
|
209
218
|
| `INTERCOM_PRESETS` | object | Every named fault preset. |
|
|
210
219
|
| `INTERCOM_NAMESPACE` | string | The service name, `"intercom"`. |
|
|
211
|
-
| `INTERCOM_TOPICS` | array | Every webhook topic the mock can send. |
|
|
220
|
+
| `INTERCOM_TOPICS` | array | Every webhook topic the mock can send, including the opt-in member topics. |
|
|
212
221
|
| `HUB_SIGNATURE_HEADER`, `signHub` | values | `X-Hub-Signature` and the `sha1=<hex>` signer, for your own receivers. |
|
|
213
222
|
| `DEFAULT_ADMINS`, `DEFAULT_SETTINGS` | values | The seeded admins and default settings. |
|
|
214
223
|
| `toHtml`, `toPlaintext`, `encodeCursor`, `decodeCursor` | functions | Body rendering and the search cursor format. |
|
|
215
224
|
| `document`, `operationIds`, `supportedOperationIds` | values | The vendored OpenAPI contract and its operation ids. |
|
|
216
|
-
| `createServer`, `serveTarget`, `DEFAULT_PORT` (`./server`) | Node | Serve over `node:http`; the `serve` CLI target (`--webhook-url`, `--emr-webhook-url`, `--webhook-secret`, `--access-token`); port 8807. |
|
|
225
|
+
| `createServer`, `serveTarget`, `DEFAULT_PORT` (`./server`) | Node | Serve over `node:http`; the `serve` CLI target (`--webhook-url`, `--webhook-events`, `--emr-webhook-url`, `--webhook-secret`, `--access-token`); port 8807. |
|
|
217
226
|
|
|
218
227
|
Part of [mockingbird](https://github.com/crvouga/mockingbird).
|
|
@@ -1149,6 +1149,73 @@ var createMetrics = () => {
|
|
|
1149
1149
|
};
|
|
1150
1150
|
};
|
|
1151
1151
|
|
|
1152
|
+
// ../core/dist/outbox.js
|
|
1153
|
+
var OutboxStore = class {
|
|
1154
|
+
items;
|
|
1155
|
+
constructor(sqlite, namespace, name = "outbox") {
|
|
1156
|
+
this.items = new Collection(sqlite, namespace, name);
|
|
1157
|
+
}
|
|
1158
|
+
record(item) {
|
|
1159
|
+
this.items.insert(item.id, item);
|
|
1160
|
+
return item;
|
|
1161
|
+
}
|
|
1162
|
+
get(id) {
|
|
1163
|
+
return this.items.get(id);
|
|
1164
|
+
}
|
|
1165
|
+
update(id, item) {
|
|
1166
|
+
this.items.update(id, item);
|
|
1167
|
+
}
|
|
1168
|
+
/** Oldest first, so a suite reads messages in the order they were sent. */
|
|
1169
|
+
list(query = {}) {
|
|
1170
|
+
const to = query.to?.toLowerCase();
|
|
1171
|
+
const matched = this.items.list({ order: "oldest" }).map((row) => row.value).filter((item) => {
|
|
1172
|
+
if (to !== void 0) {
|
|
1173
|
+
const recipients = Array.isArray(item.to) ? item.to : [item.to];
|
|
1174
|
+
if (!recipients.some((r) => r.toLowerCase() === to))
|
|
1175
|
+
return false;
|
|
1176
|
+
}
|
|
1177
|
+
if (query.since !== void 0 && Date.parse(item.createdAt) < query.since)
|
|
1178
|
+
return false;
|
|
1179
|
+
if (query.where && !query.where(item))
|
|
1180
|
+
return false;
|
|
1181
|
+
return true;
|
|
1182
|
+
});
|
|
1183
|
+
return query.limit !== void 0 ? matched.slice(-Math.max(0, query.limit)) : matched;
|
|
1184
|
+
}
|
|
1185
|
+
};
|
|
1186
|
+
var json2 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
1187
|
+
var parseSince = (value) => {
|
|
1188
|
+
if (value === null)
|
|
1189
|
+
return void 0;
|
|
1190
|
+
const parsed = /^\d+$/.test(value) ? Number(value) : Date.parse(value);
|
|
1191
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
1192
|
+
};
|
|
1193
|
+
var outboxAdminRoutes = (runtime, pick, filter) => ({
|
|
1194
|
+
"GET /outbox": ({ url, namespace }) => {
|
|
1195
|
+
const since = parseSince(url.searchParams.get("since"));
|
|
1196
|
+
if (since === null) {
|
|
1197
|
+
return json2(400, {
|
|
1198
|
+
error: { type: "mockingbird_admin", message: "since: expected epoch ms or ISO-8601" }
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
const limit = url.searchParams.get("limit");
|
|
1202
|
+
const where = filter?.(url.searchParams);
|
|
1203
|
+
const to = url.searchParams.get("to");
|
|
1204
|
+
return json2(200, {
|
|
1205
|
+
messages: pick(runtime.instance(namespace)).list({
|
|
1206
|
+
...to !== null ? { to } : {},
|
|
1207
|
+
...since !== void 0 ? { since } : {},
|
|
1208
|
+
...where ? { where } : {},
|
|
1209
|
+
...limit !== null && /^\d+$/.test(limit) ? { limit: Number(limit) } : {}
|
|
1210
|
+
})
|
|
1211
|
+
});
|
|
1212
|
+
},
|
|
1213
|
+
"GET /outbox/:id": ({ params, namespace }) => {
|
|
1214
|
+
const item = pick(runtime.instance(namespace)).get(params.id);
|
|
1215
|
+
return item ? json2(200, item) : json2(404, { error: { type: "mockingbird_admin", message: `no message ${params.id}` } });
|
|
1216
|
+
}
|
|
1217
|
+
});
|
|
1218
|
+
|
|
1152
1219
|
// ../../core/dist/timeline.js
|
|
1153
1220
|
var BRANCH_PATTERN = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
1154
1221
|
var Timeline = class {
|
|
@@ -1531,7 +1598,7 @@ var restoreNamespace = (sqlite, namespace, snapshot) => {
|
|
|
1531
1598
|
};
|
|
1532
1599
|
|
|
1533
1600
|
// ../core/dist/version.js
|
|
1534
|
-
var PACKAGE_VERSION = true ? "
|
|
1601
|
+
var PACKAGE_VERSION = true ? "1.1.0" : UNRELEASED_VERSION;
|
|
1535
1602
|
|
|
1536
1603
|
// ../core/dist/signing.js
|
|
1537
1604
|
var encoder = new TextEncoder();
|
|
@@ -1854,8 +1921,8 @@ var createWebhookHub = (options) => {
|
|
|
1854
1921
|
};
|
|
1855
1922
|
return hub;
|
|
1856
1923
|
};
|
|
1857
|
-
var
|
|
1858
|
-
var adminError2 = (status, message) =>
|
|
1924
|
+
var json3 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
1925
|
+
var adminError2 = (status, message) => json3(status, { error: { type: "mockingbird_admin", message } });
|
|
1859
1926
|
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1860
1927
|
var parseEndpoint = (value) => {
|
|
1861
1928
|
if (!isRecord3(value) || typeof value.url !== "string")
|
|
@@ -1886,7 +1953,7 @@ var parseEndpoint = (value) => {
|
|
|
1886
1953
|
return endpoint;
|
|
1887
1954
|
};
|
|
1888
1955
|
var webhookAdminRoutes = (hub) => ({
|
|
1889
|
-
"GET /webhooks": ({ url, namespace }) =>
|
|
1956
|
+
"GET /webhooks": ({ url, namespace }) => json3(200, {
|
|
1890
1957
|
deliveries: hub.deliveries(url.searchParams.get("all") === "1" ? void 0 : namespace).filter((d) => {
|
|
1891
1958
|
const type = url.searchParams.get("type");
|
|
1892
1959
|
return type === null || d.type === type;
|
|
@@ -1894,17 +1961,17 @@ var webhookAdminRoutes = (hub) => ({
|
|
|
1894
1961
|
}),
|
|
1895
1962
|
"GET /webhooks/events": ({ url, namespace }) => {
|
|
1896
1963
|
const type = url.searchParams.get("type");
|
|
1897
|
-
return
|
|
1964
|
+
return json3(200, {
|
|
1898
1965
|
events: hub.messages(url.searchParams.get("all") === "1" ? void 0 : namespace).filter((m) => type === null || m.type === type).map((m) => ({ ...m, payload: parsePayload(m) }))
|
|
1899
1966
|
});
|
|
1900
1967
|
},
|
|
1901
1968
|
"POST /webhooks/:id/replay": async ({ params }) => {
|
|
1902
1969
|
const replayed = await hub.replay(params.id);
|
|
1903
|
-
return replayed ?
|
|
1970
|
+
return replayed ? json3(200, replayed) : adminError2(404, `no delivery ${params.id}`);
|
|
1904
1971
|
},
|
|
1905
1972
|
"POST /webhooks/flush": async () => {
|
|
1906
1973
|
await hub.flush();
|
|
1907
|
-
return
|
|
1974
|
+
return json3(200, { status: "ok" });
|
|
1908
1975
|
},
|
|
1909
1976
|
"POST /webhooks/faults": ({ body, namespace }) => {
|
|
1910
1977
|
if (!isRecord3(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
|
|
@@ -1914,9 +1981,9 @@ var webhookAdminRoutes = (hub) => ({
|
|
|
1914
1981
|
if (typeof body.count === "number")
|
|
1915
1982
|
fault.count = body.count;
|
|
1916
1983
|
hub.fault(namespace, fault);
|
|
1917
|
-
return
|
|
1984
|
+
return json3(201, { namespace, ...fault });
|
|
1918
1985
|
},
|
|
1919
|
-
"GET /webhook-endpoints": ({ namespace }) =>
|
|
1986
|
+
"GET /webhook-endpoints": ({ namespace }) => json3(200, {
|
|
1920
1987
|
endpoints: hub.endpoints(namespace).map(({ secret, ...rest }) => ({
|
|
1921
1988
|
...rest,
|
|
1922
1989
|
secret: secret ? "(set)" : null
|
|
@@ -1934,11 +2001,11 @@ var webhookAdminRoutes = (hub) => ({
|
|
|
1934
2001
|
parsed.push(endpoint);
|
|
1935
2002
|
}
|
|
1936
2003
|
const set = hub.setEndpoints(namespace, parsed);
|
|
1937
|
-
return
|
|
2004
|
+
return json3(200, { endpoints: set.map((e) => ({ ...e, secret: e.secret ? "(set)" : null })) });
|
|
1938
2005
|
},
|
|
1939
2006
|
"DELETE /webhook-endpoints": ({ namespace }) => {
|
|
1940
2007
|
hub.setEndpoints(namespace, []);
|
|
1941
|
-
return
|
|
2008
|
+
return json3(200, { status: "ok" });
|
|
1942
2009
|
}
|
|
1943
2010
|
});
|
|
1944
2011
|
var parsePayload = (message) => {
|
|
@@ -2683,6 +2750,7 @@ var IntercomState = class {
|
|
|
2683
2750
|
this.admins = new Collection(sqlite, namespace, "admins");
|
|
2684
2751
|
this.settings = new Collection(sqlite, namespace, "settings");
|
|
2685
2752
|
this.counters = new Collection(sqlite, namespace, "counters");
|
|
2753
|
+
this.outbox = new OutboxStore(sqlite, namespace, "outbox");
|
|
2686
2754
|
this.workspaceId = "mockapp";
|
|
2687
2755
|
this.ensureSeeded();
|
|
2688
2756
|
}
|
|
@@ -2693,6 +2761,7 @@ var IntercomState = class {
|
|
|
2693
2761
|
admins;
|
|
2694
2762
|
settings;
|
|
2695
2763
|
counters;
|
|
2764
|
+
outbox;
|
|
2696
2765
|
workspaceId;
|
|
2697
2766
|
ensureSeeded() {
|
|
2698
2767
|
if (this.admins.count() === 0) {
|
|
@@ -2810,8 +2879,8 @@ var INTERCOM_PRESETS = {
|
|
|
2810
2879
|
webhook: { mode: "drop" }
|
|
2811
2880
|
}
|
|
2812
2881
|
};
|
|
2813
|
-
var
|
|
2814
|
-
var adminError3 = (status, message) =>
|
|
2882
|
+
var json4 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
2883
|
+
var adminError3 = (status, message) => json4(status, { error: { type: "mockingbird_admin", message } });
|
|
2815
2884
|
var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2816
2885
|
var guard = (run) => {
|
|
2817
2886
|
try {
|
|
@@ -2826,8 +2895,9 @@ var adminRoutes = (runtime) => {
|
|
|
2826
2895
|
const conversation = (namespace, id) => api(namespace).state.conversations.get(id);
|
|
2827
2896
|
const defaultAdmin = (namespace) => api(namespace).state.admins.list({ order: "oldest" }).at(0)?.value.id ?? "";
|
|
2828
2897
|
return {
|
|
2829
|
-
|
|
2830
|
-
"GET /
|
|
2898
|
+
...outboxAdminRoutes(runtime, (instance) => instance.state.outbox),
|
|
2899
|
+
"GET /contacts": ({ namespace }) => json4(200, { contacts: api(namespace).contacts() }),
|
|
2900
|
+
"GET /conversations": ({ namespace }) => json4(200, { conversations: api(namespace).conversations() }),
|
|
2831
2901
|
"POST /conversations": ({ body, namespace }) => guard(() => {
|
|
2832
2902
|
if (!isRecord5(body) || typeof body.body !== "string") {
|
|
2833
2903
|
return adminError3(400, 'expected {"contactId" | "externalId", "adminId"?, "body"}');
|
|
@@ -2840,7 +2910,7 @@ var adminRoutes = (runtime) => {
|
|
|
2840
2910
|
adminId: typeof body.adminId === "string" ? body.adminId : defaultAdmin(namespace),
|
|
2841
2911
|
body: body.body
|
|
2842
2912
|
});
|
|
2843
|
-
return
|
|
2913
|
+
return json4(201, created);
|
|
2844
2914
|
}),
|
|
2845
2915
|
"POST /conversations/:id/admin-reply": ({ params, body, namespace }) => guard(() => {
|
|
2846
2916
|
const found = conversation(namespace, params.id);
|
|
@@ -2848,32 +2918,33 @@ var adminRoutes = (runtime) => {
|
|
|
2848
2918
|
if (!isRecord5(body) || typeof body.body !== "string") {
|
|
2849
2919
|
return adminError3(
|
|
2850
2920
|
400,
|
|
2851
|
-
'expected {"adminId"?, "body", "messageType"?: "comment" | "note"}'
|
|
2921
|
+
'expected {"adminId" | "admin_id"?, "body", "messageType"?: "comment" | "note"}'
|
|
2852
2922
|
);
|
|
2853
2923
|
}
|
|
2854
2924
|
const instance = api(namespace);
|
|
2925
|
+
const requested = body.adminId ?? body.admin_id;
|
|
2855
2926
|
const author = instance.state.admins.get(
|
|
2856
|
-
typeof
|
|
2927
|
+
typeof requested === "string" ? requested : defaultAdmin(namespace)
|
|
2857
2928
|
);
|
|
2858
|
-
if (!author) return adminError3(404, `no admin ${String(
|
|
2929
|
+
if (!author) return adminError3(404, `no admin ${String(requested)}`);
|
|
2859
2930
|
const next = instance.appendPart(found, {
|
|
2860
2931
|
partType: body.messageType === "note" ? "note" : "comment",
|
|
2861
2932
|
author: { type: "admin", id: author.id, name: author.name, email: author.email },
|
|
2862
2933
|
body: toHtml(body.body)
|
|
2863
2934
|
});
|
|
2864
|
-
return
|
|
2935
|
+
return json4(200, next);
|
|
2865
2936
|
}),
|
|
2866
2937
|
"POST /conversations/:id/close": ({ params, body, namespace }) => guard(() => {
|
|
2867
2938
|
const found = conversation(namespace, params.id);
|
|
2868
2939
|
if (!found) return adminError3(404, `no conversation ${params.id}`);
|
|
2869
2940
|
const adminId = isRecord5(body) && typeof body.adminId === "string" ? body.adminId : defaultAdmin(namespace);
|
|
2870
|
-
return
|
|
2941
|
+
return json4(200, api(namespace).manage(found, { action: "close", adminId }));
|
|
2871
2942
|
}),
|
|
2872
2943
|
"POST /conversations/:id/open": ({ params, body, namespace }) => guard(() => {
|
|
2873
2944
|
const found = conversation(namespace, params.id);
|
|
2874
2945
|
if (!found) return adminError3(404, `no conversation ${params.id}`);
|
|
2875
2946
|
const adminId = isRecord5(body) && typeof body.adminId === "string" ? body.adminId : defaultAdmin(namespace);
|
|
2876
|
-
return
|
|
2947
|
+
return json4(200, api(namespace).manage(found, { action: "open", adminId }));
|
|
2877
2948
|
}),
|
|
2878
2949
|
"PUT /admins": ({ body, namespace }) => {
|
|
2879
2950
|
const list = Array.isArray(body) ? body : isRecord5(body) ? body.admins : void 0;
|
|
@@ -2895,9 +2966,9 @@ var adminRoutes = (runtime) => {
|
|
|
2895
2966
|
team_ids: []
|
|
2896
2967
|
});
|
|
2897
2968
|
}
|
|
2898
|
-
return
|
|
2969
|
+
return json4(200, { admins: state.admins.list({ order: "oldest" }).map((row) => row.value) });
|
|
2899
2970
|
},
|
|
2900
|
-
"GET /settings": ({ namespace }) =>
|
|
2971
|
+
"GET /settings": ({ namespace }) => json4(200, api(namespace).state.current()),
|
|
2901
2972
|
"PUT /settings": ({ body, namespace }) => {
|
|
2902
2973
|
if (!isRecord5(body)) return adminError3(400, "expected a JSON object");
|
|
2903
2974
|
const patch = {};
|
|
@@ -2911,7 +2982,7 @@ var adminRoutes = (runtime) => {
|
|
|
2911
2982
|
}
|
|
2912
2983
|
patch.customAttributes = body.customAttributes === null ? null : body.customAttributes.map(String);
|
|
2913
2984
|
}
|
|
2914
|
-
return
|
|
2985
|
+
return json4(200, api(namespace).state.update(patch));
|
|
2915
2986
|
}
|
|
2916
2987
|
};
|
|
2917
2988
|
};
|
|
@@ -2924,14 +2995,15 @@ var createRuntime2 = (options = {}) => {
|
|
|
2924
2995
|
...hooks?.retryDelaysMs ? { retryDelaysMs: hooks.retryDelaysMs } : {},
|
|
2925
2996
|
...hooks?.fetch ? { fetch: hooks.fetch } : {},
|
|
2926
2997
|
...options.wallClock ? { now: options.wallClock } : {},
|
|
2927
|
-
endpoints: (hooks?.urls ?? []).map(
|
|
2928
|
-
|
|
2998
|
+
endpoints: (hooks?.urls ?? []).map((entry, index) => {
|
|
2999
|
+
const target = typeof entry === "string" ? { url: entry } : entry;
|
|
3000
|
+
return {
|
|
2929
3001
|
id: `we_intercom_${index}`,
|
|
2930
|
-
url,
|
|
3002
|
+
url: target.url,
|
|
2931
3003
|
...hooks?.secret ? { secret: hooks.secret } : {},
|
|
2932
|
-
events: [...hooks?.events ?? DEFAULT_TOPICS]
|
|
2933
|
-
}
|
|
2934
|
-
)
|
|
3004
|
+
events: [...target.events ?? hooks?.events ?? DEFAULT_TOPICS]
|
|
3005
|
+
};
|
|
3006
|
+
})
|
|
2935
3007
|
});
|
|
2936
3008
|
const runtime = createRuntime({
|
|
2937
3009
|
name: INTERCOM_NAMESPACE,
|
|
@@ -2972,7 +3044,9 @@ var INTERCOM_TOPICS = [
|
|
|
2972
3044
|
"conversation.admin.opened",
|
|
2973
3045
|
"conversation.admin.snoozed",
|
|
2974
3046
|
"conversation.admin.assigned",
|
|
2975
|
-
"conversation.admin.single.created"
|
|
3047
|
+
"conversation.admin.single.created",
|
|
3048
|
+
"conversation.user.created",
|
|
3049
|
+
"conversation.user.replied"
|
|
2976
3050
|
];
|
|
2977
3051
|
var CONTACT_FIELDS = [
|
|
2978
3052
|
"id",
|
|
@@ -3407,6 +3481,17 @@ var IntercomAPI = class {
|
|
|
3407
3481
|
parts: []
|
|
3408
3482
|
};
|
|
3409
3483
|
this.state.conversations.insert(conversation.id, conversation);
|
|
3484
|
+
const text2 = String(body.body);
|
|
3485
|
+
this.recordSent({
|
|
3486
|
+
operation: "CreateConversation",
|
|
3487
|
+
conversation,
|
|
3488
|
+
partId: null,
|
|
3489
|
+
partType: "source",
|
|
3490
|
+
author: conversation.source.author,
|
|
3491
|
+
bodyLength: text2.length,
|
|
3492
|
+
attachmentCount: 0
|
|
3493
|
+
});
|
|
3494
|
+
this.notify("conversation.user.created", conversation, []);
|
|
3410
3495
|
return annotateResponse(
|
|
3411
3496
|
jsonRes(200, {
|
|
3412
3497
|
type: "user_message",
|
|
@@ -3577,6 +3662,15 @@ var IntercomAPI = class {
|
|
|
3577
3662
|
body: body === void 0 ? null : toHtml(body),
|
|
3578
3663
|
attachments: stored
|
|
3579
3664
|
});
|
|
3665
|
+
this.recordSent({
|
|
3666
|
+
operation: "ReplyConversation",
|
|
3667
|
+
conversation: next,
|
|
3668
|
+
partId,
|
|
3669
|
+
partType: messageType,
|
|
3670
|
+
author,
|
|
3671
|
+
bodyLength: body?.length ?? 0,
|
|
3672
|
+
attachmentCount: stored.length
|
|
3673
|
+
});
|
|
3580
3674
|
return annotateResponse(jsonRes(200, this.conversationBody(next, { parts: next.parts })), {
|
|
3581
3675
|
ids: { conversationId: next.id, partId }
|
|
3582
3676
|
});
|
|
@@ -3611,8 +3705,29 @@ var IntercomAPI = class {
|
|
|
3611
3705
|
};
|
|
3612
3706
|
this.state.conversations.update(conversation.id, next);
|
|
3613
3707
|
if (byAdmin && visible) this.notify("conversation.admin.replied", next, [part]);
|
|
3708
|
+
if (input.author.type === "user") this.notify("conversation.user.replied", next, [part]);
|
|
3614
3709
|
return next;
|
|
3615
3710
|
}
|
|
3711
|
+
/** Record what the API client sent, for `GET /__admin/outbox`: metadata only, never the body. */
|
|
3712
|
+
recordSent(input) {
|
|
3713
|
+
const at = new Date(this.now()).toISOString();
|
|
3714
|
+
this.state.outbox.record({
|
|
3715
|
+
id: `sent_${this.state.next("sent")}`,
|
|
3716
|
+
to: [input.conversation.id, input.conversation.contactId],
|
|
3717
|
+
createdAt: at,
|
|
3718
|
+
at,
|
|
3719
|
+
operation: input.operation,
|
|
3720
|
+
conversationId: input.conversation.id,
|
|
3721
|
+
contactId: input.conversation.contactId,
|
|
3722
|
+
partId: input.partId,
|
|
3723
|
+
partType: input.partType,
|
|
3724
|
+
authorType: input.author.type === "admin" ? "admin" : "user",
|
|
3725
|
+
authorId: input.author.id,
|
|
3726
|
+
hasBody: input.bodyLength > 0,
|
|
3727
|
+
bodyLength: input.bodyLength,
|
|
3728
|
+
attachmentCount: input.attachmentCount
|
|
3729
|
+
});
|
|
3730
|
+
}
|
|
3616
3731
|
manageConversation(context) {
|
|
3617
3732
|
return this.withConversation(context, (conversation) => {
|
|
3618
3733
|
const body = this.json(context);
|
|
@@ -3832,4 +3947,4 @@ export {
|
|
|
3832
3947
|
IntercomError,
|
|
3833
3948
|
IntercomAPI
|
|
3834
3949
|
};
|
|
3835
|
-
//# sourceMappingURL=chunk-
|
|
3950
|
+
//# sourceMappingURL=chunk-DOY3BICO.js.map
|