communication-ui-library
communication-ui-library copied to clipboard
ChatAdapter errors is not thrown to the error listener
Describe the bug; what happened?
I implemented the error handler for the ChatAdapter
on the error listener, but the errors are not coming to it. I can see the error in the console logs.
export const errorCodes = [401, 403, 404];
...
const errorHandler = (e: ChatAdapterError) => {
if (e.innerError.statusCode && errorCodes.includes(e.innerError.statusCode)) {
chatAdapter?.dispose();
}
};
...
chatAdapter.on('error', errorHandler);
...
<ChatComposite
fluentTheme={customTheme}
onFetchAvatarPersonaData={() => Promise.resolve(avatarPersonaData)}
options={{ topic: false }}
adapter={chatAdapter}
/>
Image:
What are the steps to reproduce the issue?
- Start a regular chat
- Leave one side, ending the thread in the server side
- Continue typing on the other side
In this case, we implemented to end the chat thread on the server side when one closes the browser, "unload" event. So, no event of removing the participant is done to be listened to on the other side. When the other side continues typing, it gets the error "Not Found." But the ChatAdapter
doesn't throw it to the error listener.
What behavior did you expect?
Errors are to be thrown to the ChatAdapter
error listener.
If applicable, provide screenshots: Added above
In what environment did you see the issue?
@azure/communication-chat
: "^1.2.0",
@azure/communication-common
: "^2.1.0",
@azure/communication-react
: "^1.3.1",
- OS & Device: MacOS 12.6
- Browser: Google Chrome / Mozilla FireFox
- Browser Version: Google Chrome Version 108.0.5355.0 (Official Build) dev (arm64) / Mozilla FireFox 105.0.3 (64-bit)
Is there any additional information? No
Thanks for raising the issue @DercilioFontes! We have a afterCreate
function that is an optional property to our useAzureCommunicationChatAdapter
constructor for the adapter that is meant to be used to subscribe to these events. this little snippet is a example of how to use this function:
const errorHandler = (e: ChatAdapterError) => {
if (e.innerError.statusCode && errorCodes.includes(e.innerError.statusCode)) {
chatAdapter?.dispose();
}
};
...
const chatAdapterArgs: AzureCommunicationChatAdapterArgs = {
threadId: ...,
userId: ...,
token: ...,
displayName: ...,
endpoint: ...
};
...
const afterCreate = useCallBack(
async (adapter: ChatAdapter): Promise<ChatAdapter> => {
adapter.on('error', errorHandler);
}
)
const adapter = useAzureCommunicationsChatAdapter(chatAdapterArgs, afterCreate);
Please let me know if you have any questions about this implementation!
@dmceachernmsft, I tried to use your suggestion, but it will require changes in my code from how it is implemented now. I'll try when I have more time. Giving more info about how I implemented it, it follows:
I have one useEffect
to create the adapter.
const [chatAdapter, setChatAdapter] = useState<ChatAdapter>();
...
// Reserve this useEffect for only creating the ChatAdapter
// Any dependency passed here that is updated will trigger useEffect call again and recreate the ChatAdapter losing the initial messages
useEffect(() => {
(async () => {
if (chatUserConn?.threadId) {
const adapter = await createChatAdapter(
chatUserConn.endpointUrl,
chatUserConn.communicationUserId,
chatUserConn.displayName,
chatUserConn.token,
chatUserConn.threadId
);
setChatAdapter(adapter);
}
})();
}, [
chatUserConn?.communicationUserId,
chatUserConn?.displayName,
chatUserConn?.endpointUrl,
chatUserConn?.threadId,
chatUserConn?.token,
]);
// createChatAdapter imported from another file
export const createChatAdapter = async (
endpointUrl: string,
communicationUserId: string,
displayName: string,
token: string,
threadId: string
) => {
try {
return await createAzureCommunicationChatAdapter({
endpoint: endpointUrl,
userId: fromFlatCommunicationIdentifier(communicationUserId) as CommunicationUserIdentifier,
displayName: displayName,
credential: createAutoRefreshingCredential(communicationUserId, token),
threadId: threadId,
});
} catch (error) {
...
return undefined;
}
};
export const createAutoRefreshingCredential = (userId: string, token: string) => {
const options: CommunicationTokenRefreshOptions = {
token: token,
tokenRefresher: refreshTokenAsync(userId),
refreshProactively: true,
};
return new AzureCommunicationTokenCredential(options);
};
And I have another one to add the listeners.
// Listeners
useEffect(() => {
const messageReceivedHandler: MessageReceivedListener = (listener) => {
...
};
const messageSentHandler: MessageReceivedListener = (listener) => {
...
};
const participantsRemovedHandler: ParticipantsRemovedListener = (listener) => {
...
};
const endChatOnError = () => {
chatAdapter?.dispose();
};
const errorHandler = (e: ChatAdapterError) => {
if (e.innerError.statusCode && errorCodes.includes(e.innerError.statusCode)) {
...
}
};
const windowErrorHandler = (e: PromiseRejectionEvent) => {
if (errorCodes.includes(e.reason.statusCode)) {
...
}
};
if (chatAdapter) {
chatAdapter.on('messageReceived', messageReceivedHandler);
chatAdapter.on('messageSent', messageSentHandler);
chatAdapter.on('participantsRemoved', participantsRemovedHandler);
chatAdapter.on('error', errorHandler);
window.addEventListener('unhandledrejection', windowErrorHandler);
}
return () => {
if (chatAdapter) {
chatAdapter.off('messageReceived', messageReceivedHandler);
chatAdapter.off('messageSent', messageSentHandler);
chatAdapter.off('participantsRemoved', participantsRemovedHandler);
chatAdapter.off('error', errorHandler);
window.removeEventListener('unhandledrejection', windowErrorHandler);
}
};
}, [chatAdapter]);
And you can see now that I added a 'unhandledrejection'
event listener to handle the ChatAdapter
errors. It is working fine.
@DercilioFontes glad you were able to find a workaround here! seems like there is a issue inside our chat adapter if we have separate errors sneaking through! Could I get a little more info to point our search on this one?
- When do these errors typically show up? Adapter creation? when using the composite to send and receive messages?
- Which version of
communication-react
are you using? Thanks again for raising the issue for us!
The version is 1.3.1
The errors happen when the thread is ended in the Communication Service, and one is typing and sending messages. The requests to the Service are returning 404 (Not Found, because the thread is not live anymore), and the error is not coming to the ChatAdapter error listener.
Excellent. Thank you for the information! will update when we know more and when a fix is coming!
Hi @DercilioFontes seems the issue was actually in our chat-stateful-client. we have a fix up for review and will have a release with it sometime soon!
@dmceachernmsft, great! Thanks
Hi @DercilioFontes, the fix for this issue is in version 1.4.1-beta.1. Please try it out and let us know if the issue still exists.
Hi @mgamis-msft,
I finally tested it using "@azure/communication-react": "^1.4.1" and it works great!
Thanks.
Hi @mgamis-msft and @dmceachernmsft ,
I see this issue happening again. It seems to be in the @azure/communication-react": "^1.5.0"
.
The error is not thrown to the chatAdapter.on('error', errorHandler).
I see this log, but it didn't reach the error handler. It is the same situation described in this issue, typing into a chat that ended in the Communication Service.
Uncaught (in promise) Cannot find threadId, please make sure thread state is still in Stateful ChatClient.
rejected @ AzureCommunicationChatAdapter.ts:2
Promise.then (async)
step @ AzureCommunicationChatAdapter.ts:2
(anonymous) @ AzureCommunicationChatAdapter.ts:2
__awaiter @ AzureCommunicationChatAdapter.ts:2
sendTypingIndicator @ AzureCommunicationChatAdapter.ts:149
onKeyDown @ SendBox.tsx:243
(anonymous) @ InputBoxComponent.tsx:83
callCallback @ react-dom.development.js:3945
invokeGuardedCallbackDev @ react-dom.development.js:3994
invokeGuardedCallback @ react-dom.development.js:4056
invokeGuardedCallbackAndCatchFirstError @ react-dom.development.js:4070
executeDispatch @ react-dom.development.js:8243
processDispatchQueueItemsInOrder @ react-dom.development.js:8275
processDispatchQueue @ react-dom.development.js:8288
dispatchEventsForPlugins @ react-dom.development.js:8299
(anonymous) @ react-dom.development.js:8508
batchedEventUpdates$1 @ react-dom.development.js:22396
batchedEventUpdates @ react-dom.development.js:3745
dispatchEventForPluginEventSystem @ react-dom.development.js:8507
attemptToDispatchEvent @ react-dom.development.js:6005
dispatchEvent @ react-dom.development.js:5924
unstable_runWithPriority @ scheduler.development.js:468
runWithPriority$1 @ react-dom.development.js:11276
discreteUpdates$1 @ react-dom.development.js:22413
discreteUpdates @ react-dom.development.js:3756
dispatchDiscreteEvent @ react-dom.development.js:5889
stack:
"@azure/communication-chat": "^1.2.0",
"@azure/communication-common": "^2.2.0",
"@azure/communication-react": "^1.5.0",