Skip to content

Commit

Permalink
✨ Add Zendesk block (#1794)
Browse files Browse the repository at this point in the history
Adds a new Zendesk block with initial support for launching the Zendesk
Messaging Web Widget

---------

Co-authored-by: John Walsh <john@famkit.com>
Co-authored-by: Baptiste Arnaud <baptiste.arnaud95@gmail.com>
  • Loading branch information
3 people authored Sep 24, 2024
1 parent f613ce3 commit 8ced42d
Show file tree
Hide file tree
Showing 17 changed files with 253 additions and 6 deletions.
37 changes: 37 additions & 0 deletions apps/docs/editor/blocks/integrations/zendesk.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
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

## 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 the `User ID` option is set, a JWT token will be created and passed to Zendesk, to authenticate the user in Zendesk. If you need `Name` or `Email` included in the JWT token, set those options also.
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
76 changes: 76 additions & 0 deletions packages/forge/blocks/zendesk/actions/openWebWidget.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { createAction, option } from '@typebot.io/forge'
import { sign } from 'jsonwebtoken';
import { auth } from '../auth'

export const openWebWidget = createAction({
auth,
name: 'Open Web Widget',
options: option.object({
userId: option.string.layout({
label: 'User ID',
isRequired: false,
}),
name: option.string.layout({
label: 'Name',
isRequired: false,
}),
email: option.string.layout({
label: 'Email',
isRequired: false,
}),
}),
run: {
web: {
parseFunction: ({
credentials: { conversationsSecretKey, conversationsKeyId, webWidgetKey },
options: { userId, name, email },
}) => {
let token = ''

if (userId && email) {
token = sign({ scope: 'user', external_id: userId ?? '', name: name ?? '', email: email, email_verified: "true" }, conversationsSecretKey ?? '', { algorithm: "HS256", keyid: conversationsKeyId ?? '' });
} else if (userId) {
token = sign({ scope: 'user', external_id: userId ?? '', name: name ?? '' }, conversationsSecretKey ?? '', { algorithm: "HS256", keyid: conversationsKeyId ?? '' });
}

return {
args: {
isAuthEnabled: (userId !== undefined && userId != '') ? "true" : "false",
token: token,
key: webWidgetKey ?? ''
},
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" && token != "") {
zE("messenger", "loginUser", function (callback) {
callback(token);
zE("messenger", "open");
});
} else {
zE("messenger", "open");
}
};
})(document, "script");
`
}

31 changes: 31 additions & 0 deletions packages/forge/blocks/zendesk/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
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,
helperText:
'[How to find my Zendesk Key ID and Secret Key?](https://docs.typebot.io/editor/blocks/integrations/zendesk#how-to-find-my-zendesk-key-id-and-secret-key)',
}),
webWidgetKey: option.string.layout({
label: 'Web Widget Key',
isRequired: false,
withVariableButton: false,
isDebounceDisabled: true,
helperText:
'[Finding web widget key](http://localhost:3004/editor/blocks/integrations/zendesk#open-web-widget)',
})
}),
} 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 { openWebWidget } from './actions/openWebWidget'

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

export const ZendeskLogo = (props: React.SVGProps<SVGSVGElement>) => (
<svg viewBox="0 0 800 607" {...props}>
<path
d="M369.529 160.414V606.58H0L369.529 160.414ZM369.529 0.3125C369.529 102.325 286.777 185.077 184.764 185.077C82.7516 185.077 0 102.325 0 0.3125H369.529ZM430.471 606.58C430.471 504.465 513.121 421.816 615.236 421.816C717.35 421.816 800 504.567 800 606.58H430.471ZM430.471 446.376V0.3125H800L430.471 446.376Z"
fill="#03363D"
/>
</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,
}
36 changes: 32 additions & 4 deletions pnpm-lock.yaml

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

0 comments on commit 8ced42d

Please sign in to comment.