UNPKG

@microsoft/mgt-chat

Version:

Chat component for providing Teams conversations via a React component built using the ACS UI library

175 lines (154 loc) 5.47 kB
import { MessageThreadProps, ChatMessage as ACSChatMessage } from '@azure/communication-react'; import { Chat } from '@microsoft/microsoft-graph-types'; import { ActiveAccountChanged, IGraph, LoginChangedEvent, Providers, ProviderState } from '@microsoft/mgt-element'; import { produce } from 'immer'; import { loadChat, loadChatThread, loadMoreChatMessages, MessageCollection } from './graph.chat'; import { graphChatMessageToACSChatMessage } from './acs.chat'; type GraphChatClient = Pick< MessageThreadProps, | 'userId' | 'messages' | 'participantCount' | 'disableEditing' | 'onLoadPreviousChatMessages' | 'numberOfChatMessagesToReload' >; type StatefulClient<T> = { /** * Get the current state of the client */ getState(): T; /** * Register a callback to receive state updates * * @param handler Callback to receive state updates */ onStateChange(handler: (state: T) => void): void; /** * Remove a callback from receiving state updates * * @param handler Callback to be unregistered */ offStateChange(handler: (state: T) => void): void; }; const MessageCreatedComparator = (a: ACSChatMessage, b: ACSChatMessage) => a.createdOn.getTime() - b.createdOn.getTime(); class StatefulGraphChatClient implements StatefulClient<GraphChatClient> { private _subscribers: ((state: GraphChatClient) => void)[] = []; private _messagesPerCall = 5; private _nextLink: string = ''; private _chat?: Chat = undefined; constructor() { this.updateCurrentUserId(); Providers.globalProvider.onStateChanged(this.onStateChanged); Providers.globalProvider.onActiveAccountChanged(this.onActiveAccountChanged); } public onStateChange(handler: (state: GraphChatClient) => void): void { if (!this._subscribers.includes(handler)) { this._subscribers.push(handler); } } public offStateChange(handler: (state: GraphChatClient) => void): void { const index = this._subscribers.indexOf(handler); if (index !== -1) { this._subscribers = this._subscribers.splice(index, 1); } } private notifyStateChange() { this._subscribers.forEach(handler => handler(this._state)); } public getState(): GraphChatClient { return this._state; } private onStateChanged = (e: LoginChangedEvent) => { switch (Providers.globalProvider.state) { case ProviderState.SignedIn: this.updateCurrentUserId(); // update userId // load messages // configure subscriptions // emit new state; return; case ProviderState.SignedOut: // clear userId // clear subscriptions // clear messages // emit new state return; case ProviderState.Loading: default: // do nothing for now return; } }; private onActiveAccountChanged = (e: ActiveAccountChanged) => { this.updateCurrentUserId(); }; private updateCurrentUserId() { this.userId = Providers.globalProvider.getActiveAccount?.().id.split('.')[0] || ''; } private _userId: string = ''; private set userId(userId: string) { if (this._userId === userId) { return; } this._userId = userId; this._state = produce(this._state, (draft: GraphChatClient) => { draft.userId = userId; }); this.notifyStateChange(); } private _chatId: string = ''; public set chatId(value: string) { // take no action if the chatId is the same if (this._chatId === value) { return; } this._chatId = value; void this.updateFollowedChat(); } private async updateFollowedChat() { // Subscribe to notifications for messages // Subscribe to notifications for the chat itself? // load Chat Data this._chat = await loadChat(this.graph, this._chatId); // Allow messages to be loaded via the loadMoreMessages callback this._state = produce(this._state, (draft: GraphChatClient) => { draft.participantCount = this._chat?.members?.length || 0; }); this.notifyStateChange(); } private loadMoreMessages = async () => { let result: MessageCollection; if (this._nextLink) { result = await loadMoreChatMessages(this.graph, this._nextLink); } else { result = await loadChatThread(this.graph, this._chatId, this._messagesPerCall); } this._nextLink = result.nextLink; this._state = produce(this._state, (draft: GraphChatClient) => { const nextMessages = result.value // trying to filter out system messages on the graph request causes a 400 // delted messages are returned as messages with no content, which we can't filter on the graph request // so we filter them out here .filter(m => m.messageType === 'message' && m.body?.content) .map(m => graphChatMessageToACSChatMessage(m, this._userId)); draft.messages = nextMessages.concat(draft.messages as ACSChatMessage[]).sort(MessageCreatedComparator); }); this.notifyStateChange(); // return true when there are no more messages to load return !Boolean(this._nextLink); }; private get graph(): IGraph { return Providers.globalProvider.graph.forComponent('mgt-chat'); } private _state: GraphChatClient = { userId: '', messages: [], participantCount: 0, disableEditing: true, onLoadPreviousChatMessages: this.loadMoreMessages, numberOfChatMessagesToReload: this._messagesPerCall }; } export { StatefulGraphChatClient };