Skip to content

Commit

Permalink
chat-core-zendesk: support jwt token for zendesk auth (#57)
Browse files Browse the repository at this point in the history
Update init function to accept externalId and jwt token for user authentication. This allows the conversation's requester to assign to a valid zendesk user instead of a generic web user. Additionally, added support to handle refreshing jwt token via `onInvalidAuth` function.

Also caught a bug in init function, which returns a Thenable object, instead of an actual Promise, meaning the async/await syntax wouldn't actually catch any errors. Updated to use then/catch function from the returned Thenable object.

J=CLIP-1622
TEST=manual

created JWT tokens using test key/secret from prod zendesk and uses my zendesk user's externalId. Verified that I can start zendesk messaging with an agent and the ticket requester is assigned to the expected zendesk user instead of a random generated web user ID. verified that JWT token refresh handling also work.

Also published an alpha version (0.2.0-alpha.2) to use in alpha, spin up storm, and verified I can start a conversation through advisor with the proper requester field assigned.
  • Loading branch information
yen-tt authored Dec 5, 2024
1 parent dc8d0ce commit b3dab3d
Show file tree
Hide file tree
Showing 16 changed files with 142 additions and 39 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions packages/chat-core-aws-connect/THIRD-PARTY-NOTICES
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ The following NPM packages may be included in this product:
- @types/[email protected]
- @types/[email protected]
- @types/[email protected]
- @types/node@22.9.1
- @types/node@22.10.1
- @types/[email protected]
- @types/[email protected]
- @types/[email protected]
Expand Down Expand Up @@ -1826,7 +1826,7 @@ SOFTWARE.

The following NPM package may be included in this product:

- [email protected].13
- [email protected].16

This package contains the following license and notice below:

Expand Down Expand Up @@ -1939,7 +1939,7 @@ THE SOFTWARE.

The following NPM package may be included in this product:

- psl@1.12.0
- psl@1.15.0

This package contains the following license and notice below:

Expand Down Expand Up @@ -2629,7 +2629,7 @@ THE SOFTWARE.

The following NPM package may be included in this product:

- undici-types@6.19.8
- undici-types@6.20.0

This package contains the following license and notice below:

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@yext/chat-core-zendesk](./chat-core-zendesk.md) &gt; [ChatCoreZendeskConfig](./chat-core-zendesk.chatcorezendeskconfig.md) &gt; [externalId](./chat-core-zendesk.chatcorezendeskconfig.externalid.md)

## ChatCoreZendeskConfig.externalId property

The external ID to associate with the user in Zendesk.

**Signature:**

```typescript
externalId?: string;
```

## Remarks

Should be provided along with the [ChatCoreZendeskConfig.jwt](./chat-core-zendesk.chatcorezendeskconfig.jwt.md) token to authenticate the user.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@yext/chat-core-zendesk](./chat-core-zendesk.md) &gt; [ChatCoreZendeskConfig](./chat-core-zendesk.chatcorezendeskconfig.md) &gt; [jwt](./chat-core-zendesk.chatcorezendeskconfig.jwt.md)

## ChatCoreZendeskConfig.jwt property

The JWT token to authenticate the user with Zendesk.

**Signature:**

```typescript
jwt?: string;
```

## Remarks

Should be provided along with the [ChatCoreZendeskConfig.externalId](./chat-core-zendesk.chatcorezendeskconfig.externalid.md) to authenticate the user.

Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export interface ChatCoreZendeskConfig

| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [externalId?](./chat-core-zendesk.chatcorezendeskconfig.externalid.md) | | string | _(Optional)_ The external ID to associate with the user in Zendesk. |
| [integrationId](./chat-core-zendesk.chatcorezendeskconfig.integrationid.md) | | string | The web widget integration ID for the Zendesk chat. |
| [jwt?](./chat-core-zendesk.chatcorezendeskconfig.jwt.md) | | string | _(Optional)_ The JWT token to authenticate the user with Zendesk. |
| [onInvalidAuth?](./chat-core-zendesk.chatcorezendeskconfig.oninvalidauth.md) | | () =&gt; string \| Promise&lt;string&gt; | _(Optional)_ Callback to be invoked when the authentication token is invalid. The returned string will be used as the new auth token when retrying the request. |
| [ticketTags?](./chat-core-zendesk.chatcorezendeskconfig.tickettags.md) | | string\[\] | _(Optional)_ Tags to apply when handoff to Zendesk is initiated. |

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@yext/chat-core-zendesk](./chat-core-zendesk.md) &gt; [ChatCoreZendeskConfig](./chat-core-zendesk.chatcorezendeskconfig.md) &gt; [onInvalidAuth](./chat-core-zendesk.chatcorezendeskconfig.oninvalidauth.md)

## ChatCoreZendeskConfig.onInvalidAuth property

Callback to be invoked when the authentication token is invalid. The returned string will be used as the new auth token when retrying the request.

**Signature:**

```typescript
onInvalidAuth?: () => string | Promise<string>;
```
3 changes: 3 additions & 0 deletions packages/chat-core-zendesk/etc/chat-core-zendesk.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ export interface ChatCoreZendesk {

// @public
export interface ChatCoreZendeskConfig {
externalId?: string;
integrationId: string;
jwt?: string;
onInvalidAuth?: () => string | Promise<string>;
ticketTags?: string[];
}

Expand Down
2 changes: 1 addition & 1 deletion packages/chat-core-zendesk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@yext/chat-core-zendesk",
"version": "0.2.0",
"version": "0.3.0",
"description": "Typescript Networking Library for the Yext Chat API Integration with Zendesk",
"main": "./dist/commonjs/index.js",
"module": "./dist/esm/index.mjs",
Expand Down
51 changes: 34 additions & 17 deletions packages/chat-core-zendesk/src/infra/ChatCoreZendeskImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ export class ChatCoreZendeskImpl implements ChatCoreZendesk {
private conversationId: string | undefined;
private integrationId: string;
private tags: string[] = ["yext-chat-agent-handoff"];
private jwt?: string;
private externalId?: string;
private onInvalidAuth?: () => string | Promise<string>;

constructor(config: ChatCoreZendeskConfig) {
if (window === undefined) {
Expand All @@ -58,6 +61,9 @@ export class ChatCoreZendeskImpl implements ChatCoreZendesk {
this.integrationId = config.integrationId;
this.tags = [...this.tags, ...(config.ticketTags ?? [])];
this.tags = [...new Set(this.tags)];
this.jwt = config.jwt;
this.externalId = config.externalId;
this.onInvalidAuth = config.onInvalidAuth
}

/**
Expand All @@ -74,24 +80,35 @@ export class ChatCoreZendeskImpl implements ChatCoreZendesk {

private async initializeZendeskSdk(): Promise<void> {
const divId = "yext-chat-core-zendesk-container";
if (!window.document.getElementById(divId)) {
const div = window.document.createElement("div");
window.document.body.appendChild(div);
div.id = divId;
div.style.display = "none";
Smooch.render(div);
try {
await Smooch.init({
integrationId: this.integrationId,
embedded: true,
soundNotificationEnabled: false,
});
} catch (e) {
console.error("Zendesk SDK init error", e);
throw e;
}
this.setupEventListeners();
if (window.document.getElementById(divId)) {
return;
}
const div = window.document.createElement("div");
window.document.body.appendChild(div);
div.id = divId;
div.style.display = "none";
Smooch.render(div);

// Smooch.init() returns a Thenable object, not an actual Promise.
// So we can't use await syntax. Instead, we use try/catch to handle errors.
return new Promise((resolve, reject) => {
Smooch.init({
integrationId: this.integrationId,
embedded: true,
soundNotificationEnabled: false,
...(this.jwt && { jwt: this.jwt }),
...(this.externalId && { externalId: this.externalId }),
delegate: {
onInvalidAuth: this.onInvalidAuth,
},
}).then(() => {
this.setupEventListeners();
resolve();
}).catch((e) => {
console.error("Zendesk SDK init error", e);
reject(e);
});
});
}

/**
Expand Down
19 changes: 19 additions & 0 deletions packages/chat-core-zendesk/src/models/ChatCoreZendeskConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,23 @@ export interface ChatCoreZendeskConfig {
* Tags to apply when handoff to Zendesk is initiated.
*/
ticketTags?: string[];
/**
* The JWT token to authenticate the user with Zendesk.
*
* @remarks
* Should be provided along with the {@link ChatCoreZendeskConfig.externalId} to authenticate the user.
*/
jwt?: string;
/**
* The external ID to associate with the user in Zendesk.
*
* @remarks
* Should be provided along with the {@link ChatCoreZendeskConfig.jwt} token to authenticate the user.
*/
externalId?: string;
/**
* Callback to be invoked when the authentication token is invalid.
* The returned string will be used as the new auth token when retrying the request.
*/
onInvalidAuth?: () => string | Promise<string>;
}
3 changes: 3 additions & 0 deletions packages/chat-core-zendesk/tests/ChatCoreZendesk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ beforeEach(() => {
jest
.mocked(SmoochLib.createConversation)
.mockResolvedValue({ id: mockConversationId } as Conversation);
jest
.mocked(SmoochLib.init)
.mockResolvedValue(Promise.resolve());
document.body.innerHTML = "";
});

Expand Down
2 changes: 2 additions & 0 deletions test-sites/test-browser-esm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ This will serve the index.html page on http://localhost:5050. Opening that up sh
#### Zendesk

To test Zendesk integration, ensures that the bot is configured with Zendesk handoff goal and provided appropriate credentials. In the `.env` file, provide the value for `TEST_ZENDESK_INTEGRATION_ID`. The test site should switch to use ChatCoreZendesk instance if it detects a zendesk-specific handoff step.

For agent handoff with zendesk user assigned as requester, authentication is needed via providing JWT token and externalId to zendesk core. In the `.env` field, provide the value for `TEST_EXTERNAL_ID` and `TEST_JWT`. To test jwt refresh logic, provide `TEST_REFRESH_JWT`, which should be used when the initial jwt token expires.
14 changes: 7 additions & 7 deletions test-sites/test-browser-esm/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 1 addition & 7 deletions test-sites/test-browser-esm/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,7 @@ export default {
plugins: [
replace({
//inject env values
"process.env.TEST_BOT_API_KEY": JSON.stringify(
process.env.TEST_BOT_API_KEY
),
"process.env.TEST_BOT_ID": JSON.stringify(process.env.TEST_BOT_ID),
"process.env.TEST_ZENDESK_INTEGRATION_ID": JSON.stringify(
process.env.TEST_ZENDESK_INTEGRATION_ID
),
"REPLACE_ME_WITH_ENV_VALUES": JSON.stringify(process.env),
}),
resolve({
//resolve paths
Expand Down
7 changes: 6 additions & 1 deletion test-sites/test-browser-esm/sample.env
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
TEST_BOT_ID=
TEST_BOT_API_KEY=
TEST_ZENDESK_INTEGRATION_ID=
TEST_ZENDESK_INTEGRATION_ID=

# agent handoff with zendesk user authentication
TEST_EXTERNAL_ID=
TEST_JWT=
TEST_REFRESH_JWT=
10 changes: 9 additions & 1 deletion test-sites/test-browser-esm/src/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { StreamEventName, provideChatCore } from "@yext/chat-core";
import { provideChatCoreAwsConnect } from "@yext/chat-core-aws-connect";
import { provideChatCoreZendesk } from "@yext/chat-core-zendesk";

// will be replace with actual env value during rollup build process
const process = { env: REPLACE_ME_WITH_ENV_VALUES };

let chatCore = provideChatCore({
// will be replace with actual env value during rollup build process
apiKey: process.env.TEST_BOT_API_KEY || "API_KEY_PLACEHOLDER",
botId: process.env.TEST_BOT_ID,
endpoints: {
Expand Down Expand Up @@ -64,6 +66,12 @@ window.getNextMessage = async () => {
agentCore = provideChatCoreZendesk({
integrationId: process.env.TEST_ZENDESK_INTEGRATION_ID,
ticketTags: ["testingTag"],
jwt: process.env.TEST_JWT,
externalId: process.env.TEST_EXTERNAL_ID,
onInvalidAuth: () => {
console.log("onInvalidAuth: return TEST_REFRESH_JWT");
return process.env.TEST_REFRESH_JWT;
}
});
handleHandoff(data);
}
Expand Down

0 comments on commit b3dab3d

Please sign in to comment.