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

FEAT_ Zendesk #1794

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
43 changes: 43 additions & 0 deletions apps/docs/editor/blocks/integrations/zendesk.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
title: Zendesk
---

## Zendesk Messaging

With the Zendesk Chat block, you can open a Zendesk Messaging live chat window to allow a user to chat with a Zendesk agent.

## How to find my Zendesk `Key ID` and `Secret Key`?

To configure your Web Widget or mobile SDK for visitor authentication, you first need a signing key. A signing key is a type of credential which is comprised of a key id (kid) and a shared secret.

You can view, create, and delete signing keys by clicking the Account icon in the Admin Center sidebar, and then selecting End user authentication under the Security heading ( you will need to be a Zendesk Admin). The shared secret will only be displayed in its entirety when the signing key is first created.

<Frame>
<img
src="/images/blocks/integrations/zendesk/zendesk-end-user-auth.png"
alt="End User Authentication"
/>
</Frame>

Learn more here: https://developer.zendesk.com/documentation/zendesk-web-widget-sdks/sdks/web/enabling_auth_visitors/#generating-a-signing-key

## Authenticate Messaging User

This action will generate a JWT token that can be passed as a variable to Open Web Widget you to authenticate a user in Zendesk. It requires the `User ID`, `Name` `Email`. Optionally, you can also set `Email Is Verified` if the email address has been verified. This is recommended to be set if appropriate.
Copy link
Owner

Choose a reason for hiding this comment

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

I found https://developer.zendesk.com/documentation/zendesk-web-widget-sdks/sdks/web/enabling_auth_visitors/#email-verification

Zendesk will only look for verified email addresses in a JWT. If the email_verified claim is not set to true, the email address in the JWT will be ignored, and it will not be included in the end user's profile in Agent Workspace.

So I guess the Email Is Verified field is useless here. We just need to tell the user to make sure the email was verified priori to providing it to Typebot. We could put that in a moreInfoTooltip for the email field

Does that make sense?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I originally thought the same, however, because open web widget is a client side action, that would lead to exposing the secrets used to create the JWT token on the client which would be highly insecure, no?

Email Verified is used in the JWT signing process. It's possible and valid to use verified and unverified emails when launching the web widget however as you may be dealing with new customers as well as existing, verified customers so we need to support verified and unverified emails. What verification does is that it tells the agent on the other end of the chat that the customer is known.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@baptisteArno what are your thoughts on this?

Copy link
Owner

Choose a reason for hiding this comment

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

I originally thought the same, however, because open web widget is a client side action, that would lead to exposing the secrets used to create the JWT token on the client which would be highly insecure, no?

You should be able for that action to define a server run function AND and a web function. The server function will first be executed.

It's possible and valid to use verified and unverified emails when launching the web widget

But Zendesk is saying that if we provide Email without the email_verified field set to true then it will completely ignore the Email. So my question is what is the point to provide the email_verified field on Typebot. Let's make it always true and document the fact that the creator of the bot verifies the user email before providing it to Zendesk block

Or am I missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You should be able for that action to define a server run function AND and a web function. The server function will first be executed.

Oh, I see so, would the run block for openWebWidget look like this then? How do I pass the token from the server side auth function to the client openWebWidget function as it doesn't appear that variables are available to the client functions?

run: { server: async ({ credentials: { conversationsSecretKey, conversationsKeyId }, options: { userId, name, email, isEmailVerified, tokenVariableId }, variables, }) => { if (!email || email.length === 0 || !userId || userId.length === 0 || !name || name.length === 0 || conversationsSecretKey === undefined || conversationsKeyId === undefined || tokenVariableId === undefined ) return variables.set(tokenVariableId, sign({ scope: 'user', external_id: userId, name: name, email: email, email_verified: "true" }, conversationsSecretKey, { algorithm: "HS256", keyid: conversationsKeyId })); }, web: { parseFunction: ({ options }) => { return { args: { isAuthEnabled: options.isAuthEnabled ? options.isAuthEnabled?.toString() : "false", token: options.token ?? null, key: options.key ?? null }, content: parseOpenMessenger() } } }, },

Copy link
Owner

Choose a reason for hiding this comment

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

In fact parseFunction is executed on the server so you are right it should have access to variables, credentials etc... And you won't even have to declare a server run function. Let me push an update, will let you know once you can merge main into this branch 🙏

Copy link
Owner

Choose a reason for hiding this comment

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

Done: f613ce3

Copy link
Contributor Author

Choose a reason for hiding this comment

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

NICE!! Thank you!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, have removed the auth action, updated the openWebWidget action to generate the token. Looks good!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

and email_verified is set to true now


Learn more here: https://developer.zendesk.com/documentation/zendesk-web-widget-sdks/sdks/web/enabling_auth_visitors/#email-verification.

## Open Web Widget

This action opens the Messenging Web Widget. It requires the Web Widget `Key` to be set. You can find the key by going to `Channels -> Messaging`, then click on the Web Widget you wish to configure. Scroll down to 'Installation' and expand that section. In the script code block, copy the `key` value and use that for the `Key` setting.

Note, this only works on web clients.

<Frame>
<img
src="/images/blocks/integrations/zendesk/web-widget-config.png"
alt="Web Widget Key"
/>
</Frame>

If `Enable User Authentication` is enabled, and a token is set in the `User Auth Token` field, the user will be authenticated in Zendesk.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion apps/docs/mint.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@
"editor/blocks/integrations/anthropic",
"editor/blocks/integrations/dify-ai",
"editor/blocks/integrations/nocodb",
"editor/blocks/integrations/segment"
"editor/blocks/integrations/segment",
"editor/blocks/integrations/zendesk"
]
}
]
Expand Down
49 changes: 49 additions & 0 deletions packages/forge/blocks/zendesk/actions/authenticateMessagingUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { createAction, option } from '@typebot.io/forge'
import { sign } from 'jsonwebtoken';
import { auth } from '../auth'

export const authenticateMessagingUser = createAction({
name: 'Authenticate Messaging User',
auth,
options: option.object({
userId: option.string.layout({
label: 'User ID',
isRequired: true,
}),
name: option.string.layout({
label: 'Name',
isRequired: true,
}),
email: option.string.layout({
label: 'Email',
isRequired: true,
}),
isEmailVerified: option.string.layout({
label: 'Is email verified',
inputType: 'variableDropdown',
isRequired: true
}),
tokenVariableId: option.string.layout({
label: 'Token variable',
inputType: 'variableDropdown',
isRequired: true
}),
}),
run: {
server: async ({
credentials: { conversationsSecretKey, conversationsKeyId },
options: { userId, name, email, isEmailVerified, tokenVariableId },
variables,
}) => {
if (!email || email.length === 0
|| !userId || userId.length === 0
|| !name || name.length === 0
|| conversationsSecretKey === undefined
|| conversationsKeyId === undefined
|| tokenVariableId === undefined
) return
variables.set(tokenVariableId, sign({ scope: 'user', external_id: userId, name: name, email: email, email_verified: "true" }, conversationsSecretKey, { algorithm: "HS256", keyid: conversationsKeyId }));
}
},
})

66 changes: 66 additions & 0 deletions packages/forge/blocks/zendesk/actions/openWebWidget.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { createAction, option } from '@typebot.io/forge'
import { auth } from '../auth'

export const openWebWidget = createAction({
auth,
name: 'Open Web Widget',
options: option.object({
key: option.string.layout({
label: "Web widget key",
isRequired: true,
}),
isAuthEnabled: option.boolean.layout({
accordion: "Authentication",
label: "Enable user authentication",
defaultValue: false,
}),
token: option.string.layout({
accordion: "Authentication",
label: "User auth token",
isRequired: false
})
}),
run: {
web: {
parseFunction: ({ options }) => {
return {
args: {
isAuthEnabled: options.isAuthEnabled ? options.isAuthEnabled?.toString() : "false",
token: options.token ?? null,
key: options.key ?? null
},
content: parseOpenMessenger()
}
}
},
},
})

const parseOpenMessenger = () => {
return `(function (d, t) {
var ZD_URL = "https://static.zdassets.com/ekr/snippet.js?key=" + key;

var ze_script = d.createElement(t);
var s = d.getElementsByTagName(t)[0];

ze_script.id="ze-snippet";
ze_script.src = ZD_URL;
ze_script.crossorigin = "anonymous";
ze_script.defer = true;
ze_script.async = true;
s.parentNode.insertBefore(ze_script, s);

ze_script.onload = function () {
if ( isAuthEnabled === "true" ) {
zE("messenger", "loginUser", function (callback) {
callback(token);
zE("messenger", "open");
});
} else {
zE("messenger", "open");
}
};
})(document, "script");
`
}

21 changes: 21 additions & 0 deletions packages/forge/blocks/zendesk/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { option, AuthDefinition } from '@typebot.io/forge'

export const auth = {
type: 'encryptedCredentials',
name: 'Zendesk Conversations API',
schema: option.object({
conversationsSecretKey: option.string.layout({
label: 'Conversations Secret Key',
isRequired: true,
inputType: 'password',
withVariableButton: false,
isDebounceDisabled: true,
}),
conversationsKeyId: option.string.layout({
label: 'Conversations Key ID',
isRequired: true,
withVariableButton: false,
isDebounceDisabled: true,
})
}),
} satisfies AuthDefinition
14 changes: 14 additions & 0 deletions packages/forge/blocks/zendesk/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createBlock } from '@typebot.io/forge'
import { ZendeskLogo } from './logo'
import { auth } from './auth'
import { authenticateMessagingUser } from './actions/authenticateMessagingUser'
import { openWebWidget } from './actions/openWebWidget'

export const zendeskBlock = createBlock({
id: 'zendesk',
name: 'Zendesk',
tags: ['live chat', 'crm'],
LightLogo: ZendeskLogo,
auth,
actions: [authenticateMessagingUser, openWebWidget],
})
20 changes: 20 additions & 0 deletions packages/forge/blocks/zendesk/logo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/** @jsxImportSource react */

export const ZendeskLogo = (props: React.SVGProps<SVGSVGElement>) => (
<svg viewBox="0 0 256.00 256.00" {...props}>
<g id="SVGRepo_bgCarrier" stroke-width="0" />
<g
id="SVGRepo_tracerCarrier"
stroke-linecap="round"
stroke-linejoin="round"
/>
<g id="SVGRepo_iconCarrier">
<g>
<path
d="M118.249172,51.2326115 L118.249172,194.005605 L0,194.005605 L118.249172,51.2326115 Z M118.249172,2.84217094e-14 C118.249172,32.6440764 91.7686624,59.124586 59.124586,59.124586 C26.4805096,59.124586 0,32.6440764 0,2.84217094e-14 L118.249172,2.84217094e-14 Z M137.750828,194.005605 C137.750828,161.328917 164.198726,134.881019 196.875414,134.881019 C229.552102,134.881019 256,161.361529 256,194.005605 L137.750828,194.005605 Z M137.750828,142.740382 L137.750828,0 L256,0 L137.750828,142.740382 Z"
fill="#03363D"
></path>
</g>
</g>
</svg>
)
19 changes: 19 additions & 0 deletions packages/forge/blocks/zendesk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@typebot.io/zendesk-block",
"version": "1.0.0",
"description": "",
"main": "index.ts",
"keywords": [],
"license": "AGPL-3.0-or-later",
"devDependencies": {
"@typebot.io/forge": "workspace:*",
"@typebot.io/tsconfig": "workspace:*",
"@types/react": "18.2.15",
"typescript": "5.4.5",
"@typebot.io/lib": "workspace:*",
"@types/jsonwebtoken": "9.0.2"
},
"dependencies": {
"jsonwebtoken": "9.0.1"
}
}
10 changes: 10 additions & 0 deletions packages/forge/blocks/zendesk/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Do not edit this file manually
import { parseBlockCredentials, parseBlockSchema } from '@typebot.io/forge'
import { zendeskBlock } from '.'
import { auth } from './auth'

export const zendeskBlockSchema = parseBlockSchema(zendeskBlock)
export const zendeskCredentialsSchema = parseBlockCredentials(
zendeskBlock.id,
auth.schema
)
11 changes: 11 additions & 0 deletions packages/forge/blocks/zendesk/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "@typebot.io/tsconfig/base.json",
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"],
"compilerOptions": {
"lib": ["ESNext", "DOM"],
"noEmit": true,
"jsx": "preserve",
"jsxImportSource": "react"
}
}
1 change: 1 addition & 0 deletions packages/forge/repository/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export const forgedBlockIds = [
'nocodb',
'segment',
'groq',
'zendesk',
] as const satisfies ForgedBlock['type'][]
3 changes: 3 additions & 0 deletions packages/forge/repository/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { segmentBlock } from '@typebot.io/segment-block'
import { segmentCredentialsSchema } from '@typebot.io/segment-block/schemas'
import { groqBlock } from '@typebot.io/groq-block'
import { groqCredentialsSchema } from '@typebot.io/groq-block/schemas'
import { zendeskBlock } from '@typebot.io/zendesk-block'
import { zendeskCredentialsSchema } from '@typebot.io/zendesk-block/schemas'

export const forgedCredentialsSchemas = {
[openAIBlock.id]: openAICredentialsSchema,
Expand All @@ -33,4 +35,5 @@ export const forgedCredentialsSchemas = {
[nocodbBlock.id]: nocodbCredentialsSchema,
[segmentBlock.id]: segmentCredentialsSchema,
[groqBlock.id]: groqCredentialsSchema,
[zendeskBlock.id]: zendeskCredentialsSchema,
}
2 changes: 2 additions & 0 deletions packages/forge/repository/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { openAIBlock } from '@typebot.io/openai-block'
import { nocodbBlock } from '@typebot.io/nocodb-block'
import { segmentBlock } from '@typebot.io/segment-block'
import { groqBlock } from '@typebot.io/groq-block'
import { zendeskBlock } from '@typebot.io/zendesk-block'

export const forgedBlocks = {
[openAIBlock.id]: openAIBlock,
Expand All @@ -27,4 +28,5 @@ export const forgedBlocks = {
[nocodbBlock.id]: nocodbBlock,
[segmentBlock.id]: segmentBlock,
[groqBlock.id]: groqBlock,
[zendeskBlock.id]: zendeskBlock,
}
3 changes: 2 additions & 1 deletion packages/forge/repository/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@typebot.io/open-router-block": "workspace:*",
"@typebot.io/nocodb-block": "workspace:*",
"@typebot.io/segment-block": "workspace:*",
"@typebot.io/groq-block": "workspace:*"
"@typebot.io/groq-block": "workspace:*",
"@typebot.io/zendesk-block": "workspace:*"
}
}
3 changes: 3 additions & 0 deletions packages/forge/repository/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { segmentBlock } from '@typebot.io/segment-block'
import { segmentBlockSchema } from '@typebot.io/segment-block/schemas'
import { groqBlock } from '@typebot.io/groq-block'
import { groqBlockSchema } from '@typebot.io/groq-block/schemas'
import { zendeskBlock } from '@typebot.io/zendesk-block'
import { zendeskBlockSchema } from '@typebot.io/zendesk-block/schemas'

export const forgedBlockSchemas = {
[openAIBlock.id]: openAIBlockSchema,
Expand All @@ -40,4 +42,5 @@ export const forgedBlockSchemas = {
[nocodbBlock.id]: nocodbBlockSchema,
[segmentBlock.id]: segmentBlockSchema,
[groqBlock.id]: groqBlockSchema,
[zendeskBlock.id]: zendeskBlockSchema,
}
Loading