diff --git a/package-lock.json b/package-lock.json index da4c9ca..0465c68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9565,7 +9565,7 @@ }, "packages/chat-core-zendesk": { "name": "@yext/chat-core-zendesk", - "version": "0.2.0", + "version": "0.3.0", "license": "BSD-3-Clause", "dependencies": { "smooch": "5.6.0" diff --git a/packages/chat-core-aws-connect/THIRD-PARTY-NOTICES b/packages/chat-core-aws-connect/THIRD-PARTY-NOTICES index f720fd8..e690c1b 100644 --- a/packages/chat-core-aws-connect/THIRD-PARTY-NOTICES +++ b/packages/chat-core-aws-connect/THIRD-PARTY-NOTICES @@ -194,7 +194,7 @@ The following NPM packages may be included in this product: - @types/istanbul-lib-report@3.0.3 - @types/istanbul-reports@3.0.4 - @types/jsdom@20.0.1 - - @types/node@22.9.1 + - @types/node@22.10.1 - @types/stack-utils@2.0.3 - @types/tough-cookie@4.0.5 - @types/yargs-parser@21.0.3 @@ -1826,7 +1826,7 @@ SOFTWARE. The following NPM package may be included in this product: - - nwsapi@2.2.13 + - nwsapi@2.2.16 This package contains the following license and notice below: @@ -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: @@ -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: diff --git a/packages/chat-core-zendesk/docs/chat-core-zendesk.chatcorezendeskconfig.externalid.md b/packages/chat-core-zendesk/docs/chat-core-zendesk.chatcorezendeskconfig.externalid.md new file mode 100644 index 0000000..434dbc9 --- /dev/null +++ b/packages/chat-core-zendesk/docs/chat-core-zendesk.chatcorezendeskconfig.externalid.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [@yext/chat-core-zendesk](./chat-core-zendesk.md) > [ChatCoreZendeskConfig](./chat-core-zendesk.chatcorezendeskconfig.md) > [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. + diff --git a/packages/chat-core-zendesk/docs/chat-core-zendesk.chatcorezendeskconfig.jwt.md b/packages/chat-core-zendesk/docs/chat-core-zendesk.chatcorezendeskconfig.jwt.md new file mode 100644 index 0000000..cd2a22d --- /dev/null +++ b/packages/chat-core-zendesk/docs/chat-core-zendesk.chatcorezendeskconfig.jwt.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [@yext/chat-core-zendesk](./chat-core-zendesk.md) > [ChatCoreZendeskConfig](./chat-core-zendesk.chatcorezendeskconfig.md) > [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. + diff --git a/packages/chat-core-zendesk/docs/chat-core-zendesk.chatcorezendeskconfig.md b/packages/chat-core-zendesk/docs/chat-core-zendesk.chatcorezendeskconfig.md index d48513b..421edca 100644 --- a/packages/chat-core-zendesk/docs/chat-core-zendesk.chatcorezendeskconfig.md +++ b/packages/chat-core-zendesk/docs/chat-core-zendesk.chatcorezendeskconfig.md @@ -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) | | () => string \| Promise<string> | _(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. | diff --git a/packages/chat-core-zendesk/docs/chat-core-zendesk.chatcorezendeskconfig.oninvalidauth.md b/packages/chat-core-zendesk/docs/chat-core-zendesk.chatcorezendeskconfig.oninvalidauth.md new file mode 100644 index 0000000..2acb61e --- /dev/null +++ b/packages/chat-core-zendesk/docs/chat-core-zendesk.chatcorezendeskconfig.oninvalidauth.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@yext/chat-core-zendesk](./chat-core-zendesk.md) > [ChatCoreZendeskConfig](./chat-core-zendesk.chatcorezendeskconfig.md) > [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; +``` diff --git a/packages/chat-core-zendesk/etc/chat-core-zendesk.api.md b/packages/chat-core-zendesk/etc/chat-core-zendesk.api.md index 128cae0..a0a6bf2 100644 --- a/packages/chat-core-zendesk/etc/chat-core-zendesk.api.md +++ b/packages/chat-core-zendesk/etc/chat-core-zendesk.api.md @@ -24,7 +24,10 @@ export interface ChatCoreZendesk { // @public export interface ChatCoreZendeskConfig { + externalId?: string; integrationId: string; + jwt?: string; + onInvalidAuth?: () => string | Promise; ticketTags?: string[]; } diff --git a/packages/chat-core-zendesk/package.json b/packages/chat-core-zendesk/package.json index b5edc26..799ec4f 100644 --- a/packages/chat-core-zendesk/package.json +++ b/packages/chat-core-zendesk/package.json @@ -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", diff --git a/packages/chat-core-zendesk/src/infra/ChatCoreZendeskImpl.ts b/packages/chat-core-zendesk/src/infra/ChatCoreZendeskImpl.ts index 8366341..2843bd3 100644 --- a/packages/chat-core-zendesk/src/infra/ChatCoreZendeskImpl.ts +++ b/packages/chat-core-zendesk/src/infra/ChatCoreZendeskImpl.ts @@ -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; constructor(config: ChatCoreZendeskConfig) { if (window === undefined) { @@ -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 } /** @@ -74,24 +80,35 @@ export class ChatCoreZendeskImpl implements ChatCoreZendesk { private async initializeZendeskSdk(): Promise { 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); + }); + }); } /** diff --git a/packages/chat-core-zendesk/src/models/ChatCoreZendeskConfig.ts b/packages/chat-core-zendesk/src/models/ChatCoreZendeskConfig.ts index 805d1ce..1cc9e36 100644 --- a/packages/chat-core-zendesk/src/models/ChatCoreZendeskConfig.ts +++ b/packages/chat-core-zendesk/src/models/ChatCoreZendeskConfig.ts @@ -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; } diff --git a/packages/chat-core-zendesk/tests/ChatCoreZendesk.test.ts b/packages/chat-core-zendesk/tests/ChatCoreZendesk.test.ts index 3c2c27d..cedc305 100644 --- a/packages/chat-core-zendesk/tests/ChatCoreZendesk.test.ts +++ b/packages/chat-core-zendesk/tests/ChatCoreZendesk.test.ts @@ -40,6 +40,9 @@ beforeEach(() => { jest .mocked(SmoochLib.createConversation) .mockResolvedValue({ id: mockConversationId } as Conversation); + jest + .mocked(SmoochLib.init) + .mockResolvedValue(Promise.resolve()); document.body.innerHTML = ""; }); diff --git a/test-sites/test-browser-esm/README.md b/test-sites/test-browser-esm/README.md index 40678f5..098550d 100644 --- a/test-sites/test-browser-esm/README.md +++ b/test-sites/test-browser-esm/README.md @@ -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. diff --git a/test-sites/test-browser-esm/package-lock.json b/test-sites/test-browser-esm/package-lock.json index d1c4440..63b2103 100644 --- a/test-sites/test-browser-esm/package-lock.json +++ b/test-sites/test-browser-esm/package-lock.json @@ -56,13 +56,13 @@ "cross-fetch": "^3.1.5" }, "devDependencies": { - "@babel/preset-env": "^7.21.5", + "@babel/preset-env": "^7.26.0", "@babel/preset-typescript": "^7.21.5", "@microsoft/api-documenter": "^7.22.4", "@microsoft/api-extractor": "^7.34.8", "@types/jest": "^29.5.1", "@types/node-fetch": "^2.6.4", - "@yext/eslint-config": "^1.0.0", + "@yext/eslint-config": "^1.0.2", "babel-jest": "^29.5.0", "eslint": "^8.39.0", "generate-license-file": "^1.0.0", @@ -82,14 +82,14 @@ "amazon-connect-chatjs": "^2.3.0" }, "devDependencies": { - "@babel/preset-env": "^7.21.5", + "@babel/preset-env": "^7.26.0", "@babel/preset-typescript": "^7.21.5", "@microsoft/api-documenter": "^7.22.4", "@microsoft/api-extractor": "^7.34.8", "@types/jest": "^29.5.1", "@types/node-fetch": "^2.6.4", "@yext/chat-core": "^0.9.1", - "@yext/eslint-config": "^1.0.0", + "@yext/eslint-config": "^1.0.2", "babel-jest": "^29.5.0", "dotenv": "^16.4.5", "eslint": "^8.39.0", @@ -107,20 +107,20 @@ }, "../../packages/chat-core-zendesk": { "name": "@yext/chat-core-zendesk", - "version": "0.1.3", + "version": "0.3.0", "license": "BSD-3-Clause", "dependencies": { "smooch": "5.6.0" }, "devDependencies": { - "@babel/preset-env": "^7.21.5", + "@babel/preset-env": "^7.26.0", "@babel/preset-typescript": "^7.21.5", "@microsoft/api-documenter": "^7.22.4", "@microsoft/api-extractor": "^7.34.8", "@types/jest": "^29.5.1", "@types/smooch": "^5.3.7", "@yext/chat-core": "^0.9.1", - "@yext/eslint-config": "^1.0.0", + "@yext/eslint-config": "^1.0.2", "babel-jest": "^29.5.0", "eslint": "^8.39.0", "generate-license-file": "^1.0.0", diff --git a/test-sites/test-browser-esm/rollup.config.js b/test-sites/test-browser-esm/rollup.config.js index 2d260d6..be79c83 100644 --- a/test-sites/test-browser-esm/rollup.config.js +++ b/test-sites/test-browser-esm/rollup.config.js @@ -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 diff --git a/test-sites/test-browser-esm/sample.env b/test-sites/test-browser-esm/sample.env index 051c638..fbfb8b9 100644 --- a/test-sites/test-browser-esm/sample.env +++ b/test-sites/test-browser-esm/sample.env @@ -1,3 +1,8 @@ TEST_BOT_ID= TEST_BOT_API_KEY= -TEST_ZENDESK_INTEGRATION_ID= \ No newline at end of file +TEST_ZENDESK_INTEGRATION_ID= + +# agent handoff with zendesk user authentication +TEST_EXTERNAL_ID= +TEST_JWT= +TEST_REFRESH_JWT= \ No newline at end of file diff --git a/test-sites/test-browser-esm/src/script.js b/test-sites/test-browser-esm/src/script.js index ba8a042..1ea1b4d 100644 --- a/test-sites/test-browser-esm/src/script.js +++ b/test-sites/test-browser-esm/src/script.js @@ -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: { @@ -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); }