chatwoot-router 0.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 +23 -0
- package/LICENSE +21 -0
- package/README.md +296 -0
- package/lib/packages/chatwoot-router/scripts/store-config.js +176 -0
- package/lib/packages/chatwoot-router/scripts/stored-config.d.ts +4 -0
- package/lib/packages/chatwoot-router/scripts/stored-config.js +128 -0
- package/lib/packages/chatwoot-router/src/config.d.ts +46 -0
- package/lib/packages/chatwoot-router/src/effects.d.ts +14 -0
- package/lib/packages/chatwoot-router/src/env.d.ts +9 -0
- package/lib/packages/chatwoot-router/src/index.d.ts +7 -0
- package/lib/packages/chatwoot-router/src/index.js +1201 -0
- package/lib/packages/chatwoot-router/src/router.d.ts +14 -0
- package/lib/packages/chatwoot-router/src/routing.d.ts +19 -0
- package/lib/packages/chatwoot-router/src/settings.d.ts +3 -0
- package/lib/packages/chatwoot-router/src/webhook.d.ts +4 -0
- package/lib/shared/attributes.d.ts +6 -0
- package/lib/shared/budget.d.ts +13 -0
- package/lib/shared/chatwoot/api.d.ts +199 -0
- package/lib/shared/chatwoot/schema.d.ts +13450 -0
- package/lib/shared/chatwoot/signature.d.ts +3 -0
- package/lib/shared/config.d.ts +10 -0
- package/lib/shared/json.d.ts +4 -0
- package/lib/shared/log.d.ts +9 -0
- package/lib/shared/settings.d.ts +6 -0
- package/lib/shared/store.d.ts +37 -0
- package/lib/shared/stored-config.d.ts +6 -0
- package/lib/shared/sweep.d.ts +11 -0
- package/lib/shared/types.d.ts +86 -0
- package/package.json +73 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { DurableObject } from "cloudflare:workers";
|
|
2
|
+
import type { Env } from "./env.ts";
|
|
3
|
+
export declare const ROUTER_NAME = "global";
|
|
4
|
+
export declare const ROUTE_BUDGET = 15;
|
|
5
|
+
export declare class Router extends DurableObject<Env> {
|
|
6
|
+
private readonly store;
|
|
7
|
+
constructor(context: DurableObjectState, env: Env);
|
|
8
|
+
enqueueConversation(accountId: number, conversationId: number): Promise<void>;
|
|
9
|
+
requestSweep(): Promise<void>;
|
|
10
|
+
alarm(): Promise<void>;
|
|
11
|
+
private sweep;
|
|
12
|
+
private enqueue;
|
|
13
|
+
private schedule;
|
|
14
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ChatwootClient, type Fetch } from "../../../shared/chatwoot/api.ts";
|
|
2
|
+
import type { Settings } from "./config.ts";
|
|
3
|
+
/** Jev's answer when no owner fits; also the reserved route name. */
|
|
4
|
+
export declare const UNCLEAR = "unclear";
|
|
5
|
+
export interface RoutingStore {
|
|
6
|
+
get(key: string): string | undefined;
|
|
7
|
+
list(start: string, end: string): string[];
|
|
8
|
+
set(key: string, value: string): void;
|
|
9
|
+
}
|
|
10
|
+
export interface RoutingContext {
|
|
11
|
+
settings: Settings;
|
|
12
|
+
store: RoutingStore;
|
|
13
|
+
chatwoot: ChatwootClient;
|
|
14
|
+
/** Counts toward the invocation's subrequest budget, like the Chatwoot client's. */
|
|
15
|
+
fetch: Fetch;
|
|
16
|
+
}
|
|
17
|
+
export declare function routesAccount(settings: Settings, accountId: number): boolean;
|
|
18
|
+
export declare function routeConversation(ctx: RoutingContext, accountId: number, conversationId: number): Promise<"defer" | undefined>;
|
|
19
|
+
export declare function sanitize(text: string, identities: Array<string | null | undefined>): string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Fetch } from "./chatwoot/api.ts";
|
|
2
|
+
export declare class BudgetExhaustedError extends Error {
|
|
3
|
+
constructor();
|
|
4
|
+
}
|
|
5
|
+
export declare class Budget {
|
|
6
|
+
readonly limit: number;
|
|
7
|
+
private readonly fetchImpl;
|
|
8
|
+
private readonly timeoutMs;
|
|
9
|
+
private used;
|
|
10
|
+
constructor(limit: number, fetchImpl?: Fetch, timeoutMs?: number);
|
|
11
|
+
get remaining(): number;
|
|
12
|
+
readonly fetch: Fetch;
|
|
13
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { RelayConversation, RelayMessage } from "../types.ts";
|
|
3
|
+
import type { components, operations } from "./schema.ts";
|
|
4
|
+
export type Fetch = (input: Request) => Promise<Response>;
|
|
5
|
+
/** A non-2xx answer from Chatwoot. Only the status is kept: bodies may echo user content. */
|
|
6
|
+
export declare class ChatwootError extends Error {
|
|
7
|
+
readonly status: number;
|
|
8
|
+
readonly conversationMissing: boolean;
|
|
9
|
+
constructor(status: number, operation: string, conversationMissing?: boolean);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A conversation as returned by GET conversations/{id} and in the conversation list. The spec's
|
|
13
|
+
* `status` enum omits `snoozed`, which the API does return, so the relay treats it as a string.
|
|
14
|
+
*/
|
|
15
|
+
export type ChatwootConversation = components["schemas"]["conversation_show"];
|
|
16
|
+
declare const messageSchema: z.ZodObject<{
|
|
17
|
+
id: z.ZodNumber;
|
|
18
|
+
content: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
19
|
+
message_type: z.ZodNumber;
|
|
20
|
+
content_type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
21
|
+
status: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
22
|
+
created_at: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
23
|
+
private: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
24
|
+
content_attributes: z.ZodCatch<z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
25
|
+
email: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
26
|
+
subject: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
27
|
+
auto_reply: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
28
|
+
text_content: z.ZodCatch<z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
29
|
+
quoted: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
30
|
+
}, z.core.$strip>>>>;
|
|
31
|
+
html_content: z.ZodCatch<z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
32
|
+
quoted: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
33
|
+
}, z.core.$strip>>>>;
|
|
34
|
+
}, z.core.$strip>>>;
|
|
35
|
+
deleted: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
36
|
+
external_error: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
37
|
+
submitted_values: z.ZodOptional<z.ZodUnknown>;
|
|
38
|
+
submitted_email: z.ZodOptional<z.ZodUnknown>;
|
|
39
|
+
items: z.ZodOptional<z.ZodUnknown>;
|
|
40
|
+
}, z.core.$strip>>>>;
|
|
41
|
+
sender: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
42
|
+
id: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
43
|
+
name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
44
|
+
email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
45
|
+
type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
46
|
+
thumbnail: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
47
|
+
avatar_url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
48
|
+
}, z.core.$strip>>>;
|
|
49
|
+
attachments: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
|
|
50
|
+
file_type: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
51
|
+
data_url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
52
|
+
fallback_title: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
53
|
+
coordinates_lat: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
54
|
+
coordinates_long: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
55
|
+
meta: z.ZodCatch<z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
56
|
+
firstName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
57
|
+
lastName: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
58
|
+
first_name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
59
|
+
last_name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
60
|
+
}, z.core.$strip>>>>;
|
|
61
|
+
}, z.core.$strip>>>>;
|
|
62
|
+
}, z.core.$strip>;
|
|
63
|
+
export type ChatwootMessage = z.infer<typeof messageSchema>;
|
|
64
|
+
/** Conversations per page of the list endpoint (ConversationFinder's CONVERSATION_RESULTS_PER_PAGE default, v4.18.0). */
|
|
65
|
+
export declare const CONVERSATIONS_PER_PAGE = 25;
|
|
66
|
+
/** Chatwoot returns up to 100 messages per `after` page. */
|
|
67
|
+
export declare const MESSAGE_PAGE_SIZE = 100;
|
|
68
|
+
/** The body of POST conversations/{id}/toggle_status. */
|
|
69
|
+
export type StatusChange = operations["toggle-status-of-a-conversation"]["requestBody"]["content"]["application/json"];
|
|
70
|
+
/** A priority for POST conversations/{id}/toggle_priority; null clears it. */
|
|
71
|
+
type ConversationPriority = NonNullable<NonNullable<operations["toggle-priority-of-a-conversation"]["requestBody"]>["content"]["application/json"]["priority"]>;
|
|
72
|
+
interface NewMessage {
|
|
73
|
+
content: string;
|
|
74
|
+
private: boolean;
|
|
75
|
+
files: ReadonlyArray<{
|
|
76
|
+
blob: Blob;
|
|
77
|
+
filename: string;
|
|
78
|
+
}>;
|
|
79
|
+
/**
|
|
80
|
+
* Send an email reply from the agent's own mailbox name on the inbox's domain. Phala's Chatwoot
|
|
81
|
+
* build reads `content_attributes.send_as_agent` (Email::AgentAddressBuilder); upstream ignores it.
|
|
82
|
+
*/
|
|
83
|
+
sendAsAgent?: boolean;
|
|
84
|
+
}
|
|
85
|
+
export type ChatwootClient = ReturnType<typeof chatwootClient>;
|
|
86
|
+
type MessageQuery = NonNullable<operations["list-all-messages"]["parameters"]["query"]> & {
|
|
87
|
+
filter_internal_messages?: true;
|
|
88
|
+
};
|
|
89
|
+
export declare function chatwootClient(baseUrl: string, token: string, fetch: Fetch): {
|
|
90
|
+
/** The conversation, or undefined when it does not exist (it was deleted). */
|
|
91
|
+
getConversation(accountId: number, conversationId: number): Promise<ChatwootConversation | undefined>;
|
|
92
|
+
/** Messages with an id above `after`, oldest first; without `after`, the latest page. */
|
|
93
|
+
listMessages(accountId: number, conversationId: number, query?: MessageQuery): Promise<ChatwootMessage[]>;
|
|
94
|
+
/**
|
|
95
|
+
* One message, or undefined if it or its conversation does not exist. Asks for the ids between
|
|
96
|
+
* `messageId - 1` and `messageId + 1`, which contains it whether `after` is read inclusively
|
|
97
|
+
* (MessageFinder#messages_between at v4.18.0) or exclusively (as the spec describes it).
|
|
98
|
+
*/
|
|
99
|
+
getMessage(accountId: number, conversationId: number, messageId: number): Promise<ChatwootMessage | undefined>;
|
|
100
|
+
inboxName(accountId: number, inboxId: number): Promise<string | undefined>;
|
|
101
|
+
/**
|
|
102
|
+
* One page (CONVERSATIONS_PER_PAGE) of conversations of any assignee and the given status
|
|
103
|
+
* (default: any), most recent activity first (Conversations::SortService's default,
|
|
104
|
+
* `last_activity_at_desc`, at v4.18.0).
|
|
105
|
+
*/
|
|
106
|
+
listConversations(accountId: number, page: number, status?: "all" | "open" | "snoozed"): Promise<ChatwootConversation[]>;
|
|
107
|
+
/** The content of the account's canned response with this short code, if it exists. */
|
|
108
|
+
cannedResponse(accountId: number, shortCode: string): Promise<string | undefined>;
|
|
109
|
+
getProfile(): Promise<{
|
|
110
|
+
id?: number;
|
|
111
|
+
access_token?: string;
|
|
112
|
+
account_id?: number;
|
|
113
|
+
available_name?: string;
|
|
114
|
+
avatar_url?: string;
|
|
115
|
+
confirmed?: boolean;
|
|
116
|
+
display_name?: string | null;
|
|
117
|
+
message_signature?: string | null;
|
|
118
|
+
email?: string;
|
|
119
|
+
hmac_identifier?: string;
|
|
120
|
+
inviter_id?: number | null;
|
|
121
|
+
name?: string;
|
|
122
|
+
provider?: string;
|
|
123
|
+
pubsub_token?: string;
|
|
124
|
+
role?: "agent" | "administrator";
|
|
125
|
+
ui_settings?: {
|
|
126
|
+
[x: string]: unknown;
|
|
127
|
+
};
|
|
128
|
+
uid?: string;
|
|
129
|
+
type?: string | null;
|
|
130
|
+
custom_attributes?: {
|
|
131
|
+
[x: string]: unknown;
|
|
132
|
+
};
|
|
133
|
+
accounts?: {
|
|
134
|
+
id?: number;
|
|
135
|
+
name?: string;
|
|
136
|
+
status?: string;
|
|
137
|
+
active_at?: string | null;
|
|
138
|
+
role?: "administrator" | "agent";
|
|
139
|
+
permissions?: string[];
|
|
140
|
+
availability?: string;
|
|
141
|
+
availability_status?: string;
|
|
142
|
+
auto_offline?: boolean;
|
|
143
|
+
custom_role_id?: number | null;
|
|
144
|
+
custom_role?: Record<string, unknown> | null;
|
|
145
|
+
}[];
|
|
146
|
+
}>;
|
|
147
|
+
listAgents(accountId: number): Promise<{
|
|
148
|
+
id?: number;
|
|
149
|
+
account_id?: number;
|
|
150
|
+
readonly availability_status?: "online" | "busy" | "offline";
|
|
151
|
+
auto_offline?: boolean;
|
|
152
|
+
confirmed?: boolean;
|
|
153
|
+
email?: string;
|
|
154
|
+
available_name?: string;
|
|
155
|
+
name?: string;
|
|
156
|
+
role?: "agent" | "administrator";
|
|
157
|
+
thumbnail?: string;
|
|
158
|
+
custom_role_id?: number | null;
|
|
159
|
+
}[]>;
|
|
160
|
+
setStatus(accountId: number, conversationId: number, change: StatusChange): Promise<void>;
|
|
161
|
+
setPriority(accountId: number, conversationId: number, priority: ConversationPriority | null): Promise<void>;
|
|
162
|
+
setContactBlocked(accountId: number, contactId: number, blocked: boolean): Promise<void>;
|
|
163
|
+
assign(accountId: number, conversationId: number, assigneeId: number): Promise<void>;
|
|
164
|
+
/**
|
|
165
|
+
* Removes the conversation's assignee the way Chatwoot's dashboard does (ConversationAction.vue
|
|
166
|
+
* posts `assignee_id: null`, which AssignmentsController#create applies, at v4.18.0). The
|
|
167
|
+
* spec types `assignee_id` as a number only, so this body is serialized here.
|
|
168
|
+
*/
|
|
169
|
+
unassign(accountId: number, conversationId: number): Promise<void>;
|
|
170
|
+
/** The account's label names (Chatwoot saves them in lower case). */
|
|
171
|
+
listLabels(accountId: number): Promise<string[]>;
|
|
172
|
+
conversationLabels(accountId: number, conversationId: number): Promise<string[]>;
|
|
173
|
+
/** Replaces the conversation's labels (the spec's "Add Labels" overwrites the list). */
|
|
174
|
+
setLabels(accountId: number, conversationId: number, labels: string[]): Promise<void>;
|
|
175
|
+
setCustomAttributes: (accountId: number, conversationId: number, attributes: Record<string, unknown>) => Promise<void>;
|
|
176
|
+
/**
|
|
177
|
+
* Sends an outgoing message (or private note). With files it is sent as the spec's
|
|
178
|
+
* multipart/form-data body; the generated type renders its `attachments[]` binaries as
|
|
179
|
+
* strings, so the serializer appends the files itself.
|
|
180
|
+
*/
|
|
181
|
+
createMessage(accountId: number, conversationId: number, message: NewMessage): Promise<void>;
|
|
182
|
+
};
|
|
183
|
+
export declare function toRelayConversation(conversationId: number, conversation: ChatwootConversation): RelayConversation;
|
|
184
|
+
export declare function toRelayMessage(message: ChatwootMessage, context: {
|
|
185
|
+
account: {
|
|
186
|
+
id: number;
|
|
187
|
+
name: string;
|
|
188
|
+
};
|
|
189
|
+
inboxName?: string | null;
|
|
190
|
+
conversation: RelayConversation;
|
|
191
|
+
mentionedAgents?: ReadonlyMap<number, string>;
|
|
192
|
+
discordAvatarUrl?: string;
|
|
193
|
+
}): RelayMessage;
|
|
194
|
+
/**
|
|
195
|
+
* What Chatwoot itself shows and forwards (Message#ensure_processed_message_content, used by its
|
|
196
|
+
* Slack integration at v4.18.0): for an email, the reply without its quoted history.
|
|
197
|
+
*/
|
|
198
|
+
export declare function messageContent(message: ChatwootMessage): string;
|
|
199
|
+
export {};
|