@memberjunction/messaging-adapters 0.0.1 → 5.17.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/README.md +231 -43
- package/dist/base/BaseMessagingAdapter.d.ts +428 -0
- package/dist/base/BaseMessagingAdapter.d.ts.map +1 -0
- package/dist/base/BaseMessagingAdapter.js +934 -0
- package/dist/base/BaseMessagingAdapter.js.map +1 -0
- package/dist/base/message-formatter.d.ts +70 -0
- package/dist/base/message-formatter.d.ts.map +1 -0
- package/dist/base/message-formatter.js +201 -0
- package/dist/base/message-formatter.js.map +1 -0
- package/dist/base/types.d.ts +211 -0
- package/dist/base/types.d.ts.map +1 -0
- package/dist/base/types.js +6 -0
- package/dist/base/types.js.map +1 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/slack/SlackAdapter.d.ts +141 -0
- package/dist/slack/SlackAdapter.d.ts.map +1 -0
- package/dist/slack/SlackAdapter.js +291 -0
- package/dist/slack/SlackAdapter.js.map +1 -0
- package/dist/slack/SlackMessagingExtension.d.ts +148 -0
- package/dist/slack/SlackMessagingExtension.d.ts.map +1 -0
- package/dist/slack/SlackMessagingExtension.js +433 -0
- package/dist/slack/SlackMessagingExtension.js.map +1 -0
- package/dist/slack/slack-block-builder.d.ts +133 -0
- package/dist/slack/slack-block-builder.d.ts.map +1 -0
- package/dist/slack/slack-block-builder.js +748 -0
- package/dist/slack/slack-block-builder.js.map +1 -0
- package/dist/slack/slack-formatter.d.ts +37 -0
- package/dist/slack/slack-formatter.d.ts.map +1 -0
- package/dist/slack/slack-formatter.js +116 -0
- package/dist/slack/slack-formatter.js.map +1 -0
- package/dist/slack/slack-interactivity.d.ts +38 -0
- package/dist/slack/slack-interactivity.d.ts.map +1 -0
- package/dist/slack/slack-interactivity.js +414 -0
- package/dist/slack/slack-interactivity.js.map +1 -0
- package/dist/slack/slack-routes.d.ts +35 -0
- package/dist/slack/slack-routes.d.ts.map +1 -0
- package/dist/slack/slack-routes.js +98 -0
- package/dist/slack/slack-routes.js.map +1 -0
- package/dist/teams/TeamsAdapter.d.ts +155 -0
- package/dist/teams/TeamsAdapter.d.ts.map +1 -0
- package/dist/teams/TeamsAdapter.js +383 -0
- package/dist/teams/TeamsAdapter.js.map +1 -0
- package/dist/teams/TeamsMessagingExtension.d.ts +75 -0
- package/dist/teams/TeamsMessagingExtension.d.ts.map +1 -0
- package/dist/teams/TeamsMessagingExtension.js +176 -0
- package/dist/teams/TeamsMessagingExtension.js.map +1 -0
- package/dist/teams/teams-card-builder.d.ts +94 -0
- package/dist/teams/teams-card-builder.d.ts.map +1 -0
- package/dist/teams/teams-card-builder.js +648 -0
- package/dist/teams/teams-card-builder.js.map +1 -0
- package/dist/teams/teams-formatter.d.ts +39 -0
- package/dist/teams/teams-formatter.d.ts.map +1 -0
- package/dist/teams/teams-formatter.js +107 -0
- package/dist/teams/teams-formatter.js.map +1 -0
- package/package.json +40 -7
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @memberjunction/messaging-adapters
|
|
3
|
+
* @description Microsoft Teams-specific messaging adapter implementation.
|
|
4
|
+
*
|
|
5
|
+
* Extends `BaseMessagingAdapter` with Bot Framework operations for:
|
|
6
|
+
* - Sending and updating messages via `TurnContext`
|
|
7
|
+
* - Showing typing indicators via `ActivityTypes.Typing`
|
|
8
|
+
* - Formatting responses as Adaptive Cards
|
|
9
|
+
* - Stripping bot `<at>` mentions from message text
|
|
10
|
+
*/
|
|
11
|
+
import { TurnContext, ActivityTypes } from 'botbuilder';
|
|
12
|
+
import { LogError, LogStatus } from '@memberjunction/core';
|
|
13
|
+
import { BaseMessagingAdapter } from '../base/BaseMessagingAdapter.js';
|
|
14
|
+
import { markdownToAdaptiveCard } from './teams-formatter.js';
|
|
15
|
+
import { buildRichAdaptiveCard } from './teams-card-builder.js';
|
|
16
|
+
/**
|
|
17
|
+
* Microsoft Teams-specific adapter that implements all platform operations
|
|
18
|
+
* using the Bot Framework SDK (`botbuilder`).
|
|
19
|
+
*
|
|
20
|
+
* ## Features
|
|
21
|
+
* - Native typing indicators via `ActivityTypes.Typing`
|
|
22
|
+
* - Adaptive Card rich formatting for responses
|
|
23
|
+
* - Bot mention stripping (`<at>BotName</at>`) for clean agent input
|
|
24
|
+
* - User email extraction from Bot Framework activities
|
|
25
|
+
*
|
|
26
|
+
* ## Authentication
|
|
27
|
+
* Requires a Microsoft App ID and Password registered in Azure Bot Service.
|
|
28
|
+
* The Bot Framework SDK handles JWT token validation automatically.
|
|
29
|
+
*
|
|
30
|
+
* ## Thread History
|
|
31
|
+
* Teams thread history fetching via Microsoft Graph API is not yet implemented.
|
|
32
|
+
* This means conversations are currently single-turn. Multi-turn support
|
|
33
|
+
* requires Graph API access with `ChannelMessage.Read.All` permission.
|
|
34
|
+
*/
|
|
35
|
+
export class TeamsAdapter extends BaseMessagingAdapter {
|
|
36
|
+
static { this.CONV_REF_TTL_MS = 24 * 60 * 60 * 1000; }
|
|
37
|
+
static { this.CONV_REF_MAX_SIZE = 10_000; }
|
|
38
|
+
get PlatformName() { return 'Teams'; }
|
|
39
|
+
constructor(settings) {
|
|
40
|
+
super(settings);
|
|
41
|
+
/** The bot's Microsoft App ID (also used as the bot's user ID). */
|
|
42
|
+
this.botID = '';
|
|
43
|
+
/**
|
|
44
|
+
* Stored conversation references for proactive messaging.
|
|
45
|
+
* Maps conversation ID to the reference needed to send proactive messages.
|
|
46
|
+
* Entries are evicted after 24 hours to prevent unbounded growth.
|
|
47
|
+
*/
|
|
48
|
+
this.conversationReferences = new Map();
|
|
49
|
+
/**
|
|
50
|
+
* Recently processed form activity IDs — prevents double-processing when
|
|
51
|
+
* Web Chat sends both an invoke and a message for the same Action.Submit.
|
|
52
|
+
* Uses timestamps instead of setTimeout for cleanup (avoids timer leaks).
|
|
53
|
+
*/
|
|
54
|
+
this.recentFormActivityIds = new Map();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Convert a Bot Framework `TurnContext` into a normalized `IncomingMessage`.
|
|
58
|
+
*
|
|
59
|
+
* Called by `TeamsMessagingExtension` when a message activity is received.
|
|
60
|
+
*
|
|
61
|
+
* @param turnContext - Bot Framework turn context for the current activity.
|
|
62
|
+
* @returns Normalized incoming message.
|
|
63
|
+
*/
|
|
64
|
+
MapTeamsActivity(turnContext) {
|
|
65
|
+
const activity = turnContext.activity;
|
|
66
|
+
// Store conversation reference for potential proactive messaging
|
|
67
|
+
const conversationRef = TurnContext.getConversationReference(activity);
|
|
68
|
+
this.storeConversationRef(activity.conversation?.id ?? '', conversationRef);
|
|
69
|
+
// Extract sender email from activity (available in Teams)
|
|
70
|
+
const senderEmail = activity.from?.['email'];
|
|
71
|
+
return {
|
|
72
|
+
MessageID: activity.id ?? '',
|
|
73
|
+
Text: activity.text ?? '',
|
|
74
|
+
SenderID: activity.from?.id ?? '',
|
|
75
|
+
SenderName: activity.from?.name ?? '',
|
|
76
|
+
SenderEmail: senderEmail,
|
|
77
|
+
ChannelID: activity.channelId ?? '',
|
|
78
|
+
ThreadID: activity.conversation?.id ?? null,
|
|
79
|
+
IsDirectMessage: activity.conversation?.conversationType === 'personal',
|
|
80
|
+
IsBotMention: this.hasBotMention(activity),
|
|
81
|
+
Timestamp: activity.timestamp ? new Date(activity.timestamp) : new Date(),
|
|
82
|
+
RawEvent: { activity, turnContext }
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Initialize: set the bot ID from config or environment.
|
|
87
|
+
*/
|
|
88
|
+
async onInitialize() {
|
|
89
|
+
const settings = this.settings;
|
|
90
|
+
this.botID = settings.MicrosoftAppId ?? process.env.MICROSOFT_APP_ID ?? '';
|
|
91
|
+
}
|
|
92
|
+
getBotUserId() {
|
|
93
|
+
return this.botID;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Send a typing indicator to the Teams conversation.
|
|
97
|
+
* Teams natively supports typing indicators via the Bot Framework.
|
|
98
|
+
*/
|
|
99
|
+
async showTypingIndicator(message, _agent) {
|
|
100
|
+
const turnContext = message.RawEvent['turnContext'];
|
|
101
|
+
if (turnContext) {
|
|
102
|
+
await turnContext.sendActivity({ type: ActivityTypes.Typing });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Fetch thread history from Teams.
|
|
107
|
+
*
|
|
108
|
+
* NOTE: Not yet implemented. Requires Microsoft Graph API access with
|
|
109
|
+
* `ChannelMessage.Read.All` permission. Currently returns empty array,
|
|
110
|
+
* meaning conversations are single-turn.
|
|
111
|
+
*
|
|
112
|
+
* Future implementation would use:
|
|
113
|
+
* `GET /teams/{id}/channels/{id}/messages/{id}/replies`
|
|
114
|
+
*/
|
|
115
|
+
async fetchThreadHistory(_channelId, _threadId) {
|
|
116
|
+
// TODO: Implement via Microsoft Graph API
|
|
117
|
+
// GET /teams/{team-id}/channels/{channel-id}/messages/{message-id}/replies
|
|
118
|
+
// Requires Graph API client and ChannelMessage.Read.All permission
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Post a new streaming message or update an existing one.
|
|
123
|
+
* Falls back to sending a new message if the channel doesn't support updates.
|
|
124
|
+
*
|
|
125
|
+
* Instead of caching update support on the singleton, we detect per-turn
|
|
126
|
+
* by checking `channelId`. Web Chat (`'webchat'`) doesn't support updates;
|
|
127
|
+
* real Teams (`'msteams'`) does. For unknown channels we try/catch.
|
|
128
|
+
*/
|
|
129
|
+
async sendOrUpdateStreamingMessage(originalMessage, currentContent, existingMessageId, _agent) {
|
|
130
|
+
const turnContext = originalMessage.RawEvent['turnContext'];
|
|
131
|
+
const channelSupportsUpdate = this.channelSupportsUpdate(turnContext);
|
|
132
|
+
// Channels that don't support updates (Web Chat, emulator): skip ALL
|
|
133
|
+
// streaming messages. The typing indicator is already shown via
|
|
134
|
+
// showTypingIndicator(), and we'll send only the final Adaptive Card.
|
|
135
|
+
// Without this, the "thinking..." message stays permanently and the
|
|
136
|
+
// final card becomes a second message.
|
|
137
|
+
if (!channelSupportsUpdate) {
|
|
138
|
+
return '';
|
|
139
|
+
}
|
|
140
|
+
if (existingMessageId) {
|
|
141
|
+
try {
|
|
142
|
+
await turnContext.updateActivity({
|
|
143
|
+
id: existingMessageId,
|
|
144
|
+
type: ActivityTypes.Message,
|
|
145
|
+
text: currentContent + ' ...'
|
|
146
|
+
});
|
|
147
|
+
return existingMessageId;
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// Individual update failed — fall through to send new message
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const response = await turnContext.sendActivity(currentContent + ' ...');
|
|
154
|
+
return response?.id ?? '';
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Send the final formatted response as an Adaptive Card.
|
|
158
|
+
*/
|
|
159
|
+
async sendFinalMessage(originalMessage, response) {
|
|
160
|
+
const turnContext = originalMessage.RawEvent['turnContext'];
|
|
161
|
+
await turnContext.sendActivity({
|
|
162
|
+
type: ActivityTypes.Message,
|
|
163
|
+
// Omit `text` when an Adaptive Card is attached — Web Chat and Teams
|
|
164
|
+
// render both the text AND the card, causing duplicate content.
|
|
165
|
+
// The card itself contains all the content; `text` is only a fallback
|
|
166
|
+
// for clients that can't render cards (none of our targets).
|
|
167
|
+
attachments: [{
|
|
168
|
+
contentType: 'application/vnd.microsoft.card.adaptive',
|
|
169
|
+
content: response.RichPayload
|
|
170
|
+
}]
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Update the streaming progress message with the final Adaptive Card.
|
|
175
|
+
* Falls back to sending a new message if the channel doesn't support updates.
|
|
176
|
+
*/
|
|
177
|
+
async updateFinalMessage(originalMessage, messageId, response) {
|
|
178
|
+
const turnContext = originalMessage.RawEvent['turnContext'];
|
|
179
|
+
const channelSupportsUpdate = this.channelSupportsUpdate(turnContext);
|
|
180
|
+
if (channelSupportsUpdate) {
|
|
181
|
+
try {
|
|
182
|
+
await turnContext.updateActivity({
|
|
183
|
+
id: messageId,
|
|
184
|
+
type: ActivityTypes.Message,
|
|
185
|
+
attachments: [{
|
|
186
|
+
contentType: 'application/vnd.microsoft.card.adaptive',
|
|
187
|
+
content: response.RichPayload
|
|
188
|
+
}]
|
|
189
|
+
});
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// Fall through to send as new message
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// Fallback: send as new message
|
|
197
|
+
await this.sendFinalMessage(originalMessage, response);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Format agent response as a rich Teams Adaptive Card.
|
|
201
|
+
*
|
|
202
|
+
* When a full `ExecuteAgentResult` is available, builds a rich card with
|
|
203
|
+
* agent header, action buttons, explorer links, and metadata footer.
|
|
204
|
+
* Falls back to basic markdown-to-TextBlock conversion otherwise.
|
|
205
|
+
*/
|
|
206
|
+
async formatResponse(result, agent, responseText, metadata) {
|
|
207
|
+
const richPayload = result
|
|
208
|
+
? buildRichAdaptiveCard(result, agent, responseText, {
|
|
209
|
+
explorerBaseURL: this.settings.ExplorerBaseURL,
|
|
210
|
+
artifactId: metadata?.ArtifactId,
|
|
211
|
+
conversationId: metadata?.ConversationId,
|
|
212
|
+
})
|
|
213
|
+
: markdownToAdaptiveCard(responseText);
|
|
214
|
+
return {
|
|
215
|
+
PlainText: responseText,
|
|
216
|
+
RichPayload: richPayload,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Strip the bot's `<at>` mention from the message text.
|
|
221
|
+
* Teams @mentions use the format `<at>BotName</at>`.
|
|
222
|
+
*/
|
|
223
|
+
stripBotMention(text) {
|
|
224
|
+
return text.replace(/<at>[^<]+<\/at>/g, '').trim();
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Look up the email for a Teams user.
|
|
228
|
+
* In Teams, the email is often available directly on the activity's `from` property.
|
|
229
|
+
*
|
|
230
|
+
* @param platformUserId - Teams user ID.
|
|
231
|
+
* @returns Email address, or `null` if not available.
|
|
232
|
+
*/
|
|
233
|
+
async lookupUserEmail(platformUserId) {
|
|
234
|
+
// Teams provides email in the activity `from` field for most message types
|
|
235
|
+
// The caller already extracted SenderEmail from the activity
|
|
236
|
+
// For additional lookups, Graph API would be needed
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Detect whether the current channel supports `updateActivity`.
|
|
241
|
+
* Real Teams (`msteams`) supports updates; Web Chat (`webchat`) does not.
|
|
242
|
+
* Unknown channels are assumed to support updates (try/catch guards the call).
|
|
243
|
+
*/
|
|
244
|
+
channelSupportsUpdate(turnContext) {
|
|
245
|
+
const channelId = turnContext.activity?.channelId;
|
|
246
|
+
if (channelId === 'webchat' || channelId === 'emulator') {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
static { this.FORM_DEDUP_TTL_MS = 30_000; }
|
|
252
|
+
/**
|
|
253
|
+
* Handle an Adaptive Card Action.Submit from a form rendered in Teams.
|
|
254
|
+
*
|
|
255
|
+
* Extracts field values from `activity.value`, builds the same
|
|
256
|
+
* `@{_mode:"form",...}` payload that Slack uses for modal submissions,
|
|
257
|
+
* and routes through `HandleMessage()` as a synthetic IncomingMessage.
|
|
258
|
+
*
|
|
259
|
+
* Does NOT send a bot acknowledgement message — the agent's own response
|
|
260
|
+
* serves as the acknowledgement, and a bot-authored echo of the form values
|
|
261
|
+
* would pollute the conversation history the agent sees.
|
|
262
|
+
*/
|
|
263
|
+
async HandleFormSubmit(turnContext) {
|
|
264
|
+
try {
|
|
265
|
+
const activity = turnContext.activity;
|
|
266
|
+
// Dedup: Web Chat may fire both invoke + message for the same submit
|
|
267
|
+
const activityId = activity.id ?? '';
|
|
268
|
+
if (activityId && this.isRecentFormActivity(activityId)) {
|
|
269
|
+
LogStatus(`Teams form submit: duplicate activity '${activityId}', skipping`);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (activityId) {
|
|
273
|
+
this.recentFormActivityIds.set(activityId, Date.now());
|
|
274
|
+
}
|
|
275
|
+
const formValues = activity.value;
|
|
276
|
+
if (!formValues || formValues['action'] !== 'mj:form_submit') {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
LogStatus(`Teams form submit: user='${activity.from?.name}', conversation='${activity.conversation?.id}'`);
|
|
280
|
+
// Extract form fields: keys starting with "mj_form_" are input field IDs
|
|
281
|
+
const fields = this.extractFormFields(formValues);
|
|
282
|
+
if (fields.length === 0) {
|
|
283
|
+
LogStatus('Teams form submit: no fields extracted, ignoring');
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
// Build the @{_mode:"form"} message matching Explorer/Slack format
|
|
287
|
+
const formResponse = JSON.stringify({
|
|
288
|
+
_mode: 'form',
|
|
289
|
+
action: 'formSubmit',
|
|
290
|
+
fields,
|
|
291
|
+
});
|
|
292
|
+
const messageText = `@${formResponse}`;
|
|
293
|
+
// Build a synthetic IncomingMessage and route through the adapter
|
|
294
|
+
const senderEmail = activity.from?.['email'];
|
|
295
|
+
// Store conversation reference for proactive messaging
|
|
296
|
+
const conversationRef = TurnContext.getConversationReference(activity);
|
|
297
|
+
this.storeConversationRef(activity.conversation?.id ?? '', conversationRef);
|
|
298
|
+
const incomingMessage = {
|
|
299
|
+
MessageID: activityId,
|
|
300
|
+
Text: messageText,
|
|
301
|
+
SenderID: activity.from?.id ?? '',
|
|
302
|
+
SenderName: activity.from?.name ?? '',
|
|
303
|
+
SenderEmail: senderEmail,
|
|
304
|
+
ChannelID: activity.channelId ?? '',
|
|
305
|
+
ThreadID: activity.conversation?.id ?? null,
|
|
306
|
+
IsDirectMessage: activity.conversation?.conversationType === 'personal',
|
|
307
|
+
IsBotMention: true,
|
|
308
|
+
Timestamp: activity.timestamp ? new Date(activity.timestamp) : new Date(),
|
|
309
|
+
RawEvent: { activity, turnContext },
|
|
310
|
+
};
|
|
311
|
+
await this.HandleMessage(incomingMessage);
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
LogError('Failed to handle Teams form submission:', undefined, error);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Extract form field values from an Action.Submit activity.value object.
|
|
319
|
+
* Keys starting with "mj_form_" are input field IDs set by the card builder.
|
|
320
|
+
*/
|
|
321
|
+
extractFormFields(formValues) {
|
|
322
|
+
const fields = [];
|
|
323
|
+
for (const [key, val] of Object.entries(formValues)) {
|
|
324
|
+
if (key.startsWith('mj_form_') && val != null && val !== '') {
|
|
325
|
+
const questionId = key.replace('mj_form_', '');
|
|
326
|
+
const strValue = String(val);
|
|
327
|
+
fields.push({
|
|
328
|
+
name: questionId,
|
|
329
|
+
value: strValue,
|
|
330
|
+
label: questionId,
|
|
331
|
+
displayValue: strValue,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return fields;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Check if the bot was @mentioned in a Teams activity.
|
|
339
|
+
*/
|
|
340
|
+
hasBotMention(activity) {
|
|
341
|
+
const entities = activity.entities ?? [];
|
|
342
|
+
return entities.some(e => e.type === 'mention' &&
|
|
343
|
+
e.mentioned != null &&
|
|
344
|
+
e.mentioned?.id === this.botID);
|
|
345
|
+
}
|
|
346
|
+
// ─── TTL helpers ──────────────────────────────────────────────────
|
|
347
|
+
/** Store a conversation reference with TTL-based eviction. */
|
|
348
|
+
storeConversationRef(conversationId, ref) {
|
|
349
|
+
this.conversationReferences.set(conversationId, { ref, timestamp: Date.now() });
|
|
350
|
+
// Evict expired entries
|
|
351
|
+
const now = Date.now();
|
|
352
|
+
for (const [key, entry] of this.conversationReferences) {
|
|
353
|
+
if (now - entry.timestamp > TeamsAdapter.CONV_REF_TTL_MS) {
|
|
354
|
+
this.conversationReferences.delete(key);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (this.conversationReferences.size > TeamsAdapter.CONV_REF_MAX_SIZE) {
|
|
358
|
+
const oldest = [...this.conversationReferences.entries()]
|
|
359
|
+
.sort((a, b) => a[1].timestamp - b[1].timestamp);
|
|
360
|
+
for (const [key] of oldest.slice(0, oldest.length - TeamsAdapter.CONV_REF_MAX_SIZE)) {
|
|
361
|
+
this.conversationReferences.delete(key);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
/** Check if a form activity was recently processed (dedup). */
|
|
366
|
+
isRecentFormActivity(activityId) {
|
|
367
|
+
const timestamp = this.recentFormActivityIds.get(activityId);
|
|
368
|
+
if (timestamp == null)
|
|
369
|
+
return false;
|
|
370
|
+
if (Date.now() - timestamp > TeamsAdapter.FORM_DEDUP_TTL_MS) {
|
|
371
|
+
this.recentFormActivityIds.delete(activityId);
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
// Clean up expired entries opportunistically
|
|
375
|
+
for (const [key, ts] of this.recentFormActivityIds) {
|
|
376
|
+
if (Date.now() - ts > TeamsAdapter.FORM_DEDUP_TTL_MS) {
|
|
377
|
+
this.recentFormActivityIds.delete(key);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
//# sourceMappingURL=TeamsAdapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TeamsAdapter.js","sourceRoot":"","sources":["../../src/teams/TeamsAdapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @memberjunction/messaging-adapters
|
|
3
|
+
* @description Microsoft Teams Server Extension — registers Bot Framework webhook
|
|
4
|
+
* route and delegates to TeamsAdapter.
|
|
5
|
+
*
|
|
6
|
+
* This is the entry point for Teams integration. It extends `BaseServerExtension`
|
|
7
|
+
* to plug into MJServer's extension framework, registering a POST webhook endpoint
|
|
8
|
+
* that receives Bot Framework activities.
|
|
9
|
+
*
|
|
10
|
+
* ## Azure Bot Setup
|
|
11
|
+
*
|
|
12
|
+
* 1. Register a bot in the Azure Bot Service
|
|
13
|
+
* 2. Set the messaging endpoint to `{your-server}/webhook/teams`
|
|
14
|
+
* 3. Note the Microsoft App ID and Password
|
|
15
|
+
* 4. Create a Teams app manifest pointing to the bot registration
|
|
16
|
+
* 5. Install the app in your Teams organization
|
|
17
|
+
* 6. Configure the App ID and Password in `mj.config.cjs`
|
|
18
|
+
*
|
|
19
|
+
* ## Configuration
|
|
20
|
+
*
|
|
21
|
+
* ```javascript
|
|
22
|
+
* // mj.config.cjs
|
|
23
|
+
* serverExtensions: [{
|
|
24
|
+
* Enabled: true,
|
|
25
|
+
* DriverClass: 'TeamsMessagingExtension',
|
|
26
|
+
* RootPath: '/webhook/teams',
|
|
27
|
+
* Settings: {
|
|
28
|
+
* DefaultAgentName: 'Sage',
|
|
29
|
+
* ContextUserEmail: 'bot@company.com',
|
|
30
|
+
* MicrosoftAppId: process.env.MICROSOFT_APP_ID,
|
|
31
|
+
* MicrosoftAppPassword: process.env.MICROSOFT_APP_PASSWORD,
|
|
32
|
+
* MaxThreadMessages: 50,
|
|
33
|
+
* StreamingUpdateIntervalMs: 2000,
|
|
34
|
+
* }
|
|
35
|
+
* }]
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
import { Application } from 'express';
|
|
39
|
+
import { BaseServerExtension, ServerExtensionConfig, ExtensionInitResult, ExtensionHealthResult } from '@memberjunction/server-extensions-core';
|
|
40
|
+
/**
|
|
41
|
+
* Server Extension that registers the Bot Framework webhook route and delegates
|
|
42
|
+
* message handling to the `TeamsAdapter`.
|
|
43
|
+
*
|
|
44
|
+
* Uses the Bot Framework SDK's `CloudAdapter` for authentication and activity
|
|
45
|
+
* processing, which handles JWT token validation automatically.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* // Auto-discovered by MJServer when package is imported and config is present
|
|
50
|
+
* // No manual instantiation needed — just add to mj.config.cjs
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare class TeamsMessagingExtension extends BaseServerExtension {
|
|
54
|
+
/** The Teams adapter handling message processing. */
|
|
55
|
+
private adapter;
|
|
56
|
+
/** The Bot Framework cloud adapter for JWT validation and activity processing. */
|
|
57
|
+
private cloudAdapterInstance;
|
|
58
|
+
/**
|
|
59
|
+
* Initialize the Teams extension.
|
|
60
|
+
*
|
|
61
|
+
* Creates the Bot Framework `CloudAdapter` for authentication,
|
|
62
|
+
* initializes the `TeamsAdapter`, and registers the webhook route.
|
|
63
|
+
*/
|
|
64
|
+
Initialize(app: Application, config: ServerExtensionConfig): Promise<ExtensionInitResult>;
|
|
65
|
+
/**
|
|
66
|
+
* Shut down the Teams extension. Releases the adapter and cloud adapter.
|
|
67
|
+
*/
|
|
68
|
+
Shutdown(): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Health check for the Teams extension.
|
|
71
|
+
* Reports whether the adapter and cloud adapter are initialized.
|
|
72
|
+
*/
|
|
73
|
+
HealthCheck(): Promise<ExtensionHealthResult>;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=TeamsMessagingExtension.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TeamsMessagingExtension.d.ts","sourceRoot":"","sources":["../../src/teams/TeamsMessagingExtension.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAgB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAS/C,OAAO,EACH,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,EACnB,qBAAqB,EACxB,MAAM,wCAAwC,CAAC;AAIhD;;;;;;;;;;;;GAYG;AACH,qBACa,uBAAwB,SAAQ,mBAAmB;IAC5D,qDAAqD;IACrD,OAAO,CAAC,OAAO,CAA6B;IAE5C,kFAAkF;IAClF,OAAO,CAAC,oBAAoB,CAA6B;IAEzD;;;;;OAKG;IACG,UAAU,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA+E/F;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAM/B;;;OAGG;IACG,WAAW,IAAI,OAAO,CAAC,qBAAqB,CAAC;CAUtD"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @memberjunction/messaging-adapters
|
|
3
|
+
* @description Microsoft Teams Server Extension — registers Bot Framework webhook
|
|
4
|
+
* route and delegates to TeamsAdapter.
|
|
5
|
+
*
|
|
6
|
+
* This is the entry point for Teams integration. It extends `BaseServerExtension`
|
|
7
|
+
* to plug into MJServer's extension framework, registering a POST webhook endpoint
|
|
8
|
+
* that receives Bot Framework activities.
|
|
9
|
+
*
|
|
10
|
+
* ## Azure Bot Setup
|
|
11
|
+
*
|
|
12
|
+
* 1. Register a bot in the Azure Bot Service
|
|
13
|
+
* 2. Set the messaging endpoint to `{your-server}/webhook/teams`
|
|
14
|
+
* 3. Note the Microsoft App ID and Password
|
|
15
|
+
* 4. Create a Teams app manifest pointing to the bot registration
|
|
16
|
+
* 5. Install the app in your Teams organization
|
|
17
|
+
* 6. Configure the App ID and Password in `mj.config.cjs`
|
|
18
|
+
*
|
|
19
|
+
* ## Configuration
|
|
20
|
+
*
|
|
21
|
+
* ```javascript
|
|
22
|
+
* // mj.config.cjs
|
|
23
|
+
* serverExtensions: [{
|
|
24
|
+
* Enabled: true,
|
|
25
|
+
* DriverClass: 'TeamsMessagingExtension',
|
|
26
|
+
* RootPath: '/webhook/teams',
|
|
27
|
+
* Settings: {
|
|
28
|
+
* DefaultAgentName: 'Sage',
|
|
29
|
+
* ContextUserEmail: 'bot@company.com',
|
|
30
|
+
* MicrosoftAppId: process.env.MICROSOFT_APP_ID,
|
|
31
|
+
* MicrosoftAppPassword: process.env.MICROSOFT_APP_PASSWORD,
|
|
32
|
+
* MaxThreadMessages: 50,
|
|
33
|
+
* StreamingUpdateIntervalMs: 2000,
|
|
34
|
+
* }
|
|
35
|
+
* }]
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
39
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
40
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
41
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
42
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
43
|
+
};
|
|
44
|
+
import express from 'express';
|
|
45
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
46
|
+
import { LogError, LogStatus } from '@memberjunction/core';
|
|
47
|
+
import { CloudAdapter, ConfigurationBotFrameworkAuthentication, ActivityTypes, } from 'botbuilder';
|
|
48
|
+
import { BaseServerExtension } from '@memberjunction/server-extensions-core';
|
|
49
|
+
import { TeamsAdapter } from './TeamsAdapter.js';
|
|
50
|
+
/**
|
|
51
|
+
* Server Extension that registers the Bot Framework webhook route and delegates
|
|
52
|
+
* message handling to the `TeamsAdapter`.
|
|
53
|
+
*
|
|
54
|
+
* Uses the Bot Framework SDK's `CloudAdapter` for authentication and activity
|
|
55
|
+
* processing, which handles JWT token validation automatically.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* // Auto-discovered by MJServer when package is imported and config is present
|
|
60
|
+
* // No manual instantiation needed — just add to mj.config.cjs
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
let TeamsMessagingExtension = class TeamsMessagingExtension extends BaseServerExtension {
|
|
64
|
+
constructor() {
|
|
65
|
+
super(...arguments);
|
|
66
|
+
/** The Teams adapter handling message processing. */
|
|
67
|
+
this.adapter = null;
|
|
68
|
+
/** The Bot Framework cloud adapter for JWT validation and activity processing. */
|
|
69
|
+
this.cloudAdapterInstance = null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Initialize the Teams extension.
|
|
73
|
+
*
|
|
74
|
+
* Creates the Bot Framework `CloudAdapter` for authentication,
|
|
75
|
+
* initializes the `TeamsAdapter`, and registers the webhook route.
|
|
76
|
+
*/
|
|
77
|
+
async Initialize(app, config) {
|
|
78
|
+
try {
|
|
79
|
+
const settings = config.Settings;
|
|
80
|
+
const appId = settings.MicrosoftAppId ?? process.env.MICROSOFT_APP_ID ?? '';
|
|
81
|
+
const appPassword = settings.MicrosoftAppPassword ?? process.env.MICROSOFT_APP_PASSWORD ?? '';
|
|
82
|
+
const appTenantId = settings.MicrosoftAppTenantId ?? process.env.MICROSOFT_APP_TENANT_ID ?? '';
|
|
83
|
+
const appType = settings.MicrosoftAppType ?? process.env.MICROSOFT_APP_TYPE ?? (appTenantId ? 'SingleTenant' : 'MultiTenant');
|
|
84
|
+
// Create Bot Framework authentication
|
|
85
|
+
const botFrameworkAuth = new ConfigurationBotFrameworkAuthentication({
|
|
86
|
+
MicrosoftAppId: appId,
|
|
87
|
+
MicrosoftAppPassword: appPassword,
|
|
88
|
+
MicrosoftAppType: appType,
|
|
89
|
+
MicrosoftAppTenantId: appTenantId,
|
|
90
|
+
});
|
|
91
|
+
this.cloudAdapterInstance = new CloudAdapter(botFrameworkAuth);
|
|
92
|
+
// Set up error handler for the cloud adapter
|
|
93
|
+
this.cloudAdapterInstance.onTurnError = async (context, error) => {
|
|
94
|
+
LogError(`Teams Bot Framework error: ${error.message}`, undefined, error);
|
|
95
|
+
await context.sendActivity('Sorry, an error occurred processing your message.');
|
|
96
|
+
};
|
|
97
|
+
// Create and initialize the Teams adapter
|
|
98
|
+
this.adapter = new TeamsAdapter(settings);
|
|
99
|
+
await this.adapter.Initialize();
|
|
100
|
+
// Register the Bot Framework webhook route
|
|
101
|
+
const adapter = this.adapter;
|
|
102
|
+
const cloudAdapter = this.cloudAdapterInstance;
|
|
103
|
+
app.post(config.RootPath, express.json(), async (req, res) => {
|
|
104
|
+
try {
|
|
105
|
+
await cloudAdapter.process(req, res, async (turnContext) => {
|
|
106
|
+
const activityValue = turnContext.activity.value;
|
|
107
|
+
const isFormSubmit = activityValue != null &&
|
|
108
|
+
activityValue['action'] === 'mj:form_submit';
|
|
109
|
+
if (isFormSubmit) {
|
|
110
|
+
// Action.Submit from an Adaptive Card form.
|
|
111
|
+
// Web Chat sends this as a Message; real Teams sends
|
|
112
|
+
// an Invoke. Handle both here, then send InvokeResponse
|
|
113
|
+
// if this was an invoke activity.
|
|
114
|
+
await adapter.HandleFormSubmit(turnContext);
|
|
115
|
+
if (turnContext.activity.type === ActivityTypes.Invoke) {
|
|
116
|
+
await turnContext.sendActivity({
|
|
117
|
+
type: ActivityTypes.InvokeResponse,
|
|
118
|
+
value: { status: 200 },
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else if (turnContext.activity.type === ActivityTypes.Message) {
|
|
123
|
+
const incomingMessage = adapter.MapTeamsActivity(turnContext);
|
|
124
|
+
await adapter.HandleMessage(incomingMessage);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
LogError('Error processing Teams webhook:', undefined, error);
|
|
130
|
+
if (!res.headersSent) {
|
|
131
|
+
res.status(500).send('Internal Server Error');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
return {
|
|
136
|
+
Success: true,
|
|
137
|
+
Message: `Teams messaging extension loaded for agent ${settings.DefaultAgentName}`,
|
|
138
|
+
RegisteredRoutes: [`POST ${config.RootPath}`]
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
143
|
+
return {
|
|
144
|
+
Success: false,
|
|
145
|
+
Message: `Failed to initialize Teams extension: ${message}`
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Shut down the Teams extension. Releases the adapter and cloud adapter.
|
|
151
|
+
*/
|
|
152
|
+
async Shutdown() {
|
|
153
|
+
LogStatus('Shutting down Teams messaging extension');
|
|
154
|
+
this.adapter = null;
|
|
155
|
+
this.cloudAdapterInstance = null;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Health check for the Teams extension.
|
|
159
|
+
* Reports whether the adapter and cloud adapter are initialized.
|
|
160
|
+
*/
|
|
161
|
+
async HealthCheck() {
|
|
162
|
+
return {
|
|
163
|
+
Healthy: this.adapter !== null && this.cloudAdapterInstance !== null,
|
|
164
|
+
Name: 'TeamsMessagingExtension',
|
|
165
|
+
Details: {
|
|
166
|
+
adapterInitialized: this.adapter !== null,
|
|
167
|
+
cloudAdapterInitialized: this.cloudAdapterInstance !== null
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
TeamsMessagingExtension = __decorate([
|
|
173
|
+
RegisterClass(BaseServerExtension, 'TeamsMessagingExtension')
|
|
174
|
+
], TeamsMessagingExtension);
|
|
175
|
+
export { TeamsMessagingExtension };
|
|
176
|
+
//# sourceMappingURL=TeamsMessagingExtension.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TeamsMessagingExtension.js","sourceRoot":"","sources":["../../src/teams/TeamsMessagingExtension.ts"],"names":[],"mappings":"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"}
|