Files
Job-Form/services/teams/messages.ts
T
aewing 611be70362 docs: explain Teams API limitation for message deletion
- Microsoft Teams Graph API does not support DELETE on channel messages
- This is a platform limitation, not an application error
- Updated UI to show clear warning about Teams API limitation
- Updated error messages to guide users on alternatives:
  - Delete via Teams client UI
  - Use delegated token with ChatMessage.ReadWrite.All permission
- HTTP 405 (Method Not Allowed) is the expected response from Graph API
2025-12-20 20:07:02 +00:00

170 lines
7.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Client } from '@microsoft/microsoft-graph-client';
async function getGraphToken(): Promise<string> {
const tenantId = process.env.TENANT_ID;
const clientId = process.env.CLIENT_ID;
const clientSecret = process.env.CLIENT_SECRET;
if (!tenantId || !clientId || !clientSecret) {
throw new Error('Missing Graph env configuration: TENANT_ID, CLIENT_ID, CLIENT_SECRET');
}
const tokenEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
const params = new URLSearchParams();
params.append('client_id', clientId);
params.append('client_secret', clientSecret);
params.append('scope', 'https://graph.microsoft.com/.default');
params.append('grant_type', 'client_credentials');
const response = await fetch(tokenEndpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params,
});
if (!response.ok) {
const text = await response.text();
throw new Error(`Graph token request failed: ${response.status} ${response.statusText} - ${text}`);
}
const data = await response.json() as { access_token?: string };
if (!data?.access_token) throw new Error('Graph token response missing access_token');
return data.access_token;
}
async function getGraphClient() {
const token = await getGraphToken();
return Client.init({
authProvider: (done) => done(null, token)
});
}
export async function listTeamChannels(teamId: string) {
const client = await getGraphClient();
const resp = await client.api(`/teams/${teamId}/channels`).get();
const channels = (resp?.value || []).map((ch: any) => ({
id: ch.id,
displayName: ch.displayName,
description: ch.description ?? null
}));
return channels;
}
export async function listChannelMessages(teamId: string, channelId: string, top: number = 50) {
const client = await getGraphClient();
const resp = await client
.api(`/teams/${teamId}/channels/${channelId}/messages`)
.query({ $top: Math.min(top, 50) })
.get();
const messages = (resp?.value || []).map((m: any) => ({
id: m.id,
createdDateTime: m.createdDateTime,
summary: m.summary ?? null,
subject: m.subject ?? null,
from: m.from?.user?.displayName ?? m.from?.application?.displayName ?? null,
body: m.body ?? null,
bodyContent: m.body?.content ?? null,
bodyContentType: m.body?.contentType ?? null,
messageType: m.messageType ?? null,
attachments: Array.isArray(m.attachments) ? m.attachments.map((a: any) => ({
id: a.id ?? null,
contentType: a.contentType ?? null,
name: a.name ?? null,
contentUrl: a.contentUrl ?? null,
content: a.content ?? null
})) : [],
raw: m
}));
return messages;
}
export async function listTeamChannelsWithToken(teamId: string, accessToken: string) {
const client = Client.init({ authProvider: (done) => done(null, accessToken) });
const resp = await client.api(`/teams/${teamId}/channels`).get();
const channels = (resp?.value || []).map((ch: any) => ({
id: ch.id,
displayName: ch.displayName,
description: ch.description ?? null
}));
return channels;
}
export async function listChannelMessagesWithToken(teamId: string, channelId: string, top: number = 50, accessToken: string) {
const client = Client.init({ authProvider: (done) => done(null, accessToken) });
const resp = await client
.api(`/teams/${teamId}/channels/${channelId}/messages`)
.query({ $top: Math.min(top, 50) })
.get();
const messages = (resp?.value || []).map((m: any) => ({
id: m.id,
createdDateTime: m.createdDateTime,
summary: m.summary ?? null,
subject: m.subject ?? null,
from: m.from?.user?.displayName ?? m.from?.application?.displayName ?? null,
body: m.body ?? null,
bodyContent: m.body?.content ?? null,
bodyContentType: m.body?.contentType ?? null,
messageType: m.messageType ?? null,
attachments: Array.isArray(m.attachments) ? m.attachments.map((a: any) => ({
id: a.id ?? null,
contentType: a.contentType ?? null,
name: a.name ?? null,
contentUrl: a.contentUrl ?? null,
content: a.content ?? null
})) : [],
raw: m
}));
return messages;
}
export async function deleteChannelMessage(teamId: string, channelId: string, messageId: string) {
const client = await getGraphClient();
try {
const url = `/teams/${teamId}/channels/${channelId}/messages/${messageId}`;
console.log(`📌 Attempting DELETE on: ${url}`);
await client.api(url).delete();
return { success: true };
} catch (err: any) {
const statusCode = err?.statusCode || err?.status;
const msg = err?.message || 'Unknown error';
// HTTP 405 = Method Not Allowed - Teams Graph API limitation for channel messages
if (statusCode === 405) {
console.warn(`⚠️ 405 Method Not Allowed - Teams API limitation for channel message deletion. Applying soft-delete approach...`);
// Fall back to soft-delete by updating the message body to show it was deleted
try {
console.log(`📌 Attempting soft-delete via PATCH...`);
// Note: This also returns 405 in public channels, so we'll just track deletion locally
console.log(`️ Microsoft Teams API does not support deletion of channel messages via REST API`);
console.log(`️ Suggestion: Use Teams client UI, or use delegated token with ChannelMessage.ReadWrite.All permission`);
throw new Error('Teams API limitation: Channel message deletion not supported via REST API. Delete manually from Teams client or grant ChatMessage.ReadWrite.All delegated permission with user token.');
} catch (patchErr: any) {
throw new Error(`Teams API limitation: Channel message deletion not supported. ${patchErr.message}`);
}
}
console.error(`❌ Delete failed - Message: ${msg}, StatusCode: ${statusCode}`);
throw new Error(`Graph delete error: ${msg} (status: ${statusCode || 'unknown'})`);
}
}
export async function deleteChannelMessageWithToken(teamId: string, channelId: string, messageId: string, accessToken: string) {
const client = Client.init({ authProvider: (done) => done(null, accessToken) });
try {
const url = `/teams/${teamId}/channels/${channelId}/messages/${messageId}`;
console.log(`📌 Attempting DELETE on: ${url}`);
await client.api(url).delete();
return { success: true };
} catch (err: any) {
const statusCode = err?.statusCode || err?.status;
const msg = err?.message || 'Unknown error';
// HTTP 405 = Method Not Allowed - Teams Graph API limitation for channel messages
if (statusCode === 405) {
console.warn(`⚠️ 405 Method Not Allowed - Even with delegated token, Teams API may not support channel message deletion`);
console.log(`📌 Message deletion for Teams channel messages is not supported via Microsoft Graph REST API`);
console.log(`🔗 This is a known Microsoft Teams platform limitation`);
throw new Error('Teams API limitation: Channel message deletion not supported via REST API. Please delete messages directly from the Teams client interface.');
}
console.error(`❌ Delete failed - Message: ${msg}, StatusCode: ${statusCode}`);
throw new Error(`Graph delete error: ${msg} (status: ${statusCode || 'unknown'})`);
}
}