chat-adapter-messenger 1.0.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/LICENSE +21 -0
- package/README.md +52 -0
- package/dist/index.d.ts +162 -0
- package/dist/index.js +745 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andrian Lloyd Maagma
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# chat-adapter-messenger
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/chat-adapter-messenger)
|
|
4
|
+
[](https://www.npmjs.com/package/chat-adapter-messenger)
|
|
5
|
+
|
|
6
|
+
Messenger adapter for [Chat SDK](https://chat-sdk.dev/docs).
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install chat chat-adapter-messenger
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { Chat } from "chat";
|
|
18
|
+
import { createMessengerAdapter } from "chat-adapter-messenger";
|
|
19
|
+
|
|
20
|
+
const bot = new Chat({
|
|
21
|
+
userName: "mybot",
|
|
22
|
+
adapters: {
|
|
23
|
+
messenger: createMessengerAdapter(),
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
bot.onNewMention(async (thread, message) => {
|
|
28
|
+
await thread.post("Hello from Messenger!");
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Environment variables
|
|
33
|
+
|
|
34
|
+
| Variable | Required | Description |
|
|
35
|
+
| ---------------------------- | -------- | -------------------------------------------------------- |
|
|
36
|
+
| `FACEBOOK_APP_SECRET` | Yes | Facebook app secret (required for Messenger integration) |
|
|
37
|
+
| `FACEBOOK_PAGE_ACCESS_TOKEN` | Yes | Facebook page access token |
|
|
38
|
+
| `FACEBOOK_VERIFY_TOKEN` | Yes | Verification token for webhook setup |
|
|
39
|
+
|
|
40
|
+
## Platform setup
|
|
41
|
+
|
|
42
|
+
1. Create an app on the Facebook Developer dashboard
|
|
43
|
+
2. Add the Messenger use case to your app
|
|
44
|
+
3. Generate a Page Access Token for your Facebook Page
|
|
45
|
+
4. Set the webhook URL to `https://your-domain.com/api/webhooks/facebook`
|
|
46
|
+
5. Provide your `FACEBOOK_VERIFY_TOKEN` during webhook verification
|
|
47
|
+
6. Subscribe to the `messages` and `messaging_postbacks` events
|
|
48
|
+
7. Ensure your app is in live mode (or add test users during development)
|
|
49
|
+
|
|
50
|
+
## License
|
|
51
|
+
|
|
52
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { BaseFormatConverter, Root, AdapterPostableMessage, Adapter, Logger, ChatInstance, WebhookOptions, RawMessage, EmojiValue, FetchOptions, FetchResult, Message, ThreadInfo, ChannelInfo, FormattedContent } from 'chat';
|
|
2
|
+
|
|
3
|
+
interface MessengerAdapterConfig {
|
|
4
|
+
apiVersion?: string;
|
|
5
|
+
appSecret: string;
|
|
6
|
+
pageAccessToken: string;
|
|
7
|
+
verifyToken: string;
|
|
8
|
+
}
|
|
9
|
+
interface MessengerThreadId {
|
|
10
|
+
recipientId: string;
|
|
11
|
+
}
|
|
12
|
+
interface MessengerSender {
|
|
13
|
+
id: string;
|
|
14
|
+
}
|
|
15
|
+
interface MessengerRecipient {
|
|
16
|
+
id: string;
|
|
17
|
+
}
|
|
18
|
+
interface MessengerAttachmentPayload {
|
|
19
|
+
sticker_id?: number;
|
|
20
|
+
url?: string;
|
|
21
|
+
}
|
|
22
|
+
interface MessengerAttachment {
|
|
23
|
+
payload?: MessengerAttachmentPayload;
|
|
24
|
+
type: "image" | "video" | "audio" | "file" | "fallback" | "location";
|
|
25
|
+
}
|
|
26
|
+
interface MessengerQuickReply {
|
|
27
|
+
payload: string;
|
|
28
|
+
}
|
|
29
|
+
interface MessengerMessagePayload {
|
|
30
|
+
attachments?: MessengerAttachment[];
|
|
31
|
+
is_echo?: boolean;
|
|
32
|
+
mid: string;
|
|
33
|
+
quick_reply?: MessengerQuickReply;
|
|
34
|
+
text?: string;
|
|
35
|
+
}
|
|
36
|
+
interface MessengerDelivery {
|
|
37
|
+
mids?: string[];
|
|
38
|
+
watermark: number;
|
|
39
|
+
}
|
|
40
|
+
interface MessengerRead {
|
|
41
|
+
watermark: number;
|
|
42
|
+
}
|
|
43
|
+
interface MessengerPostback {
|
|
44
|
+
mid?: string;
|
|
45
|
+
payload: string;
|
|
46
|
+
title: string;
|
|
47
|
+
}
|
|
48
|
+
interface MessengerReaction {
|
|
49
|
+
action: "react" | "unreact";
|
|
50
|
+
emoji: string;
|
|
51
|
+
mid: string;
|
|
52
|
+
reaction: string;
|
|
53
|
+
}
|
|
54
|
+
interface MessengerMessagingEvent {
|
|
55
|
+
delivery?: MessengerDelivery;
|
|
56
|
+
message?: MessengerMessagePayload;
|
|
57
|
+
postback?: MessengerPostback;
|
|
58
|
+
reaction?: MessengerReaction;
|
|
59
|
+
read?: MessengerRead;
|
|
60
|
+
recipient: MessengerRecipient;
|
|
61
|
+
sender: MessengerSender;
|
|
62
|
+
timestamp: number;
|
|
63
|
+
}
|
|
64
|
+
interface MessengerWebhookEntry {
|
|
65
|
+
id: string;
|
|
66
|
+
messaging: MessengerMessagingEvent[];
|
|
67
|
+
time: number;
|
|
68
|
+
}
|
|
69
|
+
interface MessengerWebhookPayload {
|
|
70
|
+
entry: MessengerWebhookEntry[];
|
|
71
|
+
object: string;
|
|
72
|
+
}
|
|
73
|
+
interface MessengerSendApiResponse {
|
|
74
|
+
message_id: string;
|
|
75
|
+
recipient_id: string;
|
|
76
|
+
}
|
|
77
|
+
interface MessengerUserProfile {
|
|
78
|
+
first_name?: string;
|
|
79
|
+
id: string;
|
|
80
|
+
last_name?: string;
|
|
81
|
+
profile_pic?: string;
|
|
82
|
+
}
|
|
83
|
+
type MessengerRawMessage = MessengerMessagingEvent;
|
|
84
|
+
|
|
85
|
+
declare class MessengerFormatConverter extends BaseFormatConverter {
|
|
86
|
+
fromAst(ast: Root): string;
|
|
87
|
+
toAst(text: string): Root;
|
|
88
|
+
renderPostable(message: AdapterPostableMessage): string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
declare class MessengerAdapter implements Adapter<MessengerThreadId, MessengerRawMessage> {
|
|
92
|
+
readonly name = "messenger";
|
|
93
|
+
private readonly appSecret;
|
|
94
|
+
private readonly pageAccessToken;
|
|
95
|
+
private readonly verifyToken;
|
|
96
|
+
private readonly apiVersion;
|
|
97
|
+
private readonly logger;
|
|
98
|
+
private readonly formatConverter;
|
|
99
|
+
private readonly messageCache;
|
|
100
|
+
private readonly userProfileCache;
|
|
101
|
+
private chat;
|
|
102
|
+
private _botUserId?;
|
|
103
|
+
private _userName;
|
|
104
|
+
private readonly hasExplicitUserName;
|
|
105
|
+
get botUserId(): string | undefined;
|
|
106
|
+
get userName(): string;
|
|
107
|
+
constructor(config: MessengerAdapterConfig & {
|
|
108
|
+
logger: Logger;
|
|
109
|
+
userName?: string;
|
|
110
|
+
});
|
|
111
|
+
initialize(chat: ChatInstance): Promise<void>;
|
|
112
|
+
handleWebhook(request: Request, options?: WebhookOptions): Promise<Response>;
|
|
113
|
+
private handleVerification;
|
|
114
|
+
private verifySignature;
|
|
115
|
+
private handleIncomingMessage;
|
|
116
|
+
private handlePostback;
|
|
117
|
+
private handleEcho;
|
|
118
|
+
private handleReaction;
|
|
119
|
+
postMessage(threadId: string, message: AdapterPostableMessage): Promise<RawMessage<MessengerRawMessage>>;
|
|
120
|
+
editMessage(_threadId: string, _messageId: string, _message: AdapterPostableMessage): Promise<RawMessage<MessengerRawMessage>>;
|
|
121
|
+
deleteMessage(_threadId: string, _messageId: string): Promise<void>;
|
|
122
|
+
addReaction(_threadId: string, _messageId: string, _emoji: EmojiValue | string): Promise<void>;
|
|
123
|
+
removeReaction(_threadId: string, _messageId: string, _emoji: EmojiValue | string): Promise<void>;
|
|
124
|
+
startTyping(threadId: string): Promise<void>;
|
|
125
|
+
fetchMessages(threadId: string, options?: FetchOptions): Promise<FetchResult<MessengerRawMessage>>;
|
|
126
|
+
fetchMessage(_threadId: string, messageId: string): Promise<Message<MessengerRawMessage> | null>;
|
|
127
|
+
fetchThread(threadId: string): Promise<ThreadInfo>;
|
|
128
|
+
fetchChannelInfo(channelId: string): Promise<ChannelInfo>;
|
|
129
|
+
channelIdFromThreadId(threadId: string): string;
|
|
130
|
+
openDM(userId: string): Promise<string>;
|
|
131
|
+
isDM(_threadId: string): boolean;
|
|
132
|
+
encodeThreadId(platformData: MessengerThreadId): string;
|
|
133
|
+
decodeThreadId(threadId: string): MessengerThreadId;
|
|
134
|
+
parseMessage(raw: MessengerRawMessage): Message<MessengerRawMessage>;
|
|
135
|
+
renderFormatted(content: FormattedContent): string;
|
|
136
|
+
private parseMessengerMessage;
|
|
137
|
+
private extractAttachments;
|
|
138
|
+
private mapAttachmentType;
|
|
139
|
+
private downloadAttachment;
|
|
140
|
+
private fetchUserProfile;
|
|
141
|
+
private profileDisplayName;
|
|
142
|
+
private resolveThreadId;
|
|
143
|
+
private truncateMessage;
|
|
144
|
+
private truncateQuickReplyTitle;
|
|
145
|
+
private buildQuickRepliesFromCard;
|
|
146
|
+
private collectButtons;
|
|
147
|
+
private encodeQuickReplyPayload;
|
|
148
|
+
private decodeQuickReplyPayload;
|
|
149
|
+
private paginateMessages;
|
|
150
|
+
private cacheMessage;
|
|
151
|
+
private findCachedMessage;
|
|
152
|
+
private compareMessages;
|
|
153
|
+
private messageSequence;
|
|
154
|
+
private graphApiFetch;
|
|
155
|
+
private throwGraphApiError;
|
|
156
|
+
}
|
|
157
|
+
declare function createMessengerAdapter(config?: Partial<MessengerAdapterConfig & {
|
|
158
|
+
logger: Logger;
|
|
159
|
+
userName?: string;
|
|
160
|
+
}>): MessengerAdapter;
|
|
161
|
+
|
|
162
|
+
export { MessengerAdapter, type MessengerAdapterConfig, MessengerFormatConverter, type MessengerMessagingEvent, type MessengerRawMessage, type MessengerReaction, type MessengerSendApiResponse, type MessengerThreadId, type MessengerUserProfile, type MessengerWebhookPayload, createMessengerAdapter };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,745 @@
|
|
|
1
|
+
// src/adapter.ts
|
|
2
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
3
|
+
import {
|
|
4
|
+
AdapterRateLimitError,
|
|
5
|
+
AuthenticationError,
|
|
6
|
+
cardToFallbackText,
|
|
7
|
+
extractCard,
|
|
8
|
+
NetworkError,
|
|
9
|
+
ResourceNotFoundError,
|
|
10
|
+
ValidationError
|
|
11
|
+
} from "@chat-adapter/shared";
|
|
12
|
+
import {
|
|
13
|
+
ConsoleLogger,
|
|
14
|
+
convertEmojiPlaceholders,
|
|
15
|
+
getEmoji,
|
|
16
|
+
Message
|
|
17
|
+
} from "chat";
|
|
18
|
+
|
|
19
|
+
// src/format-converter.ts
|
|
20
|
+
import {
|
|
21
|
+
BaseFormatConverter,
|
|
22
|
+
parseMarkdown,
|
|
23
|
+
stringifyMarkdown
|
|
24
|
+
} from "chat";
|
|
25
|
+
var MessengerFormatConverter = class extends BaseFormatConverter {
|
|
26
|
+
fromAst(ast) {
|
|
27
|
+
return stringifyMarkdown(ast).trim();
|
|
28
|
+
}
|
|
29
|
+
toAst(text) {
|
|
30
|
+
return parseMarkdown(text);
|
|
31
|
+
}
|
|
32
|
+
renderPostable(message) {
|
|
33
|
+
if (typeof message === "string") {
|
|
34
|
+
return message;
|
|
35
|
+
}
|
|
36
|
+
if ("raw" in message) {
|
|
37
|
+
return message.raw;
|
|
38
|
+
}
|
|
39
|
+
if ("markdown" in message) {
|
|
40
|
+
return this.fromMarkdown(message.markdown);
|
|
41
|
+
}
|
|
42
|
+
if ("ast" in message) {
|
|
43
|
+
return this.fromAst(message.ast);
|
|
44
|
+
}
|
|
45
|
+
return super.renderPostable(message);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// src/adapter.ts
|
|
50
|
+
var GRAPH_API_BASE = "https://graph.facebook.com";
|
|
51
|
+
var DEFAULT_API_VERSION = "v21.0";
|
|
52
|
+
var MESSENGER_MESSAGE_LIMIT = 2e3;
|
|
53
|
+
var MESSENGER_QUICK_REPLY_LIMIT = 13;
|
|
54
|
+
var MESSENGER_QUICK_REPLY_TITLE_LIMIT = 20;
|
|
55
|
+
var MESSAGE_SEQUENCE_PATTERN = /:(\d+)$/;
|
|
56
|
+
var QUICK_REPLY_PAYLOAD_TAG = "chat-sdk-button";
|
|
57
|
+
var MessengerAdapter = class {
|
|
58
|
+
name = "messenger";
|
|
59
|
+
appSecret;
|
|
60
|
+
pageAccessToken;
|
|
61
|
+
verifyToken;
|
|
62
|
+
apiVersion;
|
|
63
|
+
logger;
|
|
64
|
+
formatConverter = new MessengerFormatConverter();
|
|
65
|
+
messageCache = /* @__PURE__ */ new Map();
|
|
66
|
+
userProfileCache = /* @__PURE__ */ new Map();
|
|
67
|
+
chat = null;
|
|
68
|
+
_botUserId;
|
|
69
|
+
_userName;
|
|
70
|
+
hasExplicitUserName;
|
|
71
|
+
get botUserId() {
|
|
72
|
+
return this._botUserId;
|
|
73
|
+
}
|
|
74
|
+
get userName() {
|
|
75
|
+
return this._userName;
|
|
76
|
+
}
|
|
77
|
+
constructor(config) {
|
|
78
|
+
this.appSecret = config.appSecret;
|
|
79
|
+
this.pageAccessToken = config.pageAccessToken;
|
|
80
|
+
this.verifyToken = config.verifyToken;
|
|
81
|
+
this.apiVersion = config.apiVersion ?? DEFAULT_API_VERSION;
|
|
82
|
+
this.logger = config.logger;
|
|
83
|
+
this._userName = config.userName ?? "bot";
|
|
84
|
+
this.hasExplicitUserName = Boolean(config.userName);
|
|
85
|
+
}
|
|
86
|
+
async initialize(chat) {
|
|
87
|
+
this.chat = chat;
|
|
88
|
+
if (!this.hasExplicitUserName) {
|
|
89
|
+
this._userName = chat.getUserName();
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const me = await this.graphApiFetch(
|
|
93
|
+
"me",
|
|
94
|
+
"GET"
|
|
95
|
+
);
|
|
96
|
+
this._botUserId = me.id;
|
|
97
|
+
if (!this.hasExplicitUserName && me.name) {
|
|
98
|
+
this._userName = me.name;
|
|
99
|
+
}
|
|
100
|
+
this.logger.info("Messenger adapter initialized", {
|
|
101
|
+
botUserId: this._botUserId,
|
|
102
|
+
userName: this._userName
|
|
103
|
+
});
|
|
104
|
+
} catch (error) {
|
|
105
|
+
this.logger.warn("Failed to fetch Messenger page identity", {
|
|
106
|
+
error: String(error)
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async handleWebhook(request, options) {
|
|
111
|
+
if (request.method === "GET") {
|
|
112
|
+
return this.handleVerification(request);
|
|
113
|
+
}
|
|
114
|
+
const body = await request.text();
|
|
115
|
+
if (!this.verifySignature(request, body)) {
|
|
116
|
+
this.logger.warn("Messenger webhook rejected due to invalid signature");
|
|
117
|
+
return new Response("Invalid signature", { status: 403 });
|
|
118
|
+
}
|
|
119
|
+
let payload;
|
|
120
|
+
try {
|
|
121
|
+
payload = JSON.parse(body);
|
|
122
|
+
} catch {
|
|
123
|
+
return new Response("Invalid JSON", { status: 400 });
|
|
124
|
+
}
|
|
125
|
+
if (payload.object !== "page") {
|
|
126
|
+
return new Response("Not a page subscription", { status: 404 });
|
|
127
|
+
}
|
|
128
|
+
if (!this.chat) {
|
|
129
|
+
this.logger.warn(
|
|
130
|
+
"Chat instance not initialized, ignoring Messenger webhook"
|
|
131
|
+
);
|
|
132
|
+
return new Response("EVENT_RECEIVED", { status: 200 });
|
|
133
|
+
}
|
|
134
|
+
for (const entry of payload.entry) {
|
|
135
|
+
for (const event of entry.messaging) {
|
|
136
|
+
if (event.message && !event.message.is_echo) {
|
|
137
|
+
this.handleIncomingMessage(event, options);
|
|
138
|
+
}
|
|
139
|
+
if (event.message?.is_echo) {
|
|
140
|
+
this.handleEcho(event);
|
|
141
|
+
}
|
|
142
|
+
if (event.postback) {
|
|
143
|
+
this.handlePostback(event, options);
|
|
144
|
+
}
|
|
145
|
+
if (event.reaction) {
|
|
146
|
+
this.handleReaction(event, options);
|
|
147
|
+
}
|
|
148
|
+
if (event.delivery) {
|
|
149
|
+
this.logger.debug("Message delivery confirmation", {
|
|
150
|
+
watermark: event.delivery.watermark,
|
|
151
|
+
mids: event.delivery.mids
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
if (event.read) {
|
|
155
|
+
this.logger.debug("Message read confirmation", {
|
|
156
|
+
watermark: event.read.watermark
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return new Response("EVENT_RECEIVED", { status: 200 });
|
|
162
|
+
}
|
|
163
|
+
handleVerification(request) {
|
|
164
|
+
const url = new URL(request.url);
|
|
165
|
+
const mode = url.searchParams.get("hub.mode");
|
|
166
|
+
const token = url.searchParams.get("hub.verify_token");
|
|
167
|
+
const challenge = url.searchParams.get("hub.challenge");
|
|
168
|
+
if (mode === "subscribe" && token === this.verifyToken) {
|
|
169
|
+
this.logger.info("Messenger webhook verified");
|
|
170
|
+
return new Response(challenge ?? "", { status: 200 });
|
|
171
|
+
}
|
|
172
|
+
this.logger.warn("Messenger webhook verification failed");
|
|
173
|
+
return new Response("Forbidden", { status: 403 });
|
|
174
|
+
}
|
|
175
|
+
verifySignature(request, body) {
|
|
176
|
+
const signature = request.headers.get("x-hub-signature-256");
|
|
177
|
+
if (!signature) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
const [algo, hash] = signature.split("=");
|
|
181
|
+
if (algo !== "sha256" || !hash) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
const computedHash = createHmac("sha256", this.appSecret).update(body, "utf8").digest("hex");
|
|
186
|
+
return timingSafeEqual(
|
|
187
|
+
Buffer.from(hash, "hex"),
|
|
188
|
+
Buffer.from(computedHash, "hex")
|
|
189
|
+
);
|
|
190
|
+
} catch {
|
|
191
|
+
this.logger.warn("Failed to verify Messenger webhook signature");
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
handleIncomingMessage(event, options) {
|
|
196
|
+
if (!this.chat) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const threadId = this.encodeThreadId({
|
|
200
|
+
recipientId: event.sender.id
|
|
201
|
+
});
|
|
202
|
+
const parsedMessage = this.parseMessengerMessage(event, threadId);
|
|
203
|
+
this.cacheMessage(parsedMessage);
|
|
204
|
+
const quickReplyPayload = event.message?.quick_reply?.payload;
|
|
205
|
+
if (quickReplyPayload) {
|
|
206
|
+
const action = this.decodeQuickReplyPayload(quickReplyPayload);
|
|
207
|
+
this.chat.processAction(
|
|
208
|
+
{
|
|
209
|
+
adapter: this,
|
|
210
|
+
actionId: action.actionId,
|
|
211
|
+
value: action.value,
|
|
212
|
+
messageId: event.message?.mid ?? `quick_reply:${event.timestamp}`,
|
|
213
|
+
threadId,
|
|
214
|
+
user: {
|
|
215
|
+
userId: event.sender.id,
|
|
216
|
+
userName: event.sender.id,
|
|
217
|
+
fullName: event.sender.id,
|
|
218
|
+
isBot: false,
|
|
219
|
+
isMe: false
|
|
220
|
+
},
|
|
221
|
+
raw: event
|
|
222
|
+
},
|
|
223
|
+
options
|
|
224
|
+
);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
this.chat.processMessage(this, threadId, parsedMessage, options);
|
|
228
|
+
}
|
|
229
|
+
handlePostback(event, options) {
|
|
230
|
+
if (!(this.chat && event.postback)) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const threadId = this.encodeThreadId({
|
|
234
|
+
recipientId: event.sender.id
|
|
235
|
+
});
|
|
236
|
+
this.chat.processAction(
|
|
237
|
+
{
|
|
238
|
+
adapter: this,
|
|
239
|
+
actionId: event.postback.payload,
|
|
240
|
+
value: event.postback.payload,
|
|
241
|
+
messageId: event.postback.mid ?? `postback:${event.timestamp}`,
|
|
242
|
+
threadId,
|
|
243
|
+
user: {
|
|
244
|
+
userId: event.sender.id,
|
|
245
|
+
userName: event.sender.id,
|
|
246
|
+
fullName: event.sender.id,
|
|
247
|
+
isBot: false,
|
|
248
|
+
isMe: false
|
|
249
|
+
},
|
|
250
|
+
raw: event
|
|
251
|
+
},
|
|
252
|
+
options
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
handleEcho(event) {
|
|
256
|
+
if (!event.message) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const threadId = this.encodeThreadId({
|
|
260
|
+
recipientId: event.recipient.id
|
|
261
|
+
});
|
|
262
|
+
const parsedMessage = this.parseMessengerMessage(event, threadId);
|
|
263
|
+
this.cacheMessage(parsedMessage);
|
|
264
|
+
}
|
|
265
|
+
handleReaction(event, options) {
|
|
266
|
+
if (!(this.chat && event.reaction)) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const threadId = this.encodeThreadId({
|
|
270
|
+
recipientId: event.sender.id
|
|
271
|
+
});
|
|
272
|
+
const added = event.reaction.action === "react";
|
|
273
|
+
this.chat.processReaction(
|
|
274
|
+
{
|
|
275
|
+
adapter: this,
|
|
276
|
+
threadId,
|
|
277
|
+
messageId: event.reaction.mid,
|
|
278
|
+
emoji: getEmoji(event.reaction.emoji),
|
|
279
|
+
rawEmoji: event.reaction.emoji,
|
|
280
|
+
added,
|
|
281
|
+
user: {
|
|
282
|
+
userId: event.sender.id,
|
|
283
|
+
userName: event.sender.id,
|
|
284
|
+
fullName: event.sender.id,
|
|
285
|
+
isBot: false,
|
|
286
|
+
isMe: false
|
|
287
|
+
},
|
|
288
|
+
raw: event
|
|
289
|
+
},
|
|
290
|
+
options
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
async postMessage(threadId, message) {
|
|
294
|
+
const { recipientId } = this.resolveThreadId(threadId);
|
|
295
|
+
const card = extractCard(message);
|
|
296
|
+
const quickReplies = card ? this.buildQuickRepliesFromCard(card) : void 0;
|
|
297
|
+
const text = this.truncateMessage(
|
|
298
|
+
convertEmojiPlaceholders(
|
|
299
|
+
card ? cardToFallbackText(card) : this.formatConverter.renderPostable(message),
|
|
300
|
+
"gchat"
|
|
301
|
+
)
|
|
302
|
+
);
|
|
303
|
+
if (!text.trim()) {
|
|
304
|
+
throw new ValidationError("messenger", "Message text cannot be empty");
|
|
305
|
+
}
|
|
306
|
+
const result = await this.graphApiFetch(
|
|
307
|
+
"me/messages",
|
|
308
|
+
"POST",
|
|
309
|
+
{
|
|
310
|
+
recipient: { id: recipientId },
|
|
311
|
+
message: {
|
|
312
|
+
text,
|
|
313
|
+
...quickReplies && quickReplies.length > 0 ? { quick_replies: quickReplies } : {}
|
|
314
|
+
},
|
|
315
|
+
messaging_type: "RESPONSE"
|
|
316
|
+
}
|
|
317
|
+
);
|
|
318
|
+
const rawMessage = {
|
|
319
|
+
sender: { id: this._botUserId ?? "" },
|
|
320
|
+
recipient: { id: recipientId },
|
|
321
|
+
timestamp: Date.now(),
|
|
322
|
+
message: {
|
|
323
|
+
mid: result.message_id,
|
|
324
|
+
text,
|
|
325
|
+
is_echo: true
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
const parsedMessage = this.parseMessengerMessage(rawMessage, threadId);
|
|
329
|
+
this.cacheMessage(parsedMessage);
|
|
330
|
+
return {
|
|
331
|
+
id: result.message_id,
|
|
332
|
+
threadId,
|
|
333
|
+
raw: rawMessage
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
async editMessage(_threadId, _messageId, _message) {
|
|
337
|
+
throw new ValidationError(
|
|
338
|
+
"messenger",
|
|
339
|
+
"Messenger does not support editing messages"
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
async deleteMessage(_threadId, _messageId) {
|
|
343
|
+
throw new ValidationError(
|
|
344
|
+
"messenger",
|
|
345
|
+
"Messenger does not support deleting messages"
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
async addReaction(_threadId, _messageId, _emoji) {
|
|
349
|
+
throw new ValidationError(
|
|
350
|
+
"messenger",
|
|
351
|
+
"Messenger does not support reactions via API"
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
async removeReaction(_threadId, _messageId, _emoji) {
|
|
355
|
+
throw new ValidationError(
|
|
356
|
+
"messenger",
|
|
357
|
+
"Messenger does not support reactions via API"
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
async startTyping(threadId) {
|
|
361
|
+
const { recipientId } = this.resolveThreadId(threadId);
|
|
362
|
+
await this.graphApiFetch("me/messages", "POST", {
|
|
363
|
+
recipient: { id: recipientId },
|
|
364
|
+
sender_action: "typing_on"
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
async fetchMessages(threadId, options = {}) {
|
|
368
|
+
const messages = [...this.messageCache.get(threadId) ?? []].sort(
|
|
369
|
+
(a, b) => this.compareMessages(a, b)
|
|
370
|
+
);
|
|
371
|
+
return this.paginateMessages(messages, options);
|
|
372
|
+
}
|
|
373
|
+
async fetchMessage(_threadId, messageId) {
|
|
374
|
+
return this.findCachedMessage(messageId) ?? null;
|
|
375
|
+
}
|
|
376
|
+
async fetchThread(threadId) {
|
|
377
|
+
const { recipientId } = this.resolveThreadId(threadId);
|
|
378
|
+
const profile = await this.fetchUserProfile(recipientId);
|
|
379
|
+
const displayName = this.profileDisplayName(profile);
|
|
380
|
+
return {
|
|
381
|
+
id: threadId,
|
|
382
|
+
channelId: recipientId,
|
|
383
|
+
channelName: displayName,
|
|
384
|
+
isDM: true,
|
|
385
|
+
metadata: { profile }
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
async fetchChannelInfo(channelId) {
|
|
389
|
+
const profile = await this.fetchUserProfile(channelId);
|
|
390
|
+
const displayName = this.profileDisplayName(profile);
|
|
391
|
+
return {
|
|
392
|
+
id: channelId,
|
|
393
|
+
name: displayName,
|
|
394
|
+
isDM: true,
|
|
395
|
+
metadata: { profile }
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
channelIdFromThreadId(threadId) {
|
|
399
|
+
return this.resolveThreadId(threadId).recipientId;
|
|
400
|
+
}
|
|
401
|
+
async openDM(userId) {
|
|
402
|
+
return this.encodeThreadId({ recipientId: userId });
|
|
403
|
+
}
|
|
404
|
+
isDM(_threadId) {
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
encodeThreadId(platformData) {
|
|
408
|
+
return `messenger:${platformData.recipientId}`;
|
|
409
|
+
}
|
|
410
|
+
decodeThreadId(threadId) {
|
|
411
|
+
const parts = threadId.split(":");
|
|
412
|
+
if (parts[0] !== "messenger" || parts.length !== 2) {
|
|
413
|
+
throw new ValidationError(
|
|
414
|
+
"messenger",
|
|
415
|
+
`Invalid Messenger thread ID: ${threadId}`
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
const recipientId = parts[1];
|
|
419
|
+
if (!recipientId) {
|
|
420
|
+
throw new ValidationError(
|
|
421
|
+
"messenger",
|
|
422
|
+
`Invalid Messenger thread ID: ${threadId}`
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
return { recipientId };
|
|
426
|
+
}
|
|
427
|
+
parseMessage(raw) {
|
|
428
|
+
const threadId = this.encodeThreadId({
|
|
429
|
+
recipientId: raw.sender.id
|
|
430
|
+
});
|
|
431
|
+
const message = this.parseMessengerMessage(raw, threadId);
|
|
432
|
+
this.cacheMessage(message);
|
|
433
|
+
return message;
|
|
434
|
+
}
|
|
435
|
+
renderFormatted(content) {
|
|
436
|
+
return this.formatConverter.fromAst(content);
|
|
437
|
+
}
|
|
438
|
+
parseMessengerMessage(event, threadId) {
|
|
439
|
+
const text = event.message?.text ?? event.postback?.title ?? "";
|
|
440
|
+
const isEcho = event.message?.is_echo ?? false;
|
|
441
|
+
const isMe = isEcho || event.sender.id === this._botUserId;
|
|
442
|
+
return new Message({
|
|
443
|
+
id: event.message?.mid ?? `event:${event.timestamp}`,
|
|
444
|
+
threadId,
|
|
445
|
+
text,
|
|
446
|
+
formatted: this.formatConverter.toAst(text),
|
|
447
|
+
raw: event,
|
|
448
|
+
author: {
|
|
449
|
+
userId: event.sender.id,
|
|
450
|
+
userName: event.sender.id,
|
|
451
|
+
fullName: event.sender.id,
|
|
452
|
+
isBot: isMe,
|
|
453
|
+
isMe
|
|
454
|
+
},
|
|
455
|
+
metadata: {
|
|
456
|
+
dateSent: new Date(event.timestamp),
|
|
457
|
+
edited: false
|
|
458
|
+
},
|
|
459
|
+
attachments: this.extractAttachments(event),
|
|
460
|
+
isMention: true
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
extractAttachments(event) {
|
|
464
|
+
if (!event.message?.attachments) {
|
|
465
|
+
return [];
|
|
466
|
+
}
|
|
467
|
+
return event.message.attachments.filter((attachment) => attachment.payload?.url).map((attachment) => {
|
|
468
|
+
const url = attachment.payload?.url;
|
|
469
|
+
return {
|
|
470
|
+
type: this.mapAttachmentType(attachment.type),
|
|
471
|
+
url,
|
|
472
|
+
fetchData: url ? async () => this.downloadAttachment(url) : void 0
|
|
473
|
+
};
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
mapAttachmentType(fbType) {
|
|
477
|
+
switch (fbType) {
|
|
478
|
+
case "image":
|
|
479
|
+
return "image";
|
|
480
|
+
case "video":
|
|
481
|
+
return "video";
|
|
482
|
+
case "audio":
|
|
483
|
+
return "audio";
|
|
484
|
+
default:
|
|
485
|
+
return "file";
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
async downloadAttachment(url) {
|
|
489
|
+
let response;
|
|
490
|
+
try {
|
|
491
|
+
response = await fetch(url);
|
|
492
|
+
} catch (error) {
|
|
493
|
+
throw new NetworkError(
|
|
494
|
+
"messenger",
|
|
495
|
+
"Failed to download Messenger attachment",
|
|
496
|
+
error instanceof Error ? error : void 0
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
if (!response.ok) {
|
|
500
|
+
throw new NetworkError(
|
|
501
|
+
"messenger",
|
|
502
|
+
`Failed to download Messenger attachment: ${response.status}`
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
return Buffer.from(await response.arrayBuffer());
|
|
506
|
+
}
|
|
507
|
+
async fetchUserProfile(userId) {
|
|
508
|
+
const cached = this.userProfileCache.get(userId);
|
|
509
|
+
if (cached) {
|
|
510
|
+
return cached;
|
|
511
|
+
}
|
|
512
|
+
try {
|
|
513
|
+
const profile = await this.graphApiFetch(
|
|
514
|
+
userId,
|
|
515
|
+
"GET",
|
|
516
|
+
void 0,
|
|
517
|
+
{ fields: "first_name,last_name,profile_pic" }
|
|
518
|
+
);
|
|
519
|
+
this.userProfileCache.set(userId, profile);
|
|
520
|
+
return profile;
|
|
521
|
+
} catch {
|
|
522
|
+
return { id: userId };
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
profileDisplayName(profile) {
|
|
526
|
+
const parts = [profile.first_name, profile.last_name].filter(Boolean);
|
|
527
|
+
return parts.join(" ") || profile.id;
|
|
528
|
+
}
|
|
529
|
+
resolveThreadId(value) {
|
|
530
|
+
if (value.startsWith("messenger:")) {
|
|
531
|
+
return this.decodeThreadId(value);
|
|
532
|
+
}
|
|
533
|
+
return { recipientId: value };
|
|
534
|
+
}
|
|
535
|
+
truncateMessage(text) {
|
|
536
|
+
if (text.length <= MESSENGER_MESSAGE_LIMIT) {
|
|
537
|
+
return text;
|
|
538
|
+
}
|
|
539
|
+
return `${text.slice(0, MESSENGER_MESSAGE_LIMIT - 3)}...`;
|
|
540
|
+
}
|
|
541
|
+
truncateQuickReplyTitle(title) {
|
|
542
|
+
if (title.length <= MESSENGER_QUICK_REPLY_TITLE_LIMIT) {
|
|
543
|
+
return title;
|
|
544
|
+
}
|
|
545
|
+
return `${title.slice(0, MESSENGER_QUICK_REPLY_TITLE_LIMIT - 3)}...`;
|
|
546
|
+
}
|
|
547
|
+
buildQuickRepliesFromCard(card) {
|
|
548
|
+
const buttons = this.collectButtons(card.children);
|
|
549
|
+
return buttons.slice(0, MESSENGER_QUICK_REPLY_LIMIT).map((button) => ({
|
|
550
|
+
content_type: "text",
|
|
551
|
+
title: this.truncateQuickReplyTitle(button.label),
|
|
552
|
+
payload: this.encodeQuickReplyPayload(button)
|
|
553
|
+
}));
|
|
554
|
+
}
|
|
555
|
+
collectButtons(children) {
|
|
556
|
+
const buttons = [];
|
|
557
|
+
for (const child of children) {
|
|
558
|
+
if (child.type === "actions") {
|
|
559
|
+
for (const actionChild of child.children) {
|
|
560
|
+
if (actionChild.type === "button" && !actionChild.disabled) {
|
|
561
|
+
buttons.push(actionChild);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (child.type === "section") {
|
|
566
|
+
buttons.push(...this.collectButtons(child.children));
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return buttons;
|
|
570
|
+
}
|
|
571
|
+
encodeQuickReplyPayload(button) {
|
|
572
|
+
return JSON.stringify({
|
|
573
|
+
source: QUICK_REPLY_PAYLOAD_TAG,
|
|
574
|
+
actionId: button.id,
|
|
575
|
+
value: button.value
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
decodeQuickReplyPayload(payload) {
|
|
579
|
+
try {
|
|
580
|
+
const parsed = JSON.parse(payload);
|
|
581
|
+
if (parsed.source === QUICK_REPLY_PAYLOAD_TAG && typeof parsed.actionId === "string") {
|
|
582
|
+
return {
|
|
583
|
+
actionId: parsed.actionId,
|
|
584
|
+
value: typeof parsed.value === "string" ? parsed.value : void 0
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
} catch {
|
|
588
|
+
}
|
|
589
|
+
return {
|
|
590
|
+
actionId: payload,
|
|
591
|
+
value: payload
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
paginateMessages(messages, options) {
|
|
595
|
+
const limit = Math.max(1, Math.min(options.limit ?? 50, 100));
|
|
596
|
+
const direction = options.direction ?? "backward";
|
|
597
|
+
if (messages.length === 0) {
|
|
598
|
+
return { messages: [] };
|
|
599
|
+
}
|
|
600
|
+
const messageIndexById = new Map(
|
|
601
|
+
messages.map((message, index) => [message.id, index])
|
|
602
|
+
);
|
|
603
|
+
if (direction === "backward") {
|
|
604
|
+
const end2 = options.cursor && messageIndexById.has(options.cursor) ? messageIndexById.get(options.cursor) ?? messages.length : messages.length;
|
|
605
|
+
const start2 = Math.max(0, end2 - limit);
|
|
606
|
+
const page2 = messages.slice(start2, end2);
|
|
607
|
+
return {
|
|
608
|
+
messages: page2,
|
|
609
|
+
nextCursor: start2 > 0 ? page2[0]?.id : void 0
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
const start = options.cursor && messageIndexById.has(options.cursor) ? (messageIndexById.get(options.cursor) ?? -1) + 1 : 0;
|
|
613
|
+
const end = Math.min(messages.length, start + limit);
|
|
614
|
+
const page = messages.slice(start, end);
|
|
615
|
+
return {
|
|
616
|
+
messages: page,
|
|
617
|
+
nextCursor: end < messages.length ? page.at(-1)?.id : void 0
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
cacheMessage(message) {
|
|
621
|
+
const existing = this.messageCache.get(message.threadId) ?? [];
|
|
622
|
+
const index = existing.findIndex((item) => item.id === message.id);
|
|
623
|
+
if (index >= 0) {
|
|
624
|
+
existing[index] = message;
|
|
625
|
+
} else {
|
|
626
|
+
existing.push(message);
|
|
627
|
+
}
|
|
628
|
+
existing.sort((a, b) => this.compareMessages(a, b));
|
|
629
|
+
this.messageCache.set(message.threadId, existing);
|
|
630
|
+
}
|
|
631
|
+
findCachedMessage(messageId) {
|
|
632
|
+
for (const messages of this.messageCache.values()) {
|
|
633
|
+
const found = messages.find((message) => message.id === messageId);
|
|
634
|
+
if (found) {
|
|
635
|
+
return found;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
return void 0;
|
|
639
|
+
}
|
|
640
|
+
compareMessages(a, b) {
|
|
641
|
+
const timeDiff = a.metadata.dateSent.getTime() - b.metadata.dateSent.getTime();
|
|
642
|
+
if (timeDiff !== 0) {
|
|
643
|
+
return timeDiff;
|
|
644
|
+
}
|
|
645
|
+
return this.messageSequence(a.id) - this.messageSequence(b.id);
|
|
646
|
+
}
|
|
647
|
+
messageSequence(messageId) {
|
|
648
|
+
const match = messageId.match(MESSAGE_SEQUENCE_PATTERN);
|
|
649
|
+
return match ? Number.parseInt(match[1], 10) : 0;
|
|
650
|
+
}
|
|
651
|
+
async graphApiFetch(endpoint, method, body, queryParams) {
|
|
652
|
+
const url = new URL(`${GRAPH_API_BASE}/${this.apiVersion}/${endpoint}`);
|
|
653
|
+
url.searchParams.set("access_token", this.pageAccessToken);
|
|
654
|
+
if (queryParams) {
|
|
655
|
+
for (const [key, value] of Object.entries(queryParams)) {
|
|
656
|
+
url.searchParams.set(key, value);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
let response;
|
|
660
|
+
try {
|
|
661
|
+
response = await fetch(url.toString(), {
|
|
662
|
+
method,
|
|
663
|
+
headers: method === "POST" ? { "Content-Type": "application/json" } : void 0,
|
|
664
|
+
body: body ? JSON.stringify(body) : void 0
|
|
665
|
+
});
|
|
666
|
+
} catch (error) {
|
|
667
|
+
throw new NetworkError(
|
|
668
|
+
"messenger",
|
|
669
|
+
`Network error calling Messenger Graph API ${endpoint}`,
|
|
670
|
+
error instanceof Error ? error : void 0
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
let data;
|
|
674
|
+
try {
|
|
675
|
+
data = await response.json();
|
|
676
|
+
} catch {
|
|
677
|
+
throw new NetworkError(
|
|
678
|
+
"messenger",
|
|
679
|
+
`Failed to parse Messenger API response for ${endpoint}`
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
if (!response.ok) {
|
|
683
|
+
this.throwGraphApiError(endpoint, response.status, data);
|
|
684
|
+
}
|
|
685
|
+
return data;
|
|
686
|
+
}
|
|
687
|
+
throwGraphApiError(endpoint, status, data) {
|
|
688
|
+
const error = data.error;
|
|
689
|
+
const message = error?.message ?? `Messenger API ${endpoint} failed`;
|
|
690
|
+
const code = error?.code ?? status;
|
|
691
|
+
if (status === 429 || code === 4 || code === 32 || code === 613) {
|
|
692
|
+
throw new AdapterRateLimitError("messenger");
|
|
693
|
+
}
|
|
694
|
+
if (status === 401 || code === 190) {
|
|
695
|
+
throw new AuthenticationError("messenger", message);
|
|
696
|
+
}
|
|
697
|
+
if (status === 403 || code === 10 || code === 200) {
|
|
698
|
+
throw new ValidationError("messenger", message);
|
|
699
|
+
}
|
|
700
|
+
if (status === 404) {
|
|
701
|
+
throw new ResourceNotFoundError("messenger", endpoint);
|
|
702
|
+
}
|
|
703
|
+
throw new NetworkError(
|
|
704
|
+
"messenger",
|
|
705
|
+
`${message} (status ${status}, code ${code})`
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
function createMessengerAdapter(config) {
|
|
710
|
+
const appSecret = config?.appSecret ?? process.env.FACEBOOK_APP_SECRET;
|
|
711
|
+
if (!appSecret) {
|
|
712
|
+
throw new ValidationError(
|
|
713
|
+
"messenger",
|
|
714
|
+
"appSecret is required. Set FACEBOOK_APP_SECRET or provide it in config."
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
const pageAccessToken = config?.pageAccessToken ?? process.env.FACEBOOK_PAGE_ACCESS_TOKEN;
|
|
718
|
+
if (!pageAccessToken) {
|
|
719
|
+
throw new ValidationError(
|
|
720
|
+
"messenger",
|
|
721
|
+
"pageAccessToken is required. Set FACEBOOK_PAGE_ACCESS_TOKEN or provide it in config."
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
const verifyToken = config?.verifyToken ?? process.env.FACEBOOK_VERIFY_TOKEN;
|
|
725
|
+
if (!verifyToken) {
|
|
726
|
+
throw new ValidationError(
|
|
727
|
+
"messenger",
|
|
728
|
+
"verifyToken is required. Set FACEBOOK_VERIFY_TOKEN or provide it in config."
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
return new MessengerAdapter({
|
|
732
|
+
appSecret,
|
|
733
|
+
pageAccessToken,
|
|
734
|
+
verifyToken,
|
|
735
|
+
apiVersion: config?.apiVersion,
|
|
736
|
+
logger: config?.logger ?? new ConsoleLogger("info").child("messenger"),
|
|
737
|
+
userName: config?.userName
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
export {
|
|
741
|
+
MessengerAdapter,
|
|
742
|
+
MessengerFormatConverter,
|
|
743
|
+
createMessengerAdapter
|
|
744
|
+
};
|
|
745
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapter.ts","../src/format-converter.ts"],"sourcesContent":["import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport {\n AdapterRateLimitError,\n AuthenticationError,\n cardToFallbackText,\n extractCard,\n NetworkError,\n ResourceNotFoundError,\n ValidationError,\n} from \"@chat-adapter/shared\";\nimport type {\n Adapter,\n AdapterPostableMessage,\n Attachment,\n ButtonElement,\n CardChild,\n CardElement,\n ChannelInfo,\n ChatInstance,\n EmojiValue,\n FetchOptions,\n FetchResult,\n FormattedContent,\n Logger,\n RawMessage,\n ThreadInfo,\n WebhookOptions,\n} from \"chat\";\nimport {\n ConsoleLogger,\n convertEmojiPlaceholders,\n getEmoji,\n Message,\n} from \"chat\";\nimport { MessengerFormatConverter } from \"./format-converter\";\nimport type {\n MessengerAdapterConfig,\n MessengerMessagingEvent,\n MessengerRawMessage,\n MessengerOutgoingQuickReply,\n MessengerSendApiResponse,\n MessengerThreadId,\n MessengerUserProfile,\n MessengerWebhookPayload,\n} from \"./types\";\n\nconst GRAPH_API_BASE = \"https://graph.facebook.com\";\nconst DEFAULT_API_VERSION = \"v21.0\";\nconst MESSENGER_MESSAGE_LIMIT = 2000;\nconst MESSENGER_QUICK_REPLY_LIMIT = 13;\nconst MESSENGER_QUICK_REPLY_TITLE_LIMIT = 20;\nconst MESSAGE_SEQUENCE_PATTERN = /:(\\d+)$/;\nconst QUICK_REPLY_PAYLOAD_TAG = \"chat-sdk-button\";\n\nexport class MessengerAdapter implements Adapter<\n MessengerThreadId,\n MessengerRawMessage\n> {\n readonly name = \"messenger\";\n\n private readonly appSecret: string;\n private readonly pageAccessToken: string;\n private readonly verifyToken: string;\n private readonly apiVersion: string;\n private readonly logger: Logger;\n private readonly formatConverter = new MessengerFormatConverter();\n private readonly messageCache = new Map<\n string,\n Message<MessengerRawMessage>[]\n >();\n private readonly userProfileCache = new Map<string, MessengerUserProfile>();\n\n private chat: ChatInstance | null = null;\n private _botUserId?: string;\n private _userName: string;\n private readonly hasExplicitUserName: boolean;\n\n get botUserId(): string | undefined {\n return this._botUserId;\n }\n\n get userName(): string {\n return this._userName;\n }\n\n constructor(\n config: MessengerAdapterConfig & { logger: Logger; userName?: string },\n ) {\n this.appSecret = config.appSecret;\n this.pageAccessToken = config.pageAccessToken;\n this.verifyToken = config.verifyToken;\n this.apiVersion = config.apiVersion ?? DEFAULT_API_VERSION;\n this.logger = config.logger;\n this._userName = config.userName ?? \"bot\";\n this.hasExplicitUserName = Boolean(config.userName);\n }\n\n async initialize(chat: ChatInstance): Promise<void> {\n this.chat = chat;\n\n if (!this.hasExplicitUserName) {\n this._userName = chat.getUserName();\n }\n\n try {\n const me = await this.graphApiFetch<{ id: string; name: string }>(\n \"me\",\n \"GET\",\n );\n this._botUserId = me.id;\n if (!this.hasExplicitUserName && me.name) {\n this._userName = me.name;\n }\n\n this.logger.info(\"Messenger adapter initialized\", {\n botUserId: this._botUserId,\n userName: this._userName,\n });\n } catch (error) {\n this.logger.warn(\"Failed to fetch Messenger page identity\", {\n error: String(error),\n });\n }\n }\n\n async handleWebhook(\n request: Request,\n options?: WebhookOptions,\n ): Promise<Response> {\n if (request.method === \"GET\") {\n return this.handleVerification(request);\n }\n\n const body = await request.text();\n\n if (!this.verifySignature(request, body)) {\n this.logger.warn(\"Messenger webhook rejected due to invalid signature\");\n return new Response(\"Invalid signature\", { status: 403 });\n }\n\n let payload: MessengerWebhookPayload;\n try {\n payload = JSON.parse(body) as MessengerWebhookPayload;\n } catch {\n return new Response(\"Invalid JSON\", { status: 400 });\n }\n\n if (payload.object !== \"page\") {\n return new Response(\"Not a page subscription\", { status: 404 });\n }\n\n if (!this.chat) {\n this.logger.warn(\n \"Chat instance not initialized, ignoring Messenger webhook\",\n );\n return new Response(\"EVENT_RECEIVED\", { status: 200 });\n }\n\n for (const entry of payload.entry) {\n for (const event of entry.messaging) {\n if (event.message && !event.message.is_echo) {\n this.handleIncomingMessage(event, options);\n }\n\n if (event.message?.is_echo) {\n this.handleEcho(event);\n }\n\n if (event.postback) {\n this.handlePostback(event, options);\n }\n\n if (event.reaction) {\n this.handleReaction(event, options);\n }\n\n if (event.delivery) {\n this.logger.debug(\"Message delivery confirmation\", {\n watermark: event.delivery.watermark,\n mids: event.delivery.mids,\n });\n }\n\n if (event.read) {\n this.logger.debug(\"Message read confirmation\", {\n watermark: event.read.watermark,\n });\n }\n }\n }\n\n return new Response(\"EVENT_RECEIVED\", { status: 200 });\n }\n\n private handleVerification(request: Request): Response {\n const url = new URL(request.url);\n const mode = url.searchParams.get(\"hub.mode\");\n const token = url.searchParams.get(\"hub.verify_token\");\n const challenge = url.searchParams.get(\"hub.challenge\");\n\n if (mode === \"subscribe\" && token === this.verifyToken) {\n this.logger.info(\"Messenger webhook verified\");\n return new Response(challenge ?? \"\", { status: 200 });\n }\n\n this.logger.warn(\"Messenger webhook verification failed\");\n return new Response(\"Forbidden\", { status: 403 });\n }\n\n private verifySignature(request: Request, body: string): boolean {\n const signature = request.headers.get(\"x-hub-signature-256\");\n if (!signature) {\n return false;\n }\n\n const [algo, hash] = signature.split(\"=\");\n if (algo !== \"sha256\" || !hash) {\n return false;\n }\n\n try {\n const computedHash = createHmac(\"sha256\", this.appSecret)\n .update(body, \"utf8\")\n .digest(\"hex\");\n\n return timingSafeEqual(\n Buffer.from(hash, \"hex\"),\n Buffer.from(computedHash, \"hex\"),\n );\n } catch {\n this.logger.warn(\"Failed to verify Messenger webhook signature\");\n return false;\n }\n }\n\n private handleIncomingMessage(\n event: MessengerMessagingEvent,\n options?: WebhookOptions,\n ): void {\n if (!this.chat) {\n return;\n }\n\n const threadId = this.encodeThreadId({\n recipientId: event.sender.id,\n });\n\n const parsedMessage = this.parseMessengerMessage(event, threadId);\n this.cacheMessage(parsedMessage);\n\n const quickReplyPayload = event.message?.quick_reply?.payload;\n if (quickReplyPayload) {\n const action = this.decodeQuickReplyPayload(quickReplyPayload);\n\n this.chat.processAction(\n {\n adapter: this,\n actionId: action.actionId,\n value: action.value,\n messageId: event.message?.mid ?? `quick_reply:${event.timestamp}`,\n threadId,\n user: {\n userId: event.sender.id,\n userName: event.sender.id,\n fullName: event.sender.id,\n isBot: false,\n isMe: false,\n },\n raw: event,\n },\n options,\n );\n return;\n }\n\n this.chat.processMessage(this, threadId, parsedMessage, options);\n }\n\n private handlePostback(\n event: MessengerMessagingEvent,\n options?: WebhookOptions,\n ): void {\n if (!(this.chat && event.postback)) {\n return;\n }\n\n const threadId = this.encodeThreadId({\n recipientId: event.sender.id,\n });\n\n this.chat.processAction(\n {\n adapter: this,\n actionId: event.postback.payload,\n value: event.postback.payload,\n messageId: event.postback.mid ?? `postback:${event.timestamp}`,\n threadId,\n user: {\n userId: event.sender.id,\n userName: event.sender.id,\n fullName: event.sender.id,\n isBot: false,\n isMe: false,\n },\n raw: event,\n },\n options,\n );\n }\n\n private handleEcho(event: MessengerMessagingEvent): void {\n if (!event.message) {\n return;\n }\n\n const threadId = this.encodeThreadId({\n recipientId: event.recipient.id,\n });\n\n const parsedMessage = this.parseMessengerMessage(event, threadId);\n this.cacheMessage(parsedMessage);\n }\n\n private handleReaction(\n event: MessengerMessagingEvent,\n options?: WebhookOptions,\n ): void {\n if (!(this.chat && event.reaction)) {\n return;\n }\n\n const threadId = this.encodeThreadId({\n recipientId: event.sender.id,\n });\n\n const added = event.reaction.action === \"react\";\n\n this.chat.processReaction(\n {\n adapter: this,\n threadId,\n messageId: event.reaction.mid,\n emoji: getEmoji(event.reaction.emoji),\n rawEmoji: event.reaction.emoji,\n added,\n user: {\n userId: event.sender.id,\n userName: event.sender.id,\n fullName: event.sender.id,\n isBot: false,\n isMe: false,\n },\n raw: event,\n },\n options,\n );\n }\n\n async postMessage(\n threadId: string,\n message: AdapterPostableMessage,\n ): Promise<RawMessage<MessengerRawMessage>> {\n const { recipientId } = this.resolveThreadId(threadId);\n\n const card = extractCard(message);\n const quickReplies = card\n ? this.buildQuickRepliesFromCard(card)\n : undefined;\n const text = this.truncateMessage(\n convertEmojiPlaceholders(\n card\n ? cardToFallbackText(card)\n : this.formatConverter.renderPostable(message),\n \"gchat\",\n ),\n );\n\n if (!text.trim()) {\n throw new ValidationError(\"messenger\", \"Message text cannot be empty\");\n }\n\n const result = await this.graphApiFetch<MessengerSendApiResponse>(\n \"me/messages\",\n \"POST\",\n {\n recipient: { id: recipientId },\n message: {\n text,\n ...(quickReplies && quickReplies.length > 0\n ? { quick_replies: quickReplies }\n : {}),\n },\n messaging_type: \"RESPONSE\",\n },\n );\n\n const rawMessage: MessengerMessagingEvent = {\n sender: { id: this._botUserId ?? \"\" },\n recipient: { id: recipientId },\n timestamp: Date.now(),\n message: {\n mid: result.message_id,\n text,\n is_echo: true,\n },\n };\n\n const parsedMessage = this.parseMessengerMessage(rawMessage, threadId);\n this.cacheMessage(parsedMessage);\n\n return {\n id: result.message_id,\n threadId,\n raw: rawMessage,\n };\n }\n\n async editMessage(\n _threadId: string,\n _messageId: string,\n _message: AdapterPostableMessage,\n ): Promise<RawMessage<MessengerRawMessage>> {\n throw new ValidationError(\n \"messenger\",\n \"Messenger does not support editing messages\",\n );\n }\n\n async deleteMessage(_threadId: string, _messageId: string): Promise<void> {\n throw new ValidationError(\n \"messenger\",\n \"Messenger does not support deleting messages\",\n );\n }\n\n async addReaction(\n _threadId: string,\n _messageId: string,\n _emoji: EmojiValue | string,\n ): Promise<void> {\n throw new ValidationError(\n \"messenger\",\n \"Messenger does not support reactions via API\",\n );\n }\n\n async removeReaction(\n _threadId: string,\n _messageId: string,\n _emoji: EmojiValue | string,\n ): Promise<void> {\n throw new ValidationError(\n \"messenger\",\n \"Messenger does not support reactions via API\",\n );\n }\n\n async startTyping(threadId: string): Promise<void> {\n const { recipientId } = this.resolveThreadId(threadId);\n await this.graphApiFetch(\"me/messages\", \"POST\", {\n recipient: { id: recipientId },\n sender_action: \"typing_on\",\n });\n }\n\n async fetchMessages(\n threadId: string,\n options: FetchOptions = {},\n ): Promise<FetchResult<MessengerRawMessage>> {\n const messages = [...(this.messageCache.get(threadId) ?? [])].sort((a, b) =>\n this.compareMessages(a, b),\n );\n\n return this.paginateMessages(messages, options);\n }\n\n async fetchMessage(\n _threadId: string,\n messageId: string,\n ): Promise<Message<MessengerRawMessage> | null> {\n return this.findCachedMessage(messageId) ?? null;\n }\n\n async fetchThread(threadId: string): Promise<ThreadInfo> {\n const { recipientId } = this.resolveThreadId(threadId);\n const profile = await this.fetchUserProfile(recipientId);\n const displayName = this.profileDisplayName(profile);\n\n return {\n id: threadId,\n channelId: recipientId,\n channelName: displayName,\n isDM: true,\n metadata: { profile },\n };\n }\n\n async fetchChannelInfo(channelId: string): Promise<ChannelInfo> {\n const profile = await this.fetchUserProfile(channelId);\n const displayName = this.profileDisplayName(profile);\n\n return {\n id: channelId,\n name: displayName,\n isDM: true,\n metadata: { profile },\n };\n }\n\n channelIdFromThreadId(threadId: string): string {\n return this.resolveThreadId(threadId).recipientId;\n }\n\n async openDM(userId: string): Promise<string> {\n return this.encodeThreadId({ recipientId: userId });\n }\n\n isDM(_threadId: string): boolean {\n return true;\n }\n\n encodeThreadId(platformData: MessengerThreadId): string {\n return `messenger:${platformData.recipientId}`;\n }\n\n decodeThreadId(threadId: string): MessengerThreadId {\n const parts = threadId.split(\":\");\n if (parts[0] !== \"messenger\" || parts.length !== 2) {\n throw new ValidationError(\n \"messenger\",\n `Invalid Messenger thread ID: ${threadId}`,\n );\n }\n\n const recipientId = parts[1];\n if (!recipientId) {\n throw new ValidationError(\n \"messenger\",\n `Invalid Messenger thread ID: ${threadId}`,\n );\n }\n\n return { recipientId };\n }\n\n parseMessage(raw: MessengerRawMessage): Message<MessengerRawMessage> {\n const threadId = this.encodeThreadId({\n recipientId: raw.sender.id,\n });\n\n const message = this.parseMessengerMessage(raw, threadId);\n this.cacheMessage(message);\n return message;\n }\n\n renderFormatted(content: FormattedContent): string {\n return this.formatConverter.fromAst(content);\n }\n\n private parseMessengerMessage(\n event: MessengerMessagingEvent,\n threadId: string,\n ): Message<MessengerRawMessage> {\n const text = event.message?.text ?? event.postback?.title ?? \"\";\n const isEcho = event.message?.is_echo ?? false;\n const isMe = isEcho || event.sender.id === this._botUserId;\n\n return new Message<MessengerRawMessage>({\n id: event.message?.mid ?? `event:${event.timestamp}`,\n threadId,\n text,\n formatted: this.formatConverter.toAst(text),\n raw: event,\n author: {\n userId: event.sender.id,\n userName: event.sender.id,\n fullName: event.sender.id,\n isBot: isMe,\n isMe,\n },\n metadata: {\n dateSent: new Date(event.timestamp),\n edited: false,\n },\n attachments: this.extractAttachments(event),\n isMention: true,\n });\n }\n\n private extractAttachments(event: MessengerMessagingEvent): Attachment[] {\n if (!event.message?.attachments) {\n return [];\n }\n\n return event.message.attachments\n .filter((attachment) => attachment.payload?.url)\n .map((attachment) => {\n const url = attachment.payload?.url;\n return {\n type: this.mapAttachmentType(attachment.type),\n url,\n fetchData: url ? async () => this.downloadAttachment(url) : undefined,\n };\n });\n }\n\n private mapAttachmentType(\n fbType: string,\n ): \"image\" | \"video\" | \"audio\" | \"file\" {\n switch (fbType) {\n case \"image\":\n return \"image\";\n case \"video\":\n return \"video\";\n case \"audio\":\n return \"audio\";\n default:\n return \"file\";\n }\n }\n\n private async downloadAttachment(url: string): Promise<Buffer> {\n let response: Response;\n try {\n response = await fetch(url);\n } catch (error) {\n throw new NetworkError(\n \"messenger\",\n \"Failed to download Messenger attachment\",\n error instanceof Error ? error : undefined,\n );\n }\n\n if (!response.ok) {\n throw new NetworkError(\n \"messenger\",\n `Failed to download Messenger attachment: ${response.status}`,\n );\n }\n\n return Buffer.from(await response.arrayBuffer());\n }\n\n private async fetchUserProfile(\n userId: string,\n ): Promise<MessengerUserProfile> {\n const cached = this.userProfileCache.get(userId);\n if (cached) {\n return cached;\n }\n\n try {\n const profile = await this.graphApiFetch<MessengerUserProfile>(\n userId,\n \"GET\",\n undefined,\n { fields: \"first_name,last_name,profile_pic\" },\n );\n this.userProfileCache.set(userId, profile);\n return profile;\n } catch {\n return { id: userId };\n }\n }\n\n private profileDisplayName(profile: MessengerUserProfile): string {\n const parts = [profile.first_name, profile.last_name].filter(Boolean);\n return parts.join(\" \") || profile.id;\n }\n\n private resolveThreadId(value: string): MessengerThreadId {\n if (value.startsWith(\"messenger:\")) {\n return this.decodeThreadId(value);\n }\n\n return { recipientId: value };\n }\n\n private truncateMessage(text: string): string {\n if (text.length <= MESSENGER_MESSAGE_LIMIT) {\n return text;\n }\n\n return `${text.slice(0, MESSENGER_MESSAGE_LIMIT - 3)}...`;\n }\n\n private truncateQuickReplyTitle(title: string): string {\n if (title.length <= MESSENGER_QUICK_REPLY_TITLE_LIMIT) {\n return title;\n }\n\n return `${title.slice(0, MESSENGER_QUICK_REPLY_TITLE_LIMIT - 3)}...`;\n }\n\n private buildQuickRepliesFromCard(\n card: CardElement,\n ): MessengerOutgoingQuickReply[] {\n const buttons = this.collectButtons(card.children);\n return buttons.slice(0, MESSENGER_QUICK_REPLY_LIMIT).map((button) => ({\n content_type: \"text\" as const,\n title: this.truncateQuickReplyTitle(button.label),\n payload: this.encodeQuickReplyPayload(button),\n }));\n }\n\n private collectButtons(children: CardChild[]): ButtonElement[] {\n const buttons: ButtonElement[] = [];\n\n for (const child of children) {\n if (child.type === \"actions\") {\n for (const actionChild of child.children) {\n if (actionChild.type === \"button\" && !actionChild.disabled) {\n buttons.push(actionChild);\n }\n }\n }\n\n if (child.type === \"section\") {\n buttons.push(...this.collectButtons(child.children));\n }\n }\n\n return buttons;\n }\n\n private encodeQuickReplyPayload(button: ButtonElement): string {\n return JSON.stringify({\n source: QUICK_REPLY_PAYLOAD_TAG,\n actionId: button.id,\n value: button.value,\n });\n }\n\n private decodeQuickReplyPayload(payload: string): {\n actionId: string;\n value?: string;\n } {\n try {\n const parsed = JSON.parse(payload) as {\n source?: string;\n actionId?: string;\n value?: string;\n };\n\n if (\n parsed.source === QUICK_REPLY_PAYLOAD_TAG &&\n typeof parsed.actionId === \"string\"\n ) {\n return {\n actionId: parsed.actionId,\n value: typeof parsed.value === \"string\" ? parsed.value : undefined,\n };\n }\n } catch {\n // Fallback to legacy payload handling when payload is not JSON.\n }\n\n return {\n actionId: payload,\n value: payload,\n };\n }\n\n private paginateMessages(\n messages: Message<MessengerRawMessage>[],\n options: FetchOptions,\n ): FetchResult<MessengerRawMessage> {\n const limit = Math.max(1, Math.min(options.limit ?? 50, 100));\n const direction = options.direction ?? \"backward\";\n\n if (messages.length === 0) {\n return { messages: [] };\n }\n\n const messageIndexById = new Map(\n messages.map((message, index) => [message.id, index]),\n );\n\n if (direction === \"backward\") {\n const end =\n options.cursor && messageIndexById.has(options.cursor)\n ? (messageIndexById.get(options.cursor) ?? messages.length)\n : messages.length;\n const start = Math.max(0, end - limit);\n const page = messages.slice(start, end);\n\n return {\n messages: page,\n nextCursor: start > 0 ? page[0]?.id : undefined,\n };\n }\n\n const start =\n options.cursor && messageIndexById.has(options.cursor)\n ? (messageIndexById.get(options.cursor) ?? -1) + 1\n : 0;\n const end = Math.min(messages.length, start + limit);\n const page = messages.slice(start, end);\n\n return {\n messages: page,\n nextCursor: end < messages.length ? page.at(-1)?.id : undefined,\n };\n }\n\n private cacheMessage(message: Message<MessengerRawMessage>): void {\n const existing = this.messageCache.get(message.threadId) ?? [];\n const index = existing.findIndex((item) => item.id === message.id);\n\n if (index >= 0) {\n existing[index] = message;\n } else {\n existing.push(message);\n }\n\n existing.sort((a, b) => this.compareMessages(a, b));\n this.messageCache.set(message.threadId, existing);\n }\n\n private findCachedMessage(\n messageId: string,\n ): Message<MessengerRawMessage> | undefined {\n for (const messages of this.messageCache.values()) {\n const found = messages.find((message) => message.id === messageId);\n if (found) {\n return found;\n }\n }\n\n return undefined;\n }\n\n private compareMessages(\n a: Message<MessengerRawMessage>,\n b: Message<MessengerRawMessage>,\n ): number {\n const timeDiff =\n a.metadata.dateSent.getTime() - b.metadata.dateSent.getTime();\n if (timeDiff !== 0) {\n return timeDiff;\n }\n\n return this.messageSequence(a.id) - this.messageSequence(b.id);\n }\n\n private messageSequence(messageId: string): number {\n const match = messageId.match(MESSAGE_SEQUENCE_PATTERN);\n return match ? Number.parseInt(match[1], 10) : 0;\n }\n\n private async graphApiFetch<TResult>(\n endpoint: string,\n method: \"GET\" | \"POST\",\n body?: Record<string, unknown>,\n queryParams?: Record<string, string>,\n ): Promise<TResult> {\n const url = new URL(`${GRAPH_API_BASE}/${this.apiVersion}/${endpoint}`);\n url.searchParams.set(\"access_token\", this.pageAccessToken);\n\n if (queryParams) {\n for (const [key, value] of Object.entries(queryParams)) {\n url.searchParams.set(key, value);\n }\n }\n\n let response: Response;\n try {\n response = await fetch(url.toString(), {\n method,\n headers:\n method === \"POST\"\n ? { \"Content-Type\": \"application/json\" }\n : undefined,\n body: body ? JSON.stringify(body) : undefined,\n });\n } catch (error) {\n throw new NetworkError(\n \"messenger\",\n `Network error calling Messenger Graph API ${endpoint}`,\n error instanceof Error ? error : undefined,\n );\n }\n\n let data: Record<string, unknown>;\n try {\n data = (await response.json()) as Record<string, unknown>;\n } catch {\n throw new NetworkError(\n \"messenger\",\n `Failed to parse Messenger API response for ${endpoint}`,\n );\n }\n\n if (!response.ok) {\n this.throwGraphApiError(endpoint, response.status, data);\n }\n\n return data as TResult;\n }\n\n private throwGraphApiError(\n endpoint: string,\n status: number,\n data: Record<string, unknown>,\n ): never {\n const error = data.error as\n | { message?: string; code?: number; type?: string }\n | undefined;\n const message = error?.message ?? `Messenger API ${endpoint} failed`;\n const code = error?.code ?? status;\n\n if (status === 429 || code === 4 || code === 32 || code === 613) {\n throw new AdapterRateLimitError(\"messenger\");\n }\n\n if (status === 401 || code === 190) {\n throw new AuthenticationError(\"messenger\", message);\n }\n\n if (status === 403 || code === 10 || code === 200) {\n throw new ValidationError(\"messenger\", message);\n }\n\n if (status === 404) {\n throw new ResourceNotFoundError(\"messenger\", endpoint);\n }\n\n throw new NetworkError(\n \"messenger\",\n `${message} (status ${status}, code ${code})`,\n );\n }\n}\n\nexport function createMessengerAdapter(\n config?: Partial<\n MessengerAdapterConfig & { logger: Logger; userName?: string }\n >,\n): MessengerAdapter {\n const appSecret = config?.appSecret ?? process.env.FACEBOOK_APP_SECRET;\n if (!appSecret) {\n throw new ValidationError(\n \"messenger\",\n \"appSecret is required. Set FACEBOOK_APP_SECRET or provide it in config.\",\n );\n }\n\n const pageAccessToken =\n config?.pageAccessToken ?? process.env.FACEBOOK_PAGE_ACCESS_TOKEN;\n if (!pageAccessToken) {\n throw new ValidationError(\n \"messenger\",\n \"pageAccessToken is required. Set FACEBOOK_PAGE_ACCESS_TOKEN or provide it in config.\",\n );\n }\n\n const verifyToken = config?.verifyToken ?? process.env.FACEBOOK_VERIFY_TOKEN;\n if (!verifyToken) {\n throw new ValidationError(\n \"messenger\",\n \"verifyToken is required. Set FACEBOOK_VERIFY_TOKEN or provide it in config.\",\n );\n }\n\n return new MessengerAdapter({\n appSecret,\n pageAccessToken,\n verifyToken,\n apiVersion: config?.apiVersion,\n logger: config?.logger ?? new ConsoleLogger(\"info\").child(\"messenger\"),\n userName: config?.userName,\n });\n}\n\nexport { MessengerFormatConverter } from \"./format-converter\";\nexport type {\n MessengerAdapterConfig,\n MessengerMessagingEvent,\n MessengerRawMessage,\n MessengerReaction,\n MessengerSendApiResponse,\n MessengerThreadId,\n MessengerUserProfile,\n MessengerWebhookPayload,\n} from \"./types\";\n","import {\n type AdapterPostableMessage,\n BaseFormatConverter,\n parseMarkdown,\n type Root,\n stringifyMarkdown,\n} from \"chat\";\n\nexport class MessengerFormatConverter extends BaseFormatConverter {\n fromAst(ast: Root): string {\n return stringifyMarkdown(ast).trim();\n }\n\n toAst(text: string): Root {\n return parseMarkdown(text);\n }\n\n override renderPostable(message: AdapterPostableMessage): string {\n if (typeof message === \"string\") {\n return message;\n }\n if (\"raw\" in message) {\n return message.raw;\n }\n if (\"markdown\" in message) {\n return this.fromMarkdown(message.markdown);\n }\n if (\"ast\" in message) {\n return this.fromAst(message.ast);\n }\n return super.renderPostable(message);\n }\n}\n"],"mappings":"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","names":["end","start","page"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "chat-adapter-messenger",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Messenger adapter for Chat SDK",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"prepare": "tsup",
|
|
21
|
+
"dev": "tsup --watch",
|
|
22
|
+
"test": "vitest run --coverage",
|
|
23
|
+
"test:watch": "vitest",
|
|
24
|
+
"typecheck": "tsc --noEmit",
|
|
25
|
+
"clean": "rm -rf dist"
|
|
26
|
+
},
|
|
27
|
+
"author": "",
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"chat": "^4.23.0"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@chat-adapter/shared": "^4.23.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^25.5.2",
|
|
36
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
37
|
+
"tsup": "^8.5.1",
|
|
38
|
+
"typescript": "^6.0.2",
|
|
39
|
+
"vitest": "^4.1.2"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"chat-sdk",
|
|
46
|
+
"chat-adapter",
|
|
47
|
+
"matrix"
|
|
48
|
+
],
|
|
49
|
+
"license": "MIT"
|
|
50
|
+
}
|