const express = require("express")const axios = require("axios")var TLSSigAPIv2 = require("tls-sig-api-v2") // Generate UserSig for Tencent Cloud IMconst sf = require("node-salesforce") // Salesforce API Connection Library for Node.js Applicationsconst YOUR_SDKAPPID = 1400000000const YOUR_SECRET = ""const ADMIN_USERID = ""const app = express()app.use(express.json())const port = process.env.PORT || 3000app.use(express.json())app.use(function (req, res, next) {res.header("Access-Control-Allow-Origin", "https://YOUR_DOMAIN")res.header("Access-Control-Allow-Headers", "*")next()})// End user calls /createticket to create a Salesforce case and a Tencent Cloud IM chat group. Waiting for a Salesforce agent joining to the group.app.post("/createticket", async (req, res) => {const { userId, caseInfo } = req.bodyif (!userId) return res.status(500).send("Missing userId")const auth = await getSalesforceAccessToken()if (auth.error) return res.status(500).send("Salesforce auth error")const salesforceCase = await createCase(auth.token, caseInfo)if (!salesforceCase.success)return res.status(500).send("Case creation failed")const groupName = salesforceCase.idconst result = await createGroup(groupName, userId)if (result.ErrorCode !== 0)return res.status(500).send("Group creation failed")res.status(200).send(result)})// When detect a new agent assigned to the group, Salesforce sends a request to join this agent to the chat groupapp.post("/joingroup", async (req, res) => {const { groupId, userId } = req.bodyif (!userId) return res.status(500).send("Missing userId")if (!groupId) return res.status(500).send("Missing groupId")const result = await joinGroup(groupId, userId)if (result.ErrorCode !== 0)return res.status(500).send("Join group failed")res.status(200).send(result)})// When detect an agent removed from the group, Salesforce sends a request to remove this agent from the chat groupapp.post("/leavegroup", async (req, res) => {const { groupId, userId } = req.bodyif (!userId) return res.status(500).send("Missing userId")if (!groupId) return res.status(500).send("Missing groupId")const result = await leaveGroup(groupId, userId)if (result.ErrorCode !== 0)return res.status(500).send("Leave group failed")res.status(200).send(result)})app.post("/deletegroup", async (req, res) => {const { groupId } = req.bodyif (!groupId) return res.status(500).send("Missing groupId")const result = await deleteGroup(groupId)if (result.ErrorCode !== 0)return res.status(500).send("Delete group failed")res.status(200).send(result)})const getSalesforceAccessToken = async function () {const url = "https://{your_instance}.salesforce.com"const conn = new sf.Connection({ loginUrl: url })try {await conn.login("SF_EMAIL", "SF_PASSWORDSF_TOKEN")return { error: undefined, token: conn.accessToken }} catch (e) {return { error: e, token: undefined }}}const createCase = async function (token, caseInfo) {const { subject, desc, name, email } = caseInfoconst body = {Subject: subject,Description: desc,SuppliedName: name,SuppliedEmail: email,}const headers = {headers: {"Content-Type": "application/json",Authorization: "Bearer " + token,},}const url ="https://{your_instance}.salesforce.com/services/data/v{api_version}/sobjects/Case"try {const result = await axios.post(url, body, headers)return result.data} catch (e) {return { id: undefined, success: false, error: e }}}const generateUserSig = function () {const expires = 600const api = new TLSSigAPIv2.Api(YOUR_SDKAPPID, YOUR_SECRET)return api.genSig(ADMIN_USERID, expires)}const generateRandom = function () {return Math.floor(Math.random() * 4294967295)}const createGroup = async function (groupName, userId) {const sig = generateUserSig()const random = generateRandom()// Use salesforceCase.id as group IDconst data = { Owner_Account: userId, Type: "Public", Name: groupName, GroupId: groupName }const url = `https://console.tim.qq.com/v4/group_open_http_svc/create_group?sdkappid=${YOUR_SDKAPPID}&identifier=${ADMIN_USERID}&usersig=${sig}&random=${random}&contenttype=json`try {const groupRes = await axios.post(url, data)return groupRes.data} catch (e) {return { ErrorCode: -1, ErrorInfo: e }}}const joinGroup = async function (groupId, userId) {const sig = generateUserSig()const random = generateRandom()const data = { GroupId: groupId, MemberList: [{ Member_Account: userId }] }const url = `https://console.tim.qq.com/v4/group_open_http_svc/add_group_member?sdkappid=${YOUR_SDKAPPID}&identifier=${ADMIN_USERID}&usersig=${sig}&random=${random}&contenttype=json`try {const groupRes = await axios.post(url, data)return groupRes.data} catch (e) {return { ErrorCode: -1, ErrorInfo: e }}}const leaveGroup = async function (groupId, userId) {const sig = generateUserSig()const random = generateRandom()const data = { GroupId: groupId, MemberToDel_Account: [userId] }const url = `https://console.tim.qq.com/v4/group_open_http_svc/delete_group_member?sdkappid=${YOUR_SDKAPPID}&identifier=${ADMIN_USERID}&usersig=${sig}&random=${random}&contenttype=json`try {const groupRes = await axios.post(url, data)return groupRes.data} catch (e) {return { ErrorCode: -1, ErrorInfo: e }}}const deleteGroup = async function (groupId) {const sig = generateUserSig()const random = generateRandom()const data = { GroupId: groupId }const url = `https://console.tim.qq.com/v4/group_open_http_svc/destroy_group?sdkappid=${YOUR_SDKAPPID}&identifier=${ADMIN_USERID}&usersig=${sig}&random=${random}&contenttype=json`try {const groupRes = await axios.post(url, data)return groupRes.data} catch (e) {return { ErrorCode: -1, ErrorInfo: e }}}app.listen(process.env.PORT || port, () =>console.log(`Example app listening on port ${port}!`))
const getSalesforceAccessToken = async function () {const url = "https://{your_instance}.salesforce.com"const conn = new sf.Connection({ loginUrl: url })try {await conn.login("SF_EMAIL", "SF_PASSWORDSF_TOKEN")return { error: undefined, token: conn.accessToken }} catch (e) {return { error: e, token: undefined }}}
const createCase = async function (token, caseInfo) {const { subject, desc, name, email } = caseInfoconst body = {Subject: subject,Description: desc,SuppliedName: name,SuppliedEmail: email,}const headers = {headers: {"Content-Type": "application/json",Authorization: "Bearer " + token,},}const url ="https://{your_instance}.salesforce.com/services/data/v{api_version}/sobjects/Case"try {const result = await axios.post(url, body, headers)return result.data} catch (e) {return { id: undefined, success: false, error: e }}}
const generateUserSig = function () {const expires = 600const api = new TLSSigAPIv2.Api(YOUR_SDKAPPID, YOUR_SECRET)return api.genSig(ADMIN_USERID, expires)}
const createGroup = async function (groupName, userId) {const sig = generateUserSig()const random = generateRandom()const data = { Owner_Account: userId, Type: "Public", Name: groupName }const url = `https://console.tim.qq.com/v4/group_open_http_svc/create_group?sdkappid=${YOUR_SDKAPPID}&identifier=${ADMIN_USERID}&usersig=${sig}&random=${random}&contenttype=json`try {const groupRes = await axios.post(url, data)return groupRes.data} catch (e) {return { ErrorCode: -1, ErrorInfo: e }}}
const joinGroup = async function (groupId, userId) {const sig = generateUserSig()const random = generateRandom()const data = { GroupId: groupId, MemberList: [{ Member_Account: userId }] }const url = `https://console.tim.qq.com/v4/group_open_http_svc/add_group_member?sdkappid=${YOUR_SDKAPPID}&identifier=${ADMIN_USERID}&usersig=${sig}&random=${random}&contenttype=json`try {const groupRes = await axios.post(url, data)return groupRes.data} catch (e) {return { ErrorCode: -1, ErrorInfo: e }}}const leaveGroup = async function (groupId, userId) {const sig = generateUserSig()const random = generateRandom()const data = { GroupId: groupId, MemberToDel_Account: [userId] }const url = `https://console.tim.qq.com/v4/group_open_http_svc/delete_group_member?sdkappid=${YOUR_SDKAPPID}&identifier=${ADMIN_USERID}&usersig=${sig}&random=${random}&contenttype=json`try {const groupRes = await axios.post(url, data)return groupRes.data} catch (e) {return { ErrorCode: -1, ErrorInfo: e }}}const deleteGroup = async function (groupId) {const sig = generateUserSig()const random = generateRandom()const data = { GroupId: groupId }const url = `https://console.tim.qq.com/v4/group_open_http_svc/destroy_group?sdkappid=${YOUR_SDKAPPID}&identifier=${ADMIN_USERID}&usersig=${sig}&random=${random}&contenttype=json`try {const groupRes = await axios.post(url, data)return groupRes.data} catch (e) {return { ErrorCode: -1, ErrorInfo: e }}}
lightning:container
. And you can use Tencent Cloud IM Web UIKit to build an agent chat component, and deploy it in the Lightning Container as a Salesforce utilities bar widget at the bottom.<!-- tim_utilities_bar.cmp --><aura:component implements="flexipage:availableForAllPageTypes" access="global"><lightning:utilityBarAPI aura:id="utilitybar" /></aura:component>
<!-- tim_utilities_bar.cmp --><aura:component implements="flexipage:availableForAllPageTypes" access="global"><lightning:utilityBarAPI aura:id="utilitybar" /><aura:attribute name="recordId" type="String" /><aura:attribute name="data" type="String" /><lightning:navigation aura:id="navService" /><lightning:containeraura:id="TIM_Bar"src="{!$Resource.tim_bar + '/index.html'}"/></aura:component>
// tim_utilities_barController.js({handleMessage: function(component, message, helper) {var payload = message.getParams().payload// Once container is ready, initUIKitif (payload === "READY") helper.initUIKit(component, message, helper)}});({initUIKit: function (component, message, helper) {// Get Agent's IDvar userId = $A.get("$SObjectType.CurrentUser.Id")var message = { userId: userId }try {// Send the ID to the componentcomponent.find("TIM_Bar").message(message)} catch (err) {console.error("Error from Utilities Bar:", err)}},})
<!-- tim_utilities_bar.cmp --><lightning:containeraura:id="TIM_Bar"src="{!$Resource.tim_bar + '/index.html'}"onmessage="{!c.handleMessage}"/>
// index.jstry {const clientState = "READY";LLC.sendMessage(clientState);console.warn("Lightning Container --> TO SALESFORCE --> Sent:", clientState);} catch (e) {console.error("LLC NOT WORKING", e);}try {LLC.addErrorHandler((error) => console.log("LLC ERROR:", error));LLC.addMessageHandler((salesforceMessage) => {console.warn("SALESFORCE --> Lightning Container --> Arrived:", salesforceMessage);const app = createApp(App, {user: salesforceMessage});app.use(store).use(router).use(TUIKit).use(Aegis).use(ElementPlus).mount('#app');});} catch (e) {console.error("Error from LLC!!", e);}
// AssignAgent.apxttrigger AssignAgent on Case (after update, after delete) {if(trigger.isUpdate){// Case is updatingSystem.debug('Case Update Fired:');for(Case a : trigger.new){Case oldCase = trigger.oldMap.get(a.ID);if(String.valueOf(a.OwnerId).substring(0, 3) == '005'){// Owner Agent ID is changed, 005 prefix means agent IDSystem.debug('Agent invited :' + a.OwnerId);// Assign new owner to the Tencent Cloud IM groupString[] data = new String[2];data[0] = a.Id; // Case ID is group IDdata[1] = a.OwnerId;TimCallouts.joinGroup(data); //Custom callout class}// New case agent is different fromthe current agentif(String.valueOf(oldCase.OwnerId).substring(0, 3) == '005' && oldCase.OwnerId != a.OwnerId && String.valueOf(a.OwnerId).substring(0, 3) == '005'){// Delete the old agent from the group.// Note: A Case's very first owner will be the system owner.System.debug('Old Agent will be removed from group' + a.Id);System.debug('leaveGroup: ' + oldCase.OwnerId);String[] removeData = new String[2];removeData[0] = a.Id; // Case ID is group IDremoveData[1] = oldCase.OwnerId;TIMCallouts.leaveGroup(removeData);}}}if(trigger.isDelete ){// Case is deletingSystem.debug('Case Delete Fired:');for(Case a : trigger.old){if(String.valueOf(a.OwnerId).substring(0, 3) == '005'){System.debug('Delete Group :' + a.Id);String[] data = new String[1];data[0] = a.Id;TimCallouts.deleteGroup(data); //Custom callout class}}}}
// AgentOmniChanne.apxttrigger AgentOmniChannel on AgentWork (after update, after insert) {if(Trigger.isUpdate){for(AgentWork a : Trigger.new){AgentWork oldCase = Trigger.oldMap.get(a.ID);if(a.Status == 'Opened' && String.valueOf(a.OwnerId).substring(0, 3) == '005' ){String[] data = new String[2];data[0] = a.WorkItemId;data[1] = a.OwnerId;TIMCallouts.joinGroup(data);}}}}
// TIMCallOuts.apxcpublic class TIMCallouts {@future(callout=true)public static void joinGroup(String[] data) {String groupId = data[0];String userId = data[1];Http http = new Http();HttpRequest request = new HttpRequest();request.setEndpoint('https://{your_web_server}/joingroup');request.setMethod('POST');request.setHeader('Content-Type', 'application/json;charset=UTF-8');request.setBody('{"userId":"'+ userId +'", "groupId":"'+ groupId +'"}');HttpResponse response = http.send(request);// Parse the JSON responseif (response.getStatusCode() != 200) {System.debug('Join group failed: '+response.getStatusCode()+' '+response.getStatus());} else {System.debug('Tencent Cloud IM Response: ' + response.getBody());}}@future(callout=true)public static void leaveGroup(String[] data) {String groupId = data[0];String userId = data[1];Http http = new Http();HttpRequest request = new HttpRequest();request.setEndpoint('https://{your_web_server}/leavegroup');request.setMethod('POST');request.setHeader('Content-Type', 'application/json;charset=UTF-8');// Set the body as a JSON objectrequest.setBody('{"userId":"'+ userId +'", "groupId":"'+ groupId +'"}');HttpResponse response = http.send(request);// Parse the JSON responseif (response.getStatusCode() != 200) {System.debug('Leave group failed: ' + response.getStatusCode() + ' ' + response.getStatus());} else {System.debug('Tencent Cloud IM Response: ' + response.getBody());}}@future(callout=true)public static void deleteGroup(String data) {String groupId = data;Http http = new Http();HttpRequest request = new HttpRequest();request.setEndpoint('http://{your_web_server}/deletegroup');request.setMethod('POST');request.setHeader('Content-Type', 'application/json;charset=UTF-8');// Set the body as a JSON objectrequest.setBody('{"groupId":"'+ groupId + '}');HttpResponse response = http.send(request);// Parse the JSON responseif (response.getStatusCode() != 200) {System.debug('Delete group failed: ' + response.getStatusCode() + ' ' + response.getStatus());} else {System.debug('Tencent Cloud IM Response: ' + response.getBody());}}}
Was this page helpful?