611be70362
- 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
170 lines
7.2 KiB
TypeScript
170 lines
7.2 KiB
TypeScript
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'})`);
|
||
}
|
||
}
|