Skip to content

Dispatch Teams install/join activities #847

Description

@magnusburton

Problem Statement

When the bot is installed on Teams, the adapter receives the lifecycle activities but dispatches
no Chat SDK event, so chat.onMemberJoinedChannel never fires and there is no install signal at
all. registerEventHandlers handles both events with a cache-only listener:

this.app.on("conversationUpdate", async (ctx) => { this.cacheUserContext(ctx.activity); });
this.app.on("installationUpdate", async (ctx) => { this.cacheUserContext(ctx.activity); });

The Slack adapter, by contrast, calls chat.processMemberJoinedChannel(...) when the bot joins a
channel. So the same consumer welcome handler works on Slack and is dead on Teams.

Proposed Solution

Map these to Chat SDK events, matching the Slack adapter:

  1. conversationUpdate where membersAdded contains the bot (recipient.id) →
    processMemberJoinedChannel({ adapter, channelId, userId: <bot id>, inviterId }).
    Also set botUserId on the adapter so the standard "did the bot join?" guard
    (event.userId === event.adapter.botUserId) works — it is currently never assigned, so the
    guard can't be satisfied.
  2. installationUpdate add/remove → a first-class event (e.g. onInstalled /
    onUninstalled), since a personal install is not a channel join and has no onMemberJoinedChannel
    analog. Include the conversation reference so consumers can send a proactive welcome and persist
    it for later proactive messages.
  3. Fix the conversation-update route key union to include teamMemberAdded (and any other
    eventTypes Microsoft emits) so the typed alias is usable.

Alternatives Considered

We subclass TeamsAdapter and override the (protected) registerEventHandlers to register our own
installationUpdate / conversationUpdate listeners before super.registerEventHandlers()
the router runs matching routes as a middleware chain and the base cache listeners are terminal
(never call ctx.next()), so a later route is skipped. We post the welcome via ctx.send(...),
subscribe the channel intro thread via getState().subscribe(...), and persist ctx.ref. This
depends on protected internals and the middleware-ordering detail, which a native dispatch would
let us delete.

Use Case

Priority

Important

Contribution

  • I am willing to help implement this feature

Additional Context

The activities Teams actually sends

  • Personal installinstallationUpdate, action: "add", conversation.conversationType: "personal", carrying conversation.id, serviceUrl, from.aadObjectId, and locale.
  • Personal uninstallinstallationUpdate, action: "remove", personal.
  • Added to a team/channelconversationUpdate with membersAdded: [{ id: "28:<botId>" }]
    and channelData.eventType: "teamMemberAdded". Note: the SDK's typed eventType union
    (routes/conversation-update.d.ts) lists channelMemberAdded but not teamMemberAdded,
    which is the value Microsoft actually sends for a team install — so the typed event alias can't
    match it, and a consumer must fall back to the generic conversationUpdate and inspect
    membersAdded manually.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions