chatwoot-router 0.5.0 → 0.5.2
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 +18 -1
- package/README.md +0 -31
- package/lib/packages/chatwoot-router/src/index.js +25 -4
- package/lib/shared/budget.d.ts +4 -0
- package/lib/shared/chatwoot/api.d.ts +0 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.2] - 2026-10-05
|
|
11
|
+
|
|
12
|
+
## [0.5.1] - 2026-10-05
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Route on a customer's message as it arrives. Chatwoot's message webhook describes a contact without a `type`
|
|
17
|
+
(`Contact#webhook_data`; users and bots have one), and the router required `type: "contact"`, so it ignored
|
|
18
|
+
every customer message and a new ticket waited for the five-minute sweep.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Clear a request's timeout once its response has been read. The pending `AbortSignal.timeout` timer kept a
|
|
23
|
+
Durable Object invocation open until it fired, so most alarms were billed and reported at about 60 seconds.
|
|
24
|
+
|
|
10
25
|
## [0.5.0] - 2026-10-05
|
|
11
26
|
|
|
12
27
|
### Fixed
|
|
@@ -112,7 +127,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
112
127
|
`routing_kind`.
|
|
113
128
|
- The `chatwoot-router-store-config` command and `chatwoot-router/stored-config`, for a configuration in KV.
|
|
114
129
|
|
|
115
|
-
[Unreleased]: https://github.com/Phala-Network/chatwoot-workers/compare/chatwoot-router@0.5.
|
|
130
|
+
[Unreleased]: https://github.com/Phala-Network/chatwoot-workers/compare/chatwoot-router@0.5.2...HEAD
|
|
131
|
+
[0.5.2]: https://github.com/Phala-Network/chatwoot-workers/compare/chatwoot-router@0.5.1...chatwoot-router@0.5.2
|
|
132
|
+
[0.5.1]: https://github.com/Phala-Network/chatwoot-workers/compare/chatwoot-router@0.5.0...chatwoot-router@0.5.1
|
|
116
133
|
[0.5.0]: https://github.com/Phala-Network/chatwoot-workers/compare/chatwoot-router@0.4.0...chatwoot-router@0.5.0
|
|
117
134
|
[0.4.0]: https://github.com/Phala-Network/chatwoot-workers/compare/chatwoot-router@0.3.0...chatwoot-router@0.4.0
|
|
118
135
|
[0.3.0]: https://github.com/Phala-Network/chatwoot-workers/compare/chatwoot-router@0.2.0...chatwoot-router@0.3.0
|
package/README.md
CHANGED
|
@@ -148,37 +148,6 @@ the Router DO during rollback. If its state is lost or restored to an older reco
|
|
|
148
148
|
and isolate conversations with uncertain attempts/turns for owner review; a currently empty history does not
|
|
149
149
|
prove another send is safe. Existing pending conversations are reconciled; non-pending conversations stay with people. The user token must see all relevant inboxes.
|
|
150
150
|
|
|
151
|
-
For the first move from the relay's built-in routing (production baseline 0.27.0):
|
|
152
|
-
|
|
153
|
-
1. Record the live relay deployment/version id and CONFIG_KEY before rollout; retain its Hub and old secrets.
|
|
154
|
-
Disconnect all brand bots and prevent competing old deployments. Verify bot identities, owner membership,
|
|
155
|
-
labels, canned responses and inbox visibility. Historical confirmed brand-bot replies need no ledger migration.
|
|
156
|
-
2. Deploy and verify relay 0.30 first. Remove `routing`, `router.accounts` and `router.waitSeconds`; keep every kind
|
|
157
|
-
in `router.keepLabels`. Verify the actual version and that built-in routing/in-flight old work stopped.
|
|
158
|
-
3. Bootstrap this Worker by hand with all four secrets in a protected secrets file while bots remain disconnected.
|
|
159
|
-
Verify KV/domain, upstream permissions and test tickets; health/2xx alone do not prove routing success.
|
|
160
|
-
If using an infra PR whose push deploys both Workers independently, merge only after both hand deployments pass.
|
|
161
|
-
4. Connect bots one account at a time and verify real routing, handoff, failed/unknown reply handling, historical
|
|
162
|
-
reply deduplication and Discord/triage behavior, including at least a complete sweep.
|
|
163
|
-
5. To roll back, disconnect bots first and **keep this router running until no pending ticket remains assigned to
|
|
164
|
-
a disconnected brand bot and no non-pending ticket remains assigned to that bot**. Complete pending/open
|
|
165
|
-
account passes and let failed ending releases finish through their durable retries: pending uses native bot
|
|
166
|
-
handoff, open leftovers use explicit unassignment, and kind endings release resolved/snoozed tickets in the
|
|
167
|
-
same turn. Confirm cleared bot ownership across statuses; preserve human/other-bot owners. Then stop
|
|
168
|
-
webhook entry, cron and queued/in-flight router work; stopping cron alone does not cancel DO alarms. Preserve DO
|
|
169
|
-
namespace and storage, including reply and turn guards. Restore the recorded live 0.27 version **and its
|
|
170
|
-
CONFIG_KEY**, without overlapping old/new routing. Relay 0.27 uses its original Hub's
|
|
171
|
-
`kind-reply:<account>:<conversation>` records; the Router uses separate `reply:<account>:<conversation>`
|
|
172
|
-
records. Restoring code/config does not copy Router attempts into the Hub or reset either ledger. Only replies
|
|
173
|
-
already recorded by 0.27 retain its own reply-once protection; Router-only replies/unknown attempts can be
|
|
174
|
-
attempted again by 0.27. Keep both DOs and review/isolate those tickets before restoring old routing. Rollback
|
|
175
|
-
does not guarantee reply-once across this switch or undo sent messages or other mutations.
|
|
176
|
-
|
|
177
|
-
Current router upgrades need no custom coordination attributes, compatibility effects or one-time lifecycle
|
|
178
|
-
cleanup. Older unreadable job payloads are dropped; live snapshots and the account sweep reconstruct queued work, not durable reply/turn guards.
|
|
179
|
-
If handoff credentials fail, repair them or let a real owner take the tickets; do not bulk-open with the integration
|
|
180
|
-
user token, which can assign every ticket to that user. Keep old attributes through the owner's rollback window.
|
|
181
|
-
|
|
182
151
|
Redaction is best effort, not anonymization: other personal information can still reach TypeSafe. Logs contain
|
|
183
152
|
ids and outcomes, never message bodies or credentials. See [SECURITY.md](https://github.com/Phala-Network/chatwoot-workers/blob/main/SECURITY.md),
|
|
184
153
|
[CONTRIBUTING.md](https://github.com/Phala-Network/chatwoot-workers/blob/main/CONTRIBUTING.md) and [CHANGELOG.md](https://github.com/Phala-Network/chatwoot-workers/blob/main/packages/chatwoot-router/CHANGELOG.md). Licensed under [MIT](https://github.com/Phala-Network/chatwoot-workers/blob/main/packages/chatwoot-router/LICENSE).
|
|
@@ -95,10 +95,30 @@ var Budget = class {
|
|
|
95
95
|
if (this.used >= this.limit) throw new BudgetExhaustedError();
|
|
96
96
|
this.used += 1;
|
|
97
97
|
}
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
/**
|
|
99
|
+
* The timeout is cleared once the response body has been read (or there is none): a pending
|
|
100
|
+
* timer, such as `AbortSignal.timeout`'s, keeps the invocation open until it fires.
|
|
101
|
+
*/
|
|
102
|
+
fetch = async (request) => {
|
|
103
|
+
if (this.used >= this.limit) throw new BudgetExhaustedError();
|
|
100
104
|
this.used += 1;
|
|
101
|
-
|
|
105
|
+
const controller = new AbortController();
|
|
106
|
+
const timer = setTimeout(
|
|
107
|
+
() => controller.abort(new DOMException("The request timed out", "TimeoutError")),
|
|
108
|
+
this.timeoutMs
|
|
109
|
+
);
|
|
110
|
+
try {
|
|
111
|
+
const response = await this.fetchImpl(new Request(request, { signal: controller.signal }));
|
|
112
|
+
if (!response.body) {
|
|
113
|
+
clearTimeout(timer);
|
|
114
|
+
return response;
|
|
115
|
+
}
|
|
116
|
+
const body = response.body.pipeThrough(new TransformStream({ flush: () => clearTimeout(timer) }));
|
|
117
|
+
return new Response(body, response);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
clearTimeout(timer);
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
102
122
|
};
|
|
103
123
|
};
|
|
104
124
|
|
|
@@ -1281,7 +1301,8 @@ function eventTarget(payload) {
|
|
|
1281
1301
|
...change && (change.previous_value === "pending" || change.current_value === "resolved" || change.current_value === "pending" && change.previous_value !== "resolved") && event.updated_at !== void 0 ? { transition: { status: change.current_value, at: event.updated_at } } : {}
|
|
1282
1302
|
};
|
|
1283
1303
|
}
|
|
1284
|
-
if (event.event === "message_created" && (event.message_type === "incoming" || event.message_type === 0) && !event.private &&
|
|
1304
|
+
if (event.event === "message_created" && (event.message_type === "incoming" || event.message_type === 0) && !event.private && // A contact's webhook data has no `type` (Contact#webhook_data); a user's and a bot's do.
|
|
1305
|
+
(event.sender?.type ?? "contact") === "contact" && event.id !== void 0 && event.conversation) {
|
|
1285
1306
|
return { accountId: event.account.id, conversationId: event.conversation.id };
|
|
1286
1307
|
}
|
|
1287
1308
|
return void 0;
|
package/lib/shared/budget.d.ts
CHANGED
|
@@ -11,5 +11,9 @@ export declare class Budget {
|
|
|
11
11
|
get remaining(): number;
|
|
12
12
|
/** Counts a request that is not a fetch, such as a call to another Durable Object. */
|
|
13
13
|
consume(): void;
|
|
14
|
+
/**
|
|
15
|
+
* The timeout is cleared once the response body has been read (or there is none): a pending
|
|
16
|
+
* timer, such as `AbortSignal.timeout`'s, keeps the invocation open until it fires.
|
|
17
|
+
*/
|
|
14
18
|
readonly fetch: Fetch;
|
|
15
19
|
}
|
|
@@ -232,8 +232,6 @@ export declare function personAssignee(conversation: ChatwootConversation): {
|
|
|
232
232
|
custom_role?: Record<string, unknown> | null;
|
|
233
233
|
}[];
|
|
234
234
|
} | undefined;
|
|
235
|
-
/** An actual public answer; Chatwoot templates include greetings and out-of-office notices. */
|
|
236
|
-
export declare function isAnsweringReply(message: ChatwootMessage): boolean;
|
|
237
235
|
export declare function toRelayConversation(conversationId: number, conversation: ChatwootConversation): RelayConversation;
|
|
238
236
|
export declare function toRelayMessage(message: ChatwootMessage, context: {
|
|
239
237
|
account: {
|