Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Caller id match #156

Merged
merged 7 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@ const options = {
},
onUpdateEngagementFailed: event => {
/* HubSpot has failed to update an engagement for this call. */
},
onCallerIdMatchSucceeded: event => {
/* HubSpot has fetched caller id matches for this call. */
},
onCallerIdMatchFailed: event => {
/* HubSpot has failed to fetch caller id matches for this call. */
}
onVisibilityChanged: event => {
/* Call widget's visibility is changed. */
Expand Down Expand Up @@ -408,6 +414,58 @@ onDialNumber(data) {
</p>
</details>

<details>
<summary>onCallerIdMatchSucceeded</summary>
<p>

```js
// Message indicating that HubSpot has updated an engagement
onCallerIdMatchSucceeded(data) {
const {
callerIdMatches: (ContactIdMatch | CompanyIdMatch)[];
} = data;
...
}

type ObjectCoordinate = {
portalId: number;
objectTypeId: string;
objectId: number;
}

type ContactIdMatch = {
callerIdType: 'CONTACT';
objectCoordinates: ObjectCoordinate;
firstName: string;
lastName: string;
email: string;
}

type CompanyIdMatch = {
callerIdType: 'COMPANY';
objectCoordinates: ObjectCoordinate;
name: string;
}
```
</p>
</details>

<details>
<summary>onCallerIdMatchFailed</summary>
<p>

```js
// Message indicating that HubSpot has failed to update an engagement
onCallerIdMatchFailed(data) {
const {
error: { message: string }
} = data;
...
}
```
</p>
</details>

<details>
<summary>onVisibilityChanged</summary>
<p>
Expand Down
44 changes: 36 additions & 8 deletions demos/demo-minimal-js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import { errorType, callEndStatus } from "../../src/Constants";
// import CallingExtensions, { Constants } from "@hubspot/calling-extensions-sdk";
// const { errorType, callEndStatus } = Constants;

const state = {
export const state = {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expose state to the window object so that we can change any of the properties such as fromNumber before sending an event in the Demo Widget. For our acceptance tests, we should create an engagement state and call state to display in the UI like how we are doing so in the Mock UI.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would export it with a more definitive name. state is too generic. Thoughts?

Copy link
Contributor Author

@esme esme Nov 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, do you have any suggestions for the name - maybe engagementState? This is only exported in the demo-minimal-js iframe so we won't be seeing a conflict with any variables in the parent window

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also implement getters and setters here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the scope is minimal, i am fine. But, are we making updates to state properties? If yes, then it would be better if we have function to return initial state. So, we dont have accidental leaks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it's a minimal scope right now. Since we're not persisting the state across mounts, any changes to state properties would only affect the demo minimal js widget until we unmount the component.

engagementId: 0,
phoneNumber: "+1234",
toNumber: "+1234",
fromNumber: "+123456",
userAvailable: false,
incomingContactName: "",
};

const sizeInfo = {
Expand Down Expand Up @@ -53,7 +55,7 @@ const cti = new CallingExtensions({
},
onDialNumber: (data, rawEvent) => {
const { phoneNumber } = data;
state.phoneNumber = phoneNumber;
state.toNumber = phoneNumber;
},
onEngagementCreated: (data, rawEvent) => {
const { engagementId } = data;
Expand All @@ -75,6 +77,32 @@ const cti = new CallingExtensions({
state.engagementId = engagementId;
},
onUpdateEngagementFailed: (data, rawEvent) => {},
onCallerIdMatchSucceeded: (data, rawEvent) => {
const { callerIdMatches } = data;
if (callerIdMatches.length) {
const firstCallerIdMatch = callerIdMatches[0];
if (firstCallerIdMatch.callerIdType === "CONTACT") {
state.incomingContactName = `${firstCallerIdMatch.firstName} ${firstCallerIdMatch.lastName}`;
} else if (firstCallerIdMatch.callerIdType === "COMPANY") {
state.incomingContactName = firstCallerIdMatch.name;
}
cti.logDebugMessage({
message: `Incoming call from ${state.incomingContactName} ${state.fromNumber}`,
type: `${callerIdMatches.length} Caller ID Matches`,
});
return;
}
cti.logDebugMessage({
message: `Incoming call from ${state.fromNumber}`,
type: "No Caller ID Matches",
});
},
onCallerIdMatchFailed: (data, rawEvent) => {
cti.logDebugMessage({
message: `Incoming call from ${state.fromNumber}`,
type: "Caller ID Match Failed",
});
Comment on lines +89 to +104
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hemang-thakkar Added an argument to log messages with a custom type that would be more useful for debugging.

},
},
});

Expand Down Expand Up @@ -131,9 +159,9 @@ export function userUnavailable() {
export function incomingCall() {
window.setTimeout(() => {
cti.incomingCall({
createEngagement: "true",
fromNumber: "+123",
toNumber: state.phoneNumber,
createEngagement: true,
fromNumber: state.fromNumber,
toNumber: state.toNumber,
});
}, 500);
disableButtons([OUTGOING_CALL, INCOMING_CALL, USER_UNAVAILABLE]);
Expand All @@ -143,8 +171,8 @@ export function incomingCall() {
export function outgoingCall() {
window.setTimeout(() => {
cti.outgoingCall({
createEngagement: "true",
phoneNumber: state.phoneNumber,
createEngagement: true,
phoneNumber: state.toNumber,
});
}, 500);
disableButtons([OUTGOING_CALL, INCOMING_CALL, USER_UNAVAILABLE]);
Expand Down
24 changes: 20 additions & 4 deletions src/CallingExtensions.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"use es6";

import IFrameManager from "./IFrameManager";
import { messageType, errorType } from "./Constants";
import { messageType, debugMessageType, errorType, VERSION } from "./Constants";

const prefix = `[calling-extensions-sdk@${VERSION}]`;

/**
* @typedef {Object} EventHandlers
Expand Down Expand Up @@ -129,6 +131,10 @@ class CallingExtensions {
this.iFrameManager.sendMessage(message);
}

logDebugMessage({ message, type = debugMessageType }) {
this.iFrameManager.logDebugMessage(prefix, type, message);
}

onMessageHandler(event) {
const { type, data } = event;
const { eventHandlers } = this.options;
Expand Down Expand Up @@ -174,14 +180,24 @@ class CallingExtensions {
handler = onCreateEngagementFailed;
break;
}
case messageType.UPDATE_ENGAGEMENT_SUCCEEDED: {
const { onUpdateEngagementSucceeded } = eventHandlers;
handler = onUpdateEngagementSucceeded;
break;
}
case messageType.UPDATE_ENGAGEMENT_FAILED: {
const { onUpdateEngagementFailed } = eventHandlers;
handler = onUpdateEngagementFailed;
break;
}
case messageType.UPDATE_ENGAGEMENT_SUCCEEDED: {
const { onUpdateEngagementSucceeded } = eventHandlers;
handler = onUpdateEngagementSucceeded;
case messageType.CALLER_ID_MATCH_SUCCEEDED: {
const { onCallerIdMatchSucceeded } = eventHandlers;
handler = onCallerIdMatchSucceeded;
break;
}
case messageType.CALLER_ID_MATCH_FAILED: {
const { onCallerIdMatchFailed } = eventHandlers;
handler = onCallerIdMatchFailed;
break;
}
default: {
Expand Down
16 changes: 16 additions & 0 deletions src/Constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
const { version } = require("../package.json");
export const VERSION = version;

export const debugMessageType = {
FROM_HUBSPOT: "From HubSpot",
TO_HUBSPOT: "To HubSpot",
GENERIC_MESSAGE: "Generic Message",
};

export const messageType = {
CALL_ANSWERED: "CALL_ANSWERED",
CALL_COMPLETED: "CALL_COMPLETED",
Expand Down Expand Up @@ -34,6 +40,8 @@ export const messageType = {
USER_UNAVAILABLE: "USER_UNAVAILABLE",
UPDATE_ENGAGEMENT_FAILED: "UPDATE_ENGAGEMENT_FAILED",
UPDATE_ENGAGEMENT_SUCCEEDED: "UPDATE_ENGAGEMENT_SUCCEEDED",
CALLER_ID_MATCH_SUCCEEDED: "CALLER_ID_MATCH_SUCCEEDED",
CALLER_ID_MATCH_FAILED: "CALLER_ID_MATCH_FAILED",
VISIBILITY_CHANGED: "VISIBILITY_CHANGED",
};

Expand Down Expand Up @@ -89,3 +97,11 @@ export const callEndStatus = {
INTERNAL_BUSY,
INTERNAL_NO_ANSWER,
};

export const CONTACT = "CONTACT";
export const COMPANY = "COMPANY";

export const callerIdTypes = {
CONTACT,
COMPANY,
};
16 changes: 9 additions & 7 deletions src/IFrameManager.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use es6";

import { messageType, VERSION } from "./Constants";
import { messageType, debugMessageType, VERSION } from "./Constants";

const prefix = `[calling-extensions-sdk@${VERSION}]`;
/*
Expand Down Expand Up @@ -77,9 +77,7 @@ class IFrameManager {
}

static createIFrame(iFrameOptions, onLoadCallback) {
const {
src, width, height, hostElementSelector,
} = iFrameOptions;
const { src, width, height, hostElementSelector } = iFrameOptions;

if (!src || !width || !height || !hostElementSelector) {
throw new Error(
Expand Down Expand Up @@ -152,7 +150,11 @@ class IFrameManager {
const { type } = message;
if (type !== messageType.SYNC && !this.isReady) {
// Do not send a message unless the iFrame is ready to receive.
console.warn(prefix, "iFrame not initialized to send a message within HubSpot", message);
console.warn(
prefix,
"iFrame not initialized to send a message within HubSpot",
message,
);
return;
}

Expand All @@ -170,7 +172,7 @@ class IFrameManager {
messageId,
});

this.logDebugMessage(prefix, "To HubSpot", type, message);
this.logDebugMessage(prefix, debugMessageType.TO_HUBSPOT, type, message);
this.destinationWindow.postMessage(newMessage, this.destinationHost);
}

Expand Down Expand Up @@ -214,7 +216,7 @@ class IFrameManager {
return;
}

this.logDebugMessage(prefix, "From HubSpot", type, { data });
this.logDebugMessage(prefix, debugMessageType.FROM_HUBSPOT, type, data);
if (this.instanceRegexp.test(messageId)) {
// This is a response to some message generated by HubSpot
const callBack = this.callbacks[messageId];
Expand Down