Files
Vishal Yathish 68025ca965 [messenger] add messenger (meta) platform adapter to chat sdk (#461)
* [messenger] add messenger (meta) platform adapter to chat sdk

- Webhook handling with HMAC-SHA256 signature verification
- Generic and Button template support for cards
- Postback, reaction, delivery/read confirmation handling
- Message caching for fetchMessages (Messenger has no history API)
- Replay tests and ~98% code coverage

Co-authored-by: Dimitar K. Nikolov <mitkodkn@users.noreply.github.com>
Co-authored-by: Ben Sabic <27636870+bensabic@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix: The `@chat-adapter/messenger` package version is `4.15.0` while all other packages in the Changesets fixed version group are at `4.27.0`, breaking the fixed versioning contract.

This commit fixes the issue reported at packages/adapter-messenger/package.json:3

**Bug explanation:**

The repository uses Changesets with a `"fixed"` configuration: `[["chat", "@chat-adapter/*"]]`. This means all packages matching these patterns must always share the same version number. Every package in the group (`chat`, `@chat-adapter/discord`, `@chat-adapter/gchat`, `@chat-adapter/github`, `@chat-adapter/linear`, `@chat-adapter/shared`, `@chat-adapter/slack`, `@chat-adapter/teams`, `@chat-adapter/telegram`, `@chat-adapter/web`, `@chat-adapter/whatsapp`, and the state packages) is at version `4.27.0`, except `@chat-adapter/messenger` which is at `4.15.0`.

This is likely because the messenger adapter was newly added to the monorepo (copied from a template or created fresh) and its version was never aligned with the rest of the fixed group. This mismatch will cause problems with the Changesets release workflow — when Changesets tries to bump versions for the fixed group, it may produce inconsistent or errored releases because one package is 12 minor versions behind the others.

**Fix explanation:**

Changed `"version": "4.15.0"` to `"version": "4.27.0"` in `packages/adapter-messenger/package.json` to align it with all other packages in the fixed version group.

Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: visyat <vishal.yathish@gmail.com>

* Fix: Messenger adapter env var guard only checks `FACEBOOK_APP_SECRET` but `createMessengerAdapter` requires all three env vars, causing a `ValidationError` crash at Next.js build time when only `FACEBOOK_APP_SECRET` is set.

This commit fixes the issue reported at examples/nextjs-chat/src/lib/adapters.ts:154

**Bug Analysis:**

The build failure is confirmed in the Vercel build log with:
```
Error [ValidationError]: pageAccessToken is required. Set FACEBOOK_PAGE_ACCESS_TOKEN or provide it in config.
```

The root cause is in `examples/nextjs-chat/src/lib/adapters.ts` at line ~154. The messenger adapter guard only checks for `FACEBOOK_APP_SECRET`:
```typescript
if (process.env.FACEBOOK_APP_SECRET) {
```

However, `createMessengerAdapter` (in `packages/adapter-messenger/src/index.ts`) validates and throws `ValidationError` for each of three required env vars: `FACEBOOK_APP_SECRET`, `FACEBOOK_PAGE_ACCESS_TOKEN`, and `FACEBOOK_VERIFY_TOKEN`. When only `FACEBOOK_APP_SECRET` is set in the Vercel project environment, the guard passes, `createMessengerAdapter` is called, and it throws a `ValidationError` for the missing `FACEBOOK_PAGE_ACCESS_TOKEN`. Since this code runs at module evaluation time during the Next.js build's "Collecting page data" phase, the uncaught error crashes the entire build.

This is inconsistent with other adapters in the same file. For example, the WhatsApp adapter checks both `WHATSAPP_ACCESS_TOKEN` and `WHATSAPP_PHONE_NUMBER_ID`, and the gchat/github/linear/whatsapp adapters all wrap creation in try-catch blocks.

**Fix:**

1. Updated the env var guard to check all three required environment variables (`FACEBOOK_APP_SECRET`, `FACEBOOK_PAGE_ACCESS_TOKEN`, and `FACEBOOK_VERIFY_TOKEN`) before attempting to create the adapter.
2. Wrapped the `createMessengerAdapter` call in a try-catch block (matching the pattern used by gchat, github, linear, and whatsapp adapters) so that any unexpected validation errors are caught and logged as warnings instead of crashing the build.

Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: visyat <vishal.yathish@gmail.com>

---------

Co-authored-by: Dimitar K. Nikolov <mitkodkn@users.noreply.github.com>
Co-authored-by: Ben Sabic <27636870+bensabic@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
2026-05-08 17:25:54 +10:00
..
2026-02-20 13:25:09 -08:00
2026-02-20 13:25:09 -08:00
2026-02-20 13:25:09 -08:00
2026-02-20 13:25:09 -08:00
2026-02-20 13:25:09 -08:00

Replay Test Fixtures

Replay tests verify the Chat SDK handles real webhook payloads correctly by recording production interactions and replaying them in tests.

Quick Start: SHA-Based Recording Workflow

The recommended workflow ties recordings to git commits, making it easy to capture and convert production interactions into tests.

1. Deploy with recording enabled

# Set in Vercel environment variables (or .env.local for local dev)
RECORDING_ENABLED=true
REDIS_URL=redis://...

When deployed, recordings are automatically tagged with VERCEL_GIT_COMMIT_SHA:

session-{SHA}-{timestamp}-{random}

2. Interact with your bot

Perform the interactions you want to test:

  • @mention the bot in Slack, Teams, or Google Chat
  • Click buttons in cards (actions)
  • Add emoji reactions
  • Send follow-up messages
  • Request DMs ("DM me")

3. Find recordings for your SHA

cd examples/nextjs-chat

# List all recording sessions
pnpm recording:list

# Find sessions for current commit
pnpm recording:list | grep $(git rev-parse HEAD | cut -c1-7)

# Output shows sessions with entry counts:
#   session-abc123... (5 entries)

4. Export recordings

# Export a specific session (note: filter out pnpm output)
pnpm recording:export session-abc123 2>&1 | \
  grep -v "^>" | grep -v "^\[dotenv" | grep -v "^$" > /tmp/recording.json

# Verify the export
cat /tmp/recording.json | jq 'length'

5. Analyze the recording

# Group webhooks by platform
cat /tmp/recording.json | jq '[.[] | select(.type == "webhook")] | group_by(.platform) | .[] | {platform: .[0].platform, count: length}'

# View Slack webhook details
cat /tmp/recording.json | jq '[.[] | select(.type == "webhook" and .platform == "slack") | .body | fromjson | .event | {type, text: .text[0:50], channel_type}]'

# View Google Chat webhook details
cat /tmp/recording.json | jq '[.[] | select(.type == "webhook" and .platform == "gchat") | .body | fromjson | if .chat then {type: "direct", text: .chat.messagePayload.message.text[0:50]} else {type: "pubsub"} end]'

6. Extract webhook payloads into fixtures

# Extract Slack webhooks
cat /tmp/recording.json | jq '[.[] | select(.type == "webhook" and .platform == "slack") | .body | fromjson]'

# Extract Google Chat webhooks
cat /tmp/recording.json | jq '[.[] | select(.type == "webhook" and .platform == "gchat") | .body | fromjson]'

# Extract Teams webhooks
cat /tmp/recording.json | jq '[.[] | select(.type == "webhook" and .platform == "teams") | .body | fromjson]'

7. Create fixture file

Create a JSON file in the appropriate fixtures directory:

{
  "botName": "My Bot",
  "botUserId": "U123...",
  "mention": {
    /* first webhook - the @mention */
  },
  "followUp": {
    /* follow-up message webhook */
  }
}

8. Write the replay test

See existing tests for examples:

  • replay.test.ts - Basic mention and follow-up messaging
  • replay-actions-reactions.test.ts - Button clicks and emoji reactions
  • replay-dm.test.ts - Direct message flows

Fixture Directory Structure

fixtures/replay/
├── README.md
├── slack.json           # Basic Slack messaging
├── gchat.json           # Basic Google Chat messaging
├── teams.json           # Basic Teams messaging
├── actions-reactions/   # Button clicks and reactions
│   ├── slack.json
│   ├── gchat.json
│   └── teams.json
└── dm/                  # Direct message flows
    ├── slack.json
    ├── gchat.json
    └── teams.json

Fixture Formats

Basic messaging (slack.json, gchat.json, teams.json)

{
  "botName": "My Bot",
  "botUserId": "U123...",
  "mention": {
    /* webhook body for @mention */
  },
  "followUp": {
    /* webhook body for follow-up message */
  }
}

Actions & reactions (actions-reactions/*.json)

{
  "botName": "My Bot",
  "botUserId": "U123...",
  "mention": {
    /* webhook to subscribe the thread */
  },
  "action": {
    /* button click webhook */
  },
  "reaction": {
    /* emoji reaction webhook */
  }
}

DM flows (dm/*.json)

{
  "botName": "My Bot",
  "botUserId": "U123...",
  "dmChannelId": "D123...", // Slack only
  "dmSpaceName": "spaces/...", // GChat only
  "mention": {
    /* initial @mention in channel */
  },
  "dmRequest": {
    /* user says "DM me" in thread */
  },
  "dmMessage": {
    /* user's message in DM */
  }
}

Platform-Specific Webhook Formats

Google Chat

Event Format
Mention Direct webhook with chat.messagePayload
Follow-up Pub/Sub with message.data (base64)
Reaction Pub/Sub with ce-type: "google.workspace.chat.reaction.v1.created"
Action type: "CARD_CLICKED" event
DM Space chat.messagePayload.space.type: "DM"

Slack

Event Format
Mention event_callback with event.type: "app_mention"
Follow-up event_callback with event.type: "message" and thread_ts
Reaction event_callback with event.type: "reaction_added"
Action block_actions (URL-encoded form: payload=...)
DM event_callback with event.channel_type: "im"

Raw emoji format: Slack shortcode without colons (e.g., +1, heart)

Teams

Event Format
Mention type: "message" with bot in entities array
Follow-up type: "message" with same conversation.id
Reaction type: "messageReaction" with reactionsAdded array
Action type: "message" with value.actionId
DM conversation.conversationType: "personal"

Raw emoji format: Teams reaction type (e.g., like, heart)

Recording Implementation Details

The recorder (examples/nextjs-chat/src/lib/recorder.ts) stores entries in Redis:

  • Key: recording:{sessionId}
  • TTL: 24 hours
  • Entry types: webhook (incoming) and api-call (outgoing)

Session ID format when VERCEL_GIT_COMMIT_SHA is set:

session-{SHA}-{ISO timestamp}-{random 6 chars}

This makes it easy to find all recordings from a specific deployment.

Troubleshooting

Recording export shows log output instead of JSON

The pnpm command outputs logging to stdout. Filter it out:

pnpm recording:export <session-id> 2>&1 | grep -v "^>" | grep -v "^\[dotenv" | grep -v "^$"

DM tests failing with subscription issues

DM threads need explicit subscription. When using openDM(), remember to call subscribe():

const dmThread = await chat.openDM(message.author);
await dmThread.subscribe();
await dmThread.post("Hello!");

Google Chat Pub/Sub messages have base64 data

Decode the message.data field:

echo '<base64-data>' | base64 -d | jq .

Teams DM not working

Teams DM functionality requires additional Azure Bot configuration. See SETUP.md for details.