421 lines
17 KiB
JavaScript
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);
|
|
});
|