iva-studio-workspace/IVA-Wakeup/proxy/CAInterface_Chat.js

421 lines
17 KiB
JavaScript

console.info("===== Live Chat Proxy Entry - v16.3 =====");
const casettings = CAInterface_settings;
const CA = CAInterface();
const msgrSettings = eval(`settings${casettings.hubSuffixMessenger}`);
let nlu = msgrSettings?.nlu;
const _SELF = `${nlu.apiBaseURL}ProxyScript/run/${req.params.workspaceId}/${req.params.branch}/CAInterface_chat${casettings.hubSuffix}`;
// API Specific
const headers = {
Authorization: `Basic ${buffer.Buffer.from(casettings.token.basicAuth).toString("base64")}`,
"Accept-Language": "en-US,en;q=0.5",
};
const studioToken = msgrSettings ? { Authorization: `Bearer ${msgrSettings.messenger.token}` } : {};
console.info({
"Express Request": {
body: req.body,
},
});
const agentAltId = "G3YxOTO1LO_1NlJXEIfk";
// gather fields from request body
const { conversationId, event, input, location, logged, options, queryParams, request, transcript } = req.body || {};
const channel = req?.body?.event?.metadata?.channel ?? req?.body?.channel ?? "web";
const metadata = req.body.event?.metadata;
const connectedWithAgent = (session) => session?.liveChatRequest && session?.agent?.userId;
const loopScale = 3;
const smStr = `session-map-${conversationId}`;
(async () => {
const session = await redis.hGetAll(smStr);
// parse session from string to object
Object.keys(session).forEach((key) => {
try {
session[key] = JSON.parse(session[key]);
} catch (e) {}
});
const elapsed = (Date.now() - session?.lastActivity) / 1000;
const qelapsed = (Date.now() - session?.lastQueueStatus) / 1000;
console.info(`Time since last user activity: ${elapsed}s`);
session.warned ||= false;
if (!session?.discoveryUrls) {
const { urls } = await CA.restUrls();
session.discoveryUrls = urls;
await redis.hSet(smStr, {
discoveryUrls: JSON.stringify(urls),
});
}
if (event != "FETCH") {
console.info({ Session: session });
}
const delayCallFetch = async (delay) => {
const id = uuidv4();
await redis.hSet(smStr, { fetchid: id });
let numSeconds = typeof delay === "number" ? delay : loopScale;
setTimeout(async (smStr, id) => {
const session = await redis.hGetAll(smStr);
if (session.fetchid == id) {
axios({
method: "post",
url: _SELF,
headers: { "Content-Type": "application/json" },
data: { channel, conversationId, event: "FETCH" },
});
} else {
console.info("Fetch: loop stopped (old id)");
}
}, 1000 * numSeconds, smStr, id);
};
switch (event) {
case "JOIN": {
console.info("Joining live chat session");
const chatRequestDefaults = {
customerFirstName: "",
customerLastName: "",
customerEmail: "",
chatLaunchMode: "CHAT_ONLY",
refererURL: "example.com",
launchIdentifier: casettings.liveChat.launchIdentifier,
launchCode: "TextChatCustomerDataED",
transcript,
};
const customFieldsDefaults = { company: "Verint" };
const chatRequest = { ...chatRequestDefaults, ...(request ?? {}) };
chatRequest.customFields = { ...customFieldsDefaults, ...(request?.customFields ?? {}) };
const data = await CA.axios(`Posting data to "${session.discoveryUrls.client}"`, {
method: "post",
url: session.discoveryUrls.client,
headers,
data: chatRequest,
});
// Clean up _links
const links = {};
Object.keys(data._links).forEach((key) => {
links[key] = data._links[key].href;
});
data._links = links;
console.info(data);
await redis.hSet(smStr, {
liveChatRequest: "true",
chatDetails: JSON.stringify(data),
lastActivity: JSON.stringify(Date.now()),
lastQueueStatus: JSON.stringify(Date.now()),
});
const queueStatus = await CA.axios(`Getting queue status from "${data._links.queuedstatus}"`,
{ method: "get", url: data._links.queuedstatus, headers },
async () => {
await CA.postEvent(channel, conversationId, { input: casettings.liveChat.queueDisconnectMessage });
}
);
if (queueStatus.message) {
await CA.postEvent(channel, conversationId, { input: queueStatus.message });
}
delayCallFetch();
break;
}
case "LEAVE": {
console.info("Leaving live chat session");
try {
const { data } = await axios({
method: "post",
url: session.chatDetails._links.logout,
headers,
});
console.info({ "Logout Response": data });
if (connectedWithAgent(session)) {
await CA.postConversationLeave(
channel,
conversationId,
session.agent.userId || casettings.liveChat.participantId || agentAltId,
"liveChatEnded"
);
}
} catch (e) {
console.error(e);
}
console.info("reset session map");
await redis.hSet(smStr, { liveChatRequest: "false", agent: "{}" });
break;
}
case "FETCH": {
const State = { waiting: 0, leaving: 1, offline: 2 };
let state = State.waiting;
if (session.liveChatRequest == false) {
console.info("Live Chat is off");
state = State.offline;
} else if (session && session.chatDetails) {
// queue status check
if (!connectedWithAgent(session) && qelapsed > 30) {
const queueStatus = await CA.axios(
`Getting queue status from "${session.chatDetails._links.queuedstatus}"`,
{ method: "get", url: session.chatDetails._links.queuedstatus, headers },
async () => {
await CA.postEvent(channel, conversationId, { input: casettings.liveChat.queueDisconnectMessage });
}
);
if (queueStatus.message && queueStatus.message != session.lastQueueMessage) {
await CA.postEvent(channel, conversationId, { input: queueStatus.message });
await redis.hSet(smStr, {
lastQueueStatus: JSON.stringify(Date.now()),
lastQueueMessage: queueStatus.message
});
}
}
const data = await CA.axios(
`Getting Events from "${session.chatDetails._links.event}"`,
{ method: "get", url: session.chatDetails._links.event, headers }
);
data.events = data.events.map((event) => JSON.parse(event));
if (data.events.length > 0) console.info(data.events);
const sagent = session.agent || {};
const au_default = {
userId: casettings.liveChat.participantId || agentAltId,
avatar: casettings.liveChat.avatar,
type: "agent",
};
let agent_user = { ...au_default, ...sagent };
for (const event of data.events) {
const agentUser = { ...agent_user, name: event.userDisplayName };
if (event.userType === "GUEST") {
console.info(event);
switch (event.type) {
case "MessageReceived": {
console.info(`Guest said "${event.text}"`);
await CA.postEvent(channel, conversationId, {
conversationId,
input: event.text,
sentBy: agentUser,
ping: nanoid(),
sentAt: Date.now(),
});
CA.addTransaction(session.conversationId, event.text,
{ type: "agent", name: event.userDisplayName, userId: event.userId },
{ liveChat: true }
);
break;
}
}
} else if (event.userType === "AGENT") {
console.info(event);
switch (event.type) {
case "UserTyping":
await CA.postTyping(channel, conversationId);
break;
case "AttachmentMessageReceived": {
let url = `${casettings.liveChat.attachment.endpoint}/ProxyScript/run/${req.params.workspaceId}/${req.params.branch}/CA_attachments${casettings.hubSuffix}`;
url += `?url=${encodeURIComponent(event.url)}&sessionId=${event.sessionId}&fileName=${encodeURIComponent(event.fileName)}`;
const response = await CA.downloadFile(event.url, event.sessionId);
if (CA.channelIsExternal(channel) && response.headers['content-type']) {
const attachment = { url, mimeType: response.headers['content-type'], fileName: event.fileName };
CA.postEvent(channel, conversationId, { metadata: { channel, attachments: [attachment] } });
}
break;
}
case "MessageReceived": {
console.info(`Agent ${event.userDisplayName} said "${event.text}"`);
agent_user.name = event.userDisplayName;
let eventPayload = {
conversationId,
input: event.text,
sentBy: agentUser,
ping: nanoid(),
sentAt: Date.now(),
};
if (data.events.some((x) => x.type == "UserExited")) {
console.info("Setting liveChat to false");
eventPayload.metadata = { outputs: { liveChat: false } };
}
await gvf_handleIVAWakeupEvent().handleIVAWakeupEvent(
event, req, session, settings_66a1322d44405adda4fe9f53, redis, smStr, channel, eventPayload
);
if (session.formFlow !== true) {
await CA.postEvent(channel, conversationId, eventPayload);
}
CA.addTransaction(
session.conversationId,
event.text,
{ type: "agent", name: event.userDisplayName, userId: event.userID },
eventPayload?.metadata?.outputs || { liveChat: true }
);
break;
}
case "UserEntered":
console.info(`Agent ${event.userDisplayName} joined`);
agent_user.name = event.userDisplayName;
await redis.hSet(smStr, { agent: JSON.stringify(agent_user) });
await CA.postConversationJoin(channel, conversationId, agent_user);
await CA.postEvent(channel, conversationId, {
conversationId,
input: `You are now connected with ${event.userDisplayName}`,
sentBy: { ...agentUser, type: "host" },
ping: nanoid(),
sentAt: Date.now(),
});
break;
case "UserExited":
state = State.leaving;
console.info(`Agent ${event.userDisplayName} left`);
break;
}
}
}
} else {
console.info("Session does not exist!");
break;
}
// State handling
switch (state) {
case State.waiting: {
const idle = casettings.liveChat.idle;
if (elapsed > idle.disconnect.seconds && session.warned) {
CA.addTransaction(session.conversationId, "Live Chat Timeout", { type: "host" }, { liveChat: false });
await CA.postEvent(channel, conversationId, { input: idle.disconnect.message });
state = State.leaving;
await axios({
method: "post",
url: _SELF,
headers: studioToken,
data: { channel, conversationId, event: "LEAVE", session },
});
break;
} else if (elapsed > idle.warning.seconds && !session.warned) {
await CA.postEvent(channel, conversationId, { input: idle.warning.message });
await redis.hSet(smStr, { warned: "true" });
}
delayCallFetch();
break;
}
case State.leaving:
console.info("Leaving session");
await axios({
method: "post",
url: _SELF,
headers: studioToken,
data: { channel, conversationId, event: "LEAVE", session },
});
break;
default:
state = State.offline;
break;
}
break;
}
default: {
console.info("Live chat ongoing");
if (input && session && session.chatDetails) {
console.info(session.chatDetails);
console.info(`Sending "${input}" to live chat`);
await axios({
method: "post",
url: session.chatDetails._links.event,
headers,
data: [{ type: "MessageSent", text: input }],
});
await redis.hSet(smStr, {
lastActivity: JSON.stringify(Date.now()),
warned: "false",
lastQueueMessage: "",
lastQueueStatus: 0,
});
delayCallFetch();
}
}
}
})()
.catch(async (error) => {
console.error(`Caught error: ${error}`);
if (error.response) console.error(`Caught error: ${error.response.data}`);
const session = await redis.hGetAll(smStr);
if (session) {
await redis.hSet(smStr, { liveChatRequest: "false", agent: "{}" });
if (session && session.agent && session.chatDetails) {
session.agent = JSON.parse(session.agent);
session.chatDetails = JSON.parse(session.chatDetails);
if (connectedWithAgent(session)) {
CA.postConversationLeave(
channel,
conversationId,
session.agent.userId || casettings.liveChat.participantId || agentAltId,
"AgentExited"
);
}
axios({ method: "post", url: session.chatDetails._links.logout, headers });
}
}
if (conversationId && session.conversationId) {
CA.addTransaction(session.conversationId, "Live Chat Error", { type: "host" }, { liveChat: false });
await CA.postEvent(channel, conversationId, {
input: "There was a connection issue with the Live Chat, and you were disconnected.",
});
}
})
.finally(() => {
res.send(200);
});