Open vtrofin opened 11 months ago
Hey @vtrofin could you send HAR file or some network information to help us debug this one. I'll try to replicate this issue locally on codesandbox.
If you get rid of the the race does the API ever return?
My one concern with the implementation is that you are mounting the connect client inside a hook and setting to a useState variable and that it is not fully mounted when the API is called.
You may want to immediately invoke the client setup and store it in the window or something. Trying to mount it in a useEffect may run into some issues.
@vtrofin we worked on an example to help mitigate this here
For now, by using the onAgent
callback, you can re-initialize the CustomerProfilesClient on login which should prevent this issue.
I will add a feature request to the amazon-connect-streams library to re-initailize the Customer Profiles ifame on log out and log in.
@greysonevins thank you very much for your response and for taking the time to investigate this. I'll point a few of the implementation decisions below, but it's probably that the client is not properly initialized in my implementation. I'll re-check the client initialization in my codebase.
But the difference is that only after retrieving the Connect credentials I initialize the ccp client and the customer profile client, in the private route. The reason for this choice was that at the time I was unsure whether the ccp client can be properly initiated and refreshed without having the credentials first.
I'll provide a HAR file to the support team. I'm also wondering whether it's better to just instantiate the ccp client once for the entire app and let it manage the refresh once credentials are available.
The API never returns if i remove the race and I instantiate the customer profile like this
useEffect(() => {
connect.core.initCCP(iframeContainer as HTMLElement, initCCPOptions)
const customerProfileClient = new connect.CustomerProfilesClient(
customerProfileInstance,
)
}, [<dependencies>])
However, if I try using the onInitialized
event on the ccp client to instantiate the customer profile client, then I receive a meaningful error about the client not being ready when fetching the customer profile.
useEffect(() => {
connect.core.initCCP(iframeContainer as HTMLElement, initCCPOptions)
connect.core.onInitialized(() => {
// attempt to initiate the customer profile client
const customerProfileClient = new connect.CustomerProfilesClient(
customerProfileInstance,
)
})
}, [<dependencies>])
The error I receive when trying to fetch the customer profile is below
error.ts:20 Error: {"status":500,"statusText":"Client not ready","data":{}}
at toError (error.ts:73:12)
at logError (error.ts:19:23)
at storeCustomerProfile (hook.ts:212:9)
overrideMethod @ console.js:213
logError @ error.ts:20
storeCustomerProfile @ hook.ts:212
await in storeCustomerProfile (async)
(anonymous) @ hook.ts:222
commitHookEffectListMount @ react-dom.development.js:23150
commitPassiveMountOnFiber @ react-dom.development.js:24926
commitPassiveMountEffects_complete @ react-dom.development.js:24891
commitPassiveMountEffects_begin @ react-dom.development.js:24878
commitPassiveMountEffects @ react-dom.development.js:24866
flushPassiveEffectsImpl @ react-dom.development.js:27039
flushPassiveEffects @ react-dom.development.js:26984
(anonymous) @ react-dom.development.js:26769
workLoop @ scheduler.development.js:266
flushWork @ scheduler.development.js:239
performWorkUntilDeadline @ scheduler.development.js:533
Show 12 more frames
Show less
Again, thank you very much for investigating this issue. I'm looking at your example on how to mitigate the issue and will get back with a reply.
@greysonevins thank you for providing me with the example code.
For now, by using the onAgent callback, you can re-initialize the CustomerProfilesClient on login which should prevent this issue.
If I use your approach of searching customer profiles with a click event there is no problem, i get a proper response from the API even when I set the customer profile in state. But in my codebase i must fetch the customer profile with an useEffect hook, every time a new contact is pushed to the agent. This is the main reason i'm getting this error when i log-out and log-in again. And it only happens for that first contact, because the customer profile client is not yet ready.
Is there any way of knowing when the customer profile client is ready so I only attempt to query after that? I guess as an alternative I could make a loop and retry when I get this error.
Error: {"status":500,"statusText":"Client not ready","data":{}}
I'll provide a HAR file to the support team.
I have just shared this on the associated AWS support case on @vtrofin's behalf
In the end I've made my own check for the client to be ready and only make a query once it is ready. If it helps anyone, here's my approach with React:
Initialize the customer profile client only after the ccp iframe is initialized
useEffect(() => {
connect.core.initCCP(iframeContainer as HTMLElement, initCCPOptions)
connect.core.onInitialized(() => {
// Check whether client is ready and if not, initiate it
isClientReady()
.then((isReady) => {
if (!isReady) {
initiateCustomerProfileClient()
}
})
.catch((err) => {
})
})
}, [<dependencies>])
Set up the customer profiles client in Context. Below is the hook called in my Context Provider which retrieves the client and the helper methods to query the client
const useCustomerProfileClientHook = () => {
const customerProfileInstance = `https://<connect-instance-alias>.my.connect.aws`
const [client, setClient] = useState<unknown>(null)
const isClientReadyRef = useRef<boolean>(false)
const initiateClient = (): void => {
const customerProfileClient = new connect.CustomerProfilesClient(
customerProfileInstance,
)
setClient(() => customerProfileClient)
}
const sleep = (ms: number): Promise<void> =>
new Promise((resolve) => setTimeout(resolve, ms))
const throwErrorOnDelay = (delay: number) =>
new Promise((_, reject) => {
setTimeout(reject, delay, "delayed-api-response")
})
const isClientReady = async (): Promise<boolean> => {
if (client === null) {
return false
}
if (isClientReadyRef.current) {
return true
}
try {
const queryOptions = {
DomainName: <customer-profiles-domain>,
KeyName: "_phone",
Values: ["1111"],
}
const response = await Promise.race([
client.searchProfiles(queryOptions),
throwErrorOnDelay(5 * 1000),
])
if (response?.status === 200) {
isClientReadyRef.current = true
return true
}
return false
} catch (err) {
return false
}
}
const searchProfiles = async (options: OptionsType): Promise<unknown> => {
let i = 0
const maxI = 4
while (i < maxI) {
if (!(await isClientReady())) {
i++
if (i === maxI) {
break
}
await sleep(i * 1000)
continue
}
const profiles = await client.searchProfiles(options)
return profiles
}
throw new Error("client-not-ready")
}
return {
client,
initiateClient,
isClientReady,
searchProfiles,
}
}
Query the client profile with useEffect on page render
useEffect(() => {
const getCustomerProfile = async () => {
const queryOptions = {
DomainName: config.customerProfilesDomain,
KeyName: "_phone",
Values: phoneNumber ? [phoneNumber] : undefined,
}
const profiles = await searchProfiles(queryOptions)
}
getCustomerProfile()
}, [<dependencies>])
I guess this issue can be closed if there is nothing else to track on the AWS side. I'm wondering whether it would be better to have some sort async initialization for the CustomerProfilesClient class to avoid the edge case when the client is not ready. Thank you for your support!
Will cut a feature request for a better mechanism to notify when client is ready.
I believe the new client initialization should help.
After that, checking if the iframe exists before calling the API has helped with our integration tests too if that helps.
Something like:
const [ready, setReady] = useState(false);
const intervalRef = useRef();
useEffect(() => {
intervalRef.current = setInterval(() => {
if (
document.querySelector(
"iframe[id=AmazonConnectCustomerProfilesClient]",
) !== null
) {
setReady(true);
}
}, [200]);
return () => clearInterval(intervalRef.current);
}, [setReady]);
useEffect(() => {
if (ready && intervalRef.current) {
clearInterval(intervalRef.current);
}
}, [ready]);
Thank you very much @greysonevins for the help and for the code suggestions!
Hi team,
I'm facing an interesting edge case while trying to retrieve Amazon Connect customer profiles in a React App. This only happens if I log-out and then log back in into my app. Worth mentioning that we're using Cognito as IDP service for Amazon Connect as described in the video call escalation repo.
We're logging out as instructed in the amazon connect docs.
It looks like there is an fac error around the time of my request to the customer profile API but the connect streams apparently treats this error as a normal occurrence.
This is how I instantiate the customer profile client:
This is the hook that I run to fetch the customer profile:
I've included a screencast of the exact edge case i'm facing. You can consult it here https://www.loom.com/share/0d236d17144e47e5b1cbb54824bac15f?sid=c58f4c50-76d5-4448-a9d0-7280c399efd0
Below i'll paste the export of my console window. As you can see below the
Promise.race
ends with throwing an error 10 seconds after not receiving a response for the customer profile search request. Any ideas on how I could fix or mitigate this edge case? As mentioned, I only noticed it after logging out from Amazon connect and then logging back into Amazon Connect.